4 Copyright 2007, Red Hat, Inc
7 This software may be freely redistributed under the terms of the GNU
8 general public license.
10 You should have received a copy of the GNU General Public License
11 along with this program; if not, write to the Free Software
12 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
16 import SimpleXMLRPCServer
23 from gettext
import textdomain
27 from func
.config
import read_config
28 from func
.commonconfig
import FuncdConfig
29 from func
import logger
30 from func
import certs
31 import func
.jobthing
as jobthing
35 import AuthedXMLRPCServer
38 import func
.utils
as futils
42 class XmlRpcInterface(object):
50 config_file
= '/etc/func/minion.conf'
51 self
.config
= read_config(config_file
, FuncdConfig
)
52 self
.logger
= logger
.Logger().logger
53 self
.audit_logger
= logger
.AuditLogger()
54 self
.__setup
_handlers
()
56 # need a reference so we can log ip's, certs, etc
57 # self.server = server
59 def __setup_handlers(self
):
62 Add RPC functions from each class to the global list so they can be called.
66 for x
in self
.modules
.keys():
68 self
.modules
[x
].register_rpc(self
.handlers
, x
)
69 self
.logger
.debug("adding %s" % x
)
70 except AttributeError, e
:
71 self
.logger
.warning("module %s not loaded, missing register_rpc method" % self
.modules
[x
])
74 # internal methods that we do instead of spreading internal goo
75 # all over the modules. For now, at lest -akl
78 # system.listMethods os a quasi stanard xmlrpc method, so
79 # thats why it has a odd looking name
80 self
.handlers
["system.listMethods"] = self
.list_methods
81 self
.handlers
["system.list_methods"] = self
.list_methods
82 self
.handlers
["system.list_modules"] = self
.list_modules
84 def list_modules(self
):
85 modules
= self
.modules
.keys()
89 def list_methods(self
):
90 methods
= self
.handlers
.keys()
94 def get_dispatch_method(self
, method
):
96 if method
in self
.handlers
:
97 return FuncApiMethod(self
.logger
, method
, self
.handlers
[method
])
100 self
.logger
.info("Unhandled method call for method: %s " % method
)
101 raise codes
.InvalidMethodException
107 Used to hold a reference to all of the registered functions.
110 def __init__(self
, logger
, name
, method
):
113 self
.__method
= method
122 (t
, v
, tb
) = sys
.exc_info()
123 self
.logger
.info("Exception occured: %s" % t
)
124 self
.logger
.info("Exception value: %s" % v
)
125 self
.logger
.info("Exception Info:\n%s" % string
.join(traceback
.format_list(traceback
.extract_tb(tb
))))
127 def __call__(self
, *args
):
129 self
.logger
.debug("(X) -------------------------------------------")
132 rc
= self
.__method
(*args
)
133 except codes
.FuncException
, e
:
135 (t
, v
, tb
) = sys
.exc_info()
136 rc
= futils
.nice_exception(t
,v
,tb
)
139 (t
, v
, tb
) = sys
.exc_info()
140 rc
= futils
.nice_exception(t
,v
,tb
)
141 self
.logger
.debug("Return code for %s: %s" % (self
.__name
, rc
))
149 Code for starting the XMLRPC service.
151 server
=FuncSSLXMLRPCServer(('', 51234))
152 server
.logRequests
= 0 # don't print stuff to console
153 server
.serve_forever()
157 class FuncXMLRPCServer(SimpleXMLRPCServer
.SimpleXMLRPCServer
, XmlRpcInterface
):
159 def __init__(self
, args
):
161 self
.allow_reuse_address
= True
163 self
.modules
= module_loader
.load_modules()
164 SimpleXMLRPCServer
.SimpleXMLRPCServer
.__init
__(self
, args
)
165 XmlRpcInterface
.__init
__(self
)
168 class FuncSSLXMLRPCServer(AuthedXMLRPCServer
.AuthedSSLXMLRPCServer
,
170 def __init__(self
, args
):
171 self
.allow_reuse_address
= True
172 self
.modules
= module_loader
.load_modules()
174 XmlRpcInterface
.__init
__(self
)
175 hn
= utils
.get_hostname()
176 self
.key
= "%s/%s.pem" % (self
.config
.cert_dir
, hn
)
177 self
.cert
= "%s/%s.cert" % (self
.config
.cert_dir
, hn
)
178 self
.ca
= "%s/ca.cert" % self
.config
.cert_dir
180 self
._our
_ca
= certs
.retrieve_cert_from_file(self
.ca
)
182 AuthedXMLRPCServer
.AuthedSSLXMLRPCServer
.__init
__(self
, ("", 51234),
186 def _dispatch(self
, method
, params
):
189 the SimpleXMLRPCServer class will call _dispatch if it doesn't
190 find a handler method
192 # take _this_request and hand it off to check out the acls of the method
193 # being called vs the requesting host
195 if not hasattr(self
, '_this_request'):
196 raise codes
.InvalidMethodException
198 r
,a
= self
._this
_request
199 peer_cert
= r
.get_peer_certificate()
203 # generally calling conventions are: hardware.info
204 # async convention is async.hardware.info
205 # here we parse out the async to decide how to invoke it.
206 # see the async docs on the Wiki for further info.
207 async_dispatch
= False
208 if method
.startswith("async."):
209 async_dispatch
= True
210 method
= method
.replace("async.","",1)
212 if not self
._check
_acl
(peer_cert
, ip
, method
, params
):
213 raise codes
.AccessToMethodDenied
215 # Recognize ipython's tab completion calls
216 if method
== 'trait_names' or method
== '_getAttributeNames':
217 return self
.handlers
.keys()
219 cn
= peer_cert
.get_subject().CN
220 sub_hash
= peer_cert
.subject_name_hash()
221 self
.audit_logger
.log_call(ip
, cn
, sub_hash
, method
, params
)
224 if not async_dispatch
:
225 return self
.get_dispatch_method(method
)(*params
)
227 return jobthing
.minion_async_run(self
.get_dispatch_method
, method
, params
)
229 (t
, v
, tb
) = sys
.exc_info()
230 rc
= futils
.nice_exception(t
, v
, tb
)
233 def auth_cb(self
, request
, client_address
):
234 peer_cert
= request
.get_peer_certificate()
235 return peer_cert
.get_subject().CN
237 def _check_acl(self
, cert
, ip
, method
, params
):
238 acls
= utils
.get_acls_from_config(acldir
=self
.config
.acl_dir
)
240 # certmaster always gets to run things
241 ca_cn
= self
._our
_ca
.get_subject().CN
242 ca_hash
= self
._our
_ca
.subject_name_hash()
243 ca_key
= '%s-%s' % (ca_cn
, ca_hash
)
246 cn
= cert
.get_subject().CN
247 sub_hash
= cert
.subject_name_hash()
250 hostkey
= '%s-%s' % (cn
, sub_hash
)
251 # search all the keys, match to 'cn-subhash'
252 for hostmatch
in acls
.keys():
253 if fnmatch
.fnmatch(hostkey
, hostmatch
):
254 allow_list
.extend(acls
[hostmatch
])
255 # go through the allow_list and make sure this method is in there
256 for methodmatch
in allow_list
:
257 if fnmatch
.fnmatch(method
, methodmatch
):
269 if "daemon" in sys
.argv
or "--daemon" in sys
.argv
:
270 futils
.daemonize("/var/run/funcd.pid")
275 utils
.create_minion_keys()
277 except codes
.FuncException
, e
:
278 print >> sys
.stderr
, 'error: %s' % e
282 # ======================================================================================
283 if __name__
== "__main__":
284 textdomain(I18N_DOMAIN
)