#!/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 ThreadingSSLServer import xmlrpclib from deter import fedid # ZSI uses a deprecated multifile interface. This shuts the warning system up. from warnings import filterwarnings filterwarnings("ignore", ".*multifile.*", DeprecationWarning, "ZSI") try: from fedd_services import ns0 except ImportError: from fedd_server import ns0 from service_error import * import os.path import logging import traceback # 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 server(ThreadingSSLServer): """ Interface the fedd services to the XMLRPC and SOAP interfaces """ def __init__(self, ME, handler, ssl_ctx, impl, debug=False): """ Create an SSL server that handles the transport in handler using the credentials in ssl_ctx, and interfacing to the implementation of fedd services in fedd. ME is the host port pair on which to bind. """ ThreadingSSLServer.__init__(self, ME, handler, ssl_ctx) self.impl = impl self.soap_methods = impl.soap_services self.xmlrpc_methods = impl.xmlrpc_services self.get_handler = impl.get_handler self.log = logging.getLogger("fedd") if debug: self.handle_error = self.handle_error_debug else: self.handle_error = self.handle_error_standard def handle_error_debug(self, request=None, client_address=None): print '-'*40 traceback.print_exc() print '-'*40 def handle_error_standard(self, request=None, address=None): """ The default SSLServer prints a stack trace here. This is a little friendlier. """ if request or address: self.log.warn("[fedd] Error on incoming connection: %s %s" % \ (request or "", address or "")) else: self.log.warn("[fedd] Error on incoming connection " + \ "(Likely SSL error)") class soap_handler(BaseHTTPRequestHandler): """ Standard connection between SOAP and the fedd services in impl. Much of this is boilerplate from http://www.xml.com/pub/a/ws/2004/01/20/salz.html """ 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() self.request.socket.close() 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.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)) def do_GET(self): """ If a get handler is registered, use that to retrieve data. """ if self.server.get_handler: code = 200 fid = fedid(cert=self.request.get_peer_cert()) fname, type = self.server.get_handler(self.path, fid) if fname: try: f = open(fname, "rb") size = os.path.getsize(fname) except EnvironmentError, e: code = 404 size = 0 else: code = 404 size = 0 else: code = 404 size = 0 self.send_response(code) if code == 200: if type: self.send_header('Content-type', type) self.send_header('Content-Length', size) self.end_headers() bytes = f.read(4096) while len(bytes): self.wfile.write(bytes) bytes = f.read(4096) self.wfile.flush() self.request.socket.close() def log_request(self, code=0, size=0): """ Log request to the fedd logger """ self.server.log.info("Successful SOAP request code %d" % code) def soap_dispatch(self, method, req, fid): """ The connection to the implementation, using the method maps The implementation provides a mapping from SOAP method name to the method in the implementation that provides the service. """ if self.server.soap_methods.has_key(method): try: return self.server.soap_methods[method](req, fid) except service_error, e: de = ns0.faultType_Def( (ns0.faultType_Def.schema, "FeddFaultBody")).pyclass() de._code=e.code de._errstr=e.code_string() de._desc=e.desc for p in e.proof: dp = ns0.proofType_Def(ns0.proofType_Def.schema, "proof").pyclass() dp._prover = p.prover dp._principal = p.principal dp._attribute = p.attribute dp._credential = p.creds_to_certs() if de._proof: de._proof.append(dp) else: de._proof = [dp] if e.is_server_error(): raise Fault(Fault.Server, e.code_string(), detail=de) else: raise Fault(Fault.Client, e.code_string(), detail=de) else: raise Fault(Fault.Client, "Unknown method: %s" % method) class xmlrpc_handler(BaseHTTPRequestHandler): """ Standard connection between XMLRPC and the fedd services in impl. Much of this is boilerplate from http://www.xml.com/pub/a/ws/2004/01/20/salz.html """ 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() # Make sure to close the socket when we're done self.request.socket.close() def do_POST(self): """Treat an HTTP POST request as an XMLRPC service call""" # NB: XMLRPC faults are not HTTP errors, so the code is always 200, # unless an HTTP error occurs, which we don't handle. 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.xmlrpc_dispatch(method, params, fedid(cert=self.request.get_peer_cert())) data = xmlrpclib.dumps((resp,), encoding='UTF-8', methodresponse=True) except xmlrpclib.Fault, f: data = xmlrpclib.dumps(f, methodresponse=True) resp = None self.send_xml(data) def log_request(self, code=0, size=0): """ Log request to the fedd logger """ self.server.log.info("Successful XMLRPC request code %d" % code) def xmlrpc_dispatch(self, method, req, fid): """ The connection to the implementation, using the method maps The implementation provides a mapping from XMLRPC method name to the method in the implementation that provides the service. """ if self.server.xmlrpc_methods.has_key(method): try: return self.server.xmlrpc_methods[method](req, fid) except service_error, e: raise xmlrpclib.Fault(e.code_string(), e.desc) else: raise xmlrpclib.Fault(100, "Unknown method: %s" % method)