#!/usr/local/bin/python import sys from BaseHTTPServer import BaseHTTPRequestHandler from ZSI import Fault, ParseException, FaultFromNotUnderstood, \ FaultFromZSIException, FaultFromException, ParsedSoap, SoapWriter from M2Crypto import SSL from M2Crypto.SSL.SSLServer import SSLServer import xmlrpclib from optparse import OptionParser from fedd_util import fedd_ssl_context, fedid from fedd_proj import new_feddservice # The SSL server here is based on the implementation described at # http://www.xml.com/pub/a/ws/2004/01/20/salz.html # Turn off the matching of hostname to certificate ID SSL.Connection.clientPostConnectionCheck = None class fedd_server(SSLServer): def __init__(self, ME, handler, ssl_ctx, impl): SSLServer.__init__(self, ME, handler, ssl_ctx) self.impl = impl class fedd_soap_handler(BaseHTTPRequestHandler): server_version = "ZSI/2.0 fedd/0.1 " + BaseHTTPRequestHandler.server_version def send_xml(self, text, code=200): """Send an XML document as reply""" self.send_response(code) self.send_header('Content-type', 'text/xml; charset="utf-8"') self.send_header('Content-Length', str(len(text))) self.end_headers() self.wfile.write(text) self.wfile.flush() def send_fault(self, f, code=500): """Send a SOAP encoded fault as reply""" self.send_xml(f.AsSOAP(processContents="lax"), code) def check_headers(self, ps): """Send a fault for any required envelope headers""" for (uri, localname) in ps.WhatMustIUnderstand(): self.send_fault(FaultFromNotUnderstood(uri, lname, 'fedd')) return False return True def check_method(self, ps): """Confirm that this class implements the namespace and SOAP method""" root = ps.body_root if root.namespaceURI not in self.server.impl.soap_namespaces: self.send_fault(Fault(Fault.Client, 'Unknown namespace "%s"' % root.namespaceURI)) return False if getattr(root, 'localName', None) == None: self.send_fault(Fault(Fault.Client, 'No method"')) return False return True def do_POST(self): """Treat an HTTP POST request as a SOAP service call""" try: cl = int(self.headers['content-length']) data = self.rfile.read(cl) ps = ParsedSoap(data) except ParseException, e: self.send_fault(Fault(Fault.Client, str(e))) return except Exception, e: self.send_fault(FaultFromException(e, 0, sys.exc_info()[2])) return if not self.check_headers(ps): return if not self.check_method(ps): return try: resp = self.server.impl.soap_dispatch(ps.body_root.localName, ps, fedid(cert=self.request.get_peer_cert())) except Fault, f: self.send_fault(f) resp = None if resp != None: sw = SoapWriter() sw.serialize(resp) self.send_xml(str(sw)) class fedd_xmlrpc_handler(BaseHTTPRequestHandler): server_version = "ZSI/2.0 fedd/0.1 " + BaseHTTPRequestHandler.server_version def send_xml(self, text, code=200): """Send an XML document as reply""" self.send_response(code) self.send_header('Content-type', 'text/xml; charset="utf-8"') self.send_header('Content-Length', str(len(text))) self.end_headers() self.wfile.write(text) self.wfile.flush() def do_POST(self): """Treat an HTTP POST request as an XMLRPC service call""" resp = None data = None method = None cl = int(self.headers['content-length']) data = self.rfile.read(cl) try: params, method = xmlrpclib.loads(data) except xmlrpclib.ResponseError: data = xmlrpclib.dumps(xmlrpclib.Fault("Client", "Malformed request"), methodresponse=True) if method != None: try: resp = self.server.impl.xmlrpc_dispatch(method, params, fedid(cert=self.request.get_peer_cert())) data = xmlrpclib.dumps((resp,), methodresponse=True) #except Fault, f: # xf = xmlrpclib.Fault(f.code, f.string) # data = xmlrpclib.dumps(xf, methodresponse=True) # resp = None except xmlrpclib.Fault, f: data = xmlrpclib.dumps(f, methodresponse=True) resp = None self.send_xml(data) class fedd_opts(OptionParser): """Encapsulate option processing in this class, rather than in main""" def __init__(self): OptionParser.__init__(self, usage="%prog [opts] (--help for details)", version="0.1") self.set_defaults(host="localhost", port=23235, transport="soap",debug=0) self.add_option("-d", "--debug", action="count", dest="debug", help="Set debug. Repeat for more information") self.add_option("-f", "--configfile", action="store", dest="configfile", help="Configuration file (required)") self.add_option("-H", "--host", action="store", type="string", dest="host", help="Hostname to listen on (default %default)") self.add_option("-p", "--port", action="store", type="int", dest="port", help="Port to listen on (default %default)") self.add_option("-x","--transport", action="store", type="choice", choices=("xmlrpc", "soap"), help="Transport for request (xmlrpc|soap) (Default: %default)") self.add_option("--trace", action="store_const", dest="tracefile", const=sys.stderr, help="Print SOAP exchange to stderr") opts, args = fedd_opts().parse_args() if opts.configfile != None: try: impl = new_feddservice(opts.configfile) except RuntimeError, e: str = getattr(e, 'desc', None) or getattr(e,'message', None) or \ "No message" sys.exit("Error configuring fedd: %s" % str) else: sys.exit("--configfile is required") SOAP_port = (opts.host, opts.port) if impl.cert_file == None: sys.exit("Must supply certificate file (probably in config)") ctx = None while ctx == None: try: ctx = fedd_ssl_context(impl.cert_file, impl.trusted_certs, password=impl.cert_pwd) except SSL.SSLError, e: if str(e) != "bad decrypt" or impl.cert_pwd != None: raise if opts.transport == "soap": s = fedd_server(SOAP_port, fedd_soap_handler, ctx, impl) elif opts.transport == "xmlrpc": s = fedd_server(SOAP_port, fedd_xmlrpc_handler, ctx, impl) else: s = None s.serve_forever()