From 959d380863f429cdc322d66f8a3341af5d4d8058 Mon Sep 17 00:00:00 2001 From: Narayan Desai Date: Mon, 24 Oct 2005 08:47:26 +0000 Subject: sync from cobalt tree 2005/10/24 03:06:46-05:00 anl.gov!desai (Logical change 1.341) git-svn-id: https://svn.mcs.anl.gov/repos/bcfg/trunk/bcfg2@1404 ce84e21b-d406-0410-9b95-82705330c041 --- src/lib/Server/Component.py | 177 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) (limited to 'src') diff --git a/src/lib/Server/Component.py b/src/lib/Server/Component.py index e69de29bb..54af40db8 100644 --- a/src/lib/Server/Component.py +++ b/src/lib/Server/Component.py @@ -0,0 +1,177 @@ +'''Cobalt component base classes''' +__revision__ = '$Revision: 1.4 $' + +from ConfigParser import ConfigParser, NoOptionError +from cPickle import loads, dumps +from M2Crypto import SSL +from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler, SimpleXMLRPCDispatcher +from select import select +from socket import gethostname +from sys import exc_info +import sys +from syslog import openlog, syslog, LOG_INFO, LOG_ERR, LOG_LOCAL0 +from traceback import extract_tb +from xmlrpclib import dumps, loads, Fault +from urlparse import urlparse + +class CobaltXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): + '''CobaltXMLRPCRequestHandler takes care of ssl xmlrpc requests''' + def finish(self): + '''Finish HTTPS connections properly''' + self.request.set_shutdown(SSL.SSL_RECEIVED_SHUTDOWN | SSL.SSL_SENT_SHUTDOWN) + self.request.close() + + def do_POST(self): + '''Overload do_POST to pass through client address information''' + try: + # get arguments + data = self.rfile.read(int(self.headers["content-length"])) + response = self.server._cobalt_marshalled_dispatch(data, self.client_address) + except: # This should only happen if the module is buggy + # internal error, report as HTTP server error + (trace, val, trb) = exc_info() + syslog(LOG_ERR, "Unexpected failure in handler") + for line in extract_tb(trb): + syslog(LOG_ERR, ' File "%s", line %i, in %s\n %s\n' % line) + syslog(LOG_ERR, "%s: %s\n"%(trace, val)) + del trace, val, trb + self.send_response(500) + self.end_headers() + else: + # got a valid XML RPC response + self.send_response(200) + self.send_header("Content-type", "text/xml") + self.send_header("Content-length", str(len(response))) + self.end_headers() + self.wfile.write(response) + + # shut down the connection + self.wfile.flush() + self.connection.shutdown(1) + +class Component(SSL.SSLServer, + SimpleXMLRPCDispatcher): + """Cobalt component providing XML-RPC access""" + __name__ = 'Component' + __implementation__ = 'Generic' + __statefields__ = [] + + def __init__(self, setup): + # need to get addr + self.setup = setup + self.cfile = ConfigParser() + openlog(self.__implementation__, 0, LOG_LOCAL0) + if setup['configfile']: + cfilename = setup['configfile'] + else: + cfilename = '/etc/cobalt.conf' + self.cfile.read([cfilename]) + if not self.cfile.has_section('communication'): + print "Configfile missing communication section" + raise SystemExit, 1 + self.static = False + if not self.cfile.has_section('components'): + print "Configfile missing components section" + raise SystemExit, 1 + + if self.cfile._sections['components'].has_key(self.__name__): + self.static = True + location = urlparse(self.cfile.get('components', self.__name__))[1].split(':') + location = (location[0], int(location[1])) + else: + location = (gethostname(), 0) + + self.password = self.cfile.get('communication', 'password') + sslctx = SSL.Context('sslv23') + try: + keyfile = self.cfile.get('communication', 'key') + except NoOptionError: + print "No key specified in cobalt.conf" + raise SystemExit, 1 + sslctx.load_cert_chain(keyfile) + #sslctx.load_verify_locations('ca.pem') + #sslctx.set_client_CA_list_from_file('ca.pem') + sslctx.set_verify(SSL.verify_none, 15) + #sslctx.set_allow_unknown_ca(1) + sslctx.set_session_id_ctx(self.__name__) + sslctx.set_info_callback(self.handle_sslinfo) + #sslctx.set_tmp_dh('dh1024.pem') + self.logRequests = 0 + # setup unhandled request syslog handling + SimpleXMLRPCDispatcher.__init__(self) + SSL.SSLServer.__init__(self, location, CobaltXMLRPCRequestHandler, sslctx) + self.port = self.socket.socket.getsockname()[1] + syslog(LOG_INFO, "Bound to port %s" % self.port) + self.funcs.update({'HandleEvents':self.HandleEvents, + 'system.listMethods':self.system_listMethods}) + + def HandleEvents(self, address, event_list): + '''Default event handler''' + return True + + def handle_sslinfo(self, where, ret, ssl_ptr): + '''This is where we need to handle all ssl negotiation issues''' + pass + + def _cobalt_marshalled_dispatch(self, data, address): + """Decode and dispatch XMLRPC requests. Overloaded to pass through + client address information + """ + rawparams, method = loads(data) + if len(rawparams) < 2: + syslog(LOG_ERR, "No authentication included with request from %s" % address[0]) + return dumps(Fault(2, "No Authentication Info")) + user = rawparams[0] + password = rawparams[1] + params = rawparams[2:] + # check authentication + if not self._authenticate_connection(method, user, password, address): + syslog(LOG_ERR, "Authentication failure from %s" % address[0]) + return dumps(Fault(3, "Authentication Failure")) + # generate response + try: + # all handlers must take address as the first argument + response = self._dispatch(method, (address, ) + params) + # wrap response in a singleton tuple + response = (response,) + response = dumps(response, methodresponse=1) + except Fault, fault: + response = dumps(fault) + except: + # report exception back to server + response = dumps(Fault(1, + "%s:%s" % (sys.exc_type, sys.exc_value))) + + return response + + def _authenticate_connection(self, method, user, password, address): + '''Authenticate new connection''' + (user, address, method) + return password == self.password + + def save_state(self): + '''Save fields defined in __statefields__ in /var/spool/cobalt/__implementation__''' + if self.__statefields__: + savedata = tuple([getattr(self, field) for field in self.__statefields__]) + try: + statefile = open("/var/spool/cobalt/%s" % self.__implementation__, 'w') + # need to flock here + statefile.write(dumps(savedata)) + except: + syslog(LOG_INFO, "Statefile save failed; data persistence disabled") + self.__statefields__ = [] + + def load_state(self): + '''Load fields defined in __statefields__ from /var/spool/cobalt/__implementation__''' + if self.__statefields__: + try: + loaddata = loads(open("/var/spool/cobalt/%s" % self.__implementation__).read()) + except: + syslog(LOG_INFO, "Statefile load failed") + return + for field in self.__statefields__: + setattr(self, field, loaddata[self.__statefields__.index(field)]) + + def system_listMethods(self, address): + """get rid of the address argument and call the underlying dispatcher method""" + return SimpleXMLRPCDispatcher.system_listMethods(self) -- cgit v1.2.3-1-g7c22