Index: fedd/fedd/server.py
===================================================================
--- fedd/fedd/server.py	(revision ec4fb429a3fab87363f4ff5ebc4b52e33b6c841b)
+++ fedd/fedd/server.py	(revision ec4fb429a3fab87363f4ff5ebc4b52e33b6c841b)
@@ -0,0 +1,223 @@
+#!/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 fedid import fedid
+from fedd_services import ns0
+from service_error import *
+
+import logging
+
+# 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):
+	"""
+	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.log = logging.getLogger("fedd")
+
+    def handle_error(self, request, address):
+	"""
+	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, address))
+	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 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
+		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)
