#!/usr/bin/env python # Authors: # Rob Crittenden # # Copyright (C) 2008 Red Hat # see file 'COPYING' for use and warranty information # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation; version 2 only # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ In-tree XML-RPC server using SimpleXMLRPCServer. """ import sys import SimpleXMLRPCServer import logging import xmlrpclib import re import threading import commands from ipalib import api from ipalib import config from ipaserver import conn from ipaserver.servercore import context from ipalib.util import xmlrpc_unmarshal import traceback import krbV class StoppableXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer): """Override of TIME_WAIT""" allow_reuse_address = True def serve_forever(self): self.stop = False while not self.stop: self.handle_request() class LoggingSimpleXMLRPCRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler): """Overides the default SimpleXMLRPCRequestHander to support logging. Logs client IP and the XML request and response. """ def parse(self, given): """Convert the incoming arguments into the format IPA expects""" args = [] kw = {} for g in given: kw[g] = unicode(given[g]) return (args, kw) def _dispatch(self, method, params): """ Dispatches the XML-RPC method. Methods beginning with an '_' are considered private and will not be called. """ if method not in funcs: logger.error('no such method %r', method) raise Exception('method "%s" is not supported' % method) func = funcs[method] krbccache = krbV.default_context().default_ccache().name context.conn = conn.IPAConn( api.env.ldap_host, api.env.ldap_port, krbccache, ) logger.info('calling %s', method) (args, kw) = xmlrpc_unmarshal(*params) return func(*args, **kw) def _marshaled_dispatch(self, data, dispatch_method = None): try: params, method = xmlrpclib.loads(data) # generate response if dispatch_method is not None: response = dispatch_method(method, params) else: response = self._dispatch(method, params) # wrap response in a singleton tuple response = (response,) response = xmlrpclib.dumps(response, methodresponse=1) except: # report exception back to client. This is needed to report # tracebacks found in server code. e_class, e = sys.exc_info()[:2] # FIXME, need to get this number from somewhere... faultCode = getattr(e_class,'faultCode',1) tb_str = ''.join(traceback.format_exception(*sys.exc_info())) faultString = tb_str response = xmlrpclib.dumps(xmlrpclib.Fault(faultCode, faultString)) return response def do_POST(self): clientIP, port = self.client_address # Log client IP and Port logger.info('Client IP: %s - Port: %s' % (clientIP, port)) try: # get arguments data = self.rfile.read(int(self.headers["content-length"])) # unmarshal the XML data params, method = xmlrpclib.loads(data) logger.info('Call to %s(%s) from %s:%s', method, ', '.join(repr(p) for p in params), clientIP, port ) # Log client request logger.debug('Client request: \n%s\n' % data) response = self._marshaled_dispatch( data, getattr(self, '_dispatch', None)) # Log server response logger.debug('Server response: \n%s\n' % response) except Exception, e: # This should only happen if the module is buggy # internal error, report as HTTP server error print e 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) if __name__ == '__main__': api.bootstrap_with_global_options(context='server') api.finalize() logger = api.log # Set up the server XMLRPCServer = StoppableXMLRPCServer( ('', api.env.lite_xmlrpc_port), LoggingSimpleXMLRPCRequestHandler ) XMLRPCServer.register_introspection_functions() # Get and register all the methods for cmd in api.Command: logger.debug('registering %s', cmd) XMLRPCServer.register_function(api.Command[cmd], cmd) funcs = XMLRPCServer.funcs logger.info('Logging to file %r', api.env.log) logger.info('Listening on port %d', api.env.lite_xmlrpc_port) try: XMLRPCServer.serve_forever() except KeyboardInterrupt: XMLRPCServer.server_close() logger.info('Server shutdown.')