Index: cred_printer/cred_printer/__init__.py
===================================================================
--- cred_printer/cred_printer/__init__.py	(revision 02dcba536bb4ec0a836dcc142d06a551653ded6c)
+++ cred_printer/cred_printer/__init__.py	(revision 02dcba536bb4ec0a836dcc142d06a551653ded6c)
@@ -0,0 +1,1 @@
+#!/usr/local/bin/python
Index: cred_printer/cred_printer/server.py
===================================================================
--- cred_printer/cred_printer/server.py	(revision 02dcba536bb4ec0a836dcc142d06a551653ded6c)
+++ cred_printer/cred_printer/server.py	(revision 02dcba536bb4ec0a836dcc142d06a551653ded6c)
@@ -0,0 +1,169 @@
+#!/usr/local/bin/python
+
+import sys
+
+from BaseHTTPServer import BaseHTTPRequestHandler
+
+from M2Crypto import SSL
+from M2Crypto.SSL.SSLServer import SSLServer
+from SocketServer import TCPServer
+
+from service_error import service_error
+import xmlrpclib
+
+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 error_handlers:
+    """
+    Class to encapsulate the debugging and non-debugging error handlers.
+    """
+    def __init__(self, debug):
+	"""
+	Set the error handler
+	"""
+	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):
+	"""
+	Debugging error handler.  Prints a stack trace.
+	"""
+	print '-'*40
+	traceback.print_exc()
+	print '-'*40
+
+
+    def handle_error_standard(self, request=None, address=None):
+	"""
+	Print standard error output, suitable for human consumption.
+	"""
+	if request or address:
+	    self.log.warn("[credd] Error on incoming connection: %s %s" % \
+		    (request or "", address or ""))
+	else:
+	    self.log.warn("[credd] Error on incoming connection " + \
+		    "(Likely SSL error)")
+
+
+
+class server(SSLServer, error_handlers):
+    """
+    Overloads a TCPServer to hold an XMLRPC implementation.  The implementation
+    needs to inclide an xmlrpc_services dist that maps from method name to
+    callable mamber that takes a single parameter (usually a list or dict) and
+    a certificate.
+    """
+    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.
+	"""
+	SSLServer.__init__(self, ME, handler, ssl_ctx)
+	error_handlers.__init__(self, debug)
+	self.impl = impl
+	self.xmlrpc_methods = impl.xmlrpc_services
+	self.log = logging.getLogger("creds")
+
+class simpleServer(TCPServer, error_handlers):
+    """
+    Presents the same interface as server to a threaded TCP server.  Allows a
+    binding of the same implementation and handler classes to
+    unencrypted/authenticated connections.
+    """
+    def __init__(self, ME, handler, impl, debug=False):
+	TCPServer.__init__(self, ME, handler)
+	error_handlers.__init__(self, debug)
+	self.impl = impl
+	self.xmlrpc_methods = impl.xmlrpc_services
+	self.log = logging.getLogger("creds")
+
+
+class xmlrpc_handler(BaseHTTPRequestHandler):
+    """
+    Standard connection between XMLRPC and the services in impl.
+
+    Much of this is boilerplate from 
+    http://www.xml.com/pub/a/ws/2004/01/20/salz.html
+    """
+    server_version = "credd/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.close()
+	#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)
+
+	# Simple servers don't have peer certificates.
+	if getattr(self.request, 'get_peer_cert', None):
+	    fid = self.request.get_peer_cert()
+	else:
+	    fid = None
+
+
+	if method != None:
+	    try:
+		resp = self.xmlrpc_dispatch(method, params, fid)
+		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)
+
Index: cred_printer/cred_printer/service_error.py
===================================================================
--- cred_printer/cred_printer/service_error.py	(revision 02dcba536bb4ec0a836dcc142d06a551653ded6c)
+++ cred_printer/cred_printer/service_error.py	(revision 02dcba536bb4ec0a836dcc142d06a551653ded6c)
@@ -0,0 +1,53 @@
+#!/usr/local/bin/python
+
+# This is used to make the service error reporting independent of the
+# transport.  The XMLRPC and SOAP dispatchers will convert it into
+# transport-specific errors.
+# Cribbed directly from fedd, because it's so convenient.  Some of the error
+# codes are inapplicable to the credential interpreter.
+class service_error(RuntimeError):
+    access = 1
+    protocol= 2
+    req = 3
+    server_config = 4
+    internal = 5
+    partial = 6
+    federant = 7
+    connect = 8
+    code_str = { 
+	access : "Access Denied",
+	protocol : "Protocol Error",
+	req : "Badly Formed Request",
+	server_config: "Server Configuration Error",
+	internal : "Internal Error",
+	partial: "Partial Embedding",
+	federant: "Federant Error",
+	connect: "Connection Error",
+    }
+    str_code = dict([ (v, k) for k, v in code_str.iteritems() ])
+    client_errors = ( req, partial)
+    server_errors = ( access, protocol, server_config, internal,
+	    federant, connect)
+
+    def __init__(self, code=None, desc=None, from_string=None, proof=None):
+	self.code = code
+	self.desc = desc
+	self.proof = proof or []
+	if not isinstance (self.proof, list): self.proof = [ proof ]
+	if code == None:
+	    self.set_code_from_string(from_string)
+	RuntimeError.__init__(self, desc)
+
+    def code_string(self, code=None):
+	code = code or self.code
+	return service_error.code_str.get(code)
+    
+    def set_code_from_string(self, errstr):
+	self.code = service_error.str_code.get(errstr, service_error.internal)
+	return self.code
+    
+    def is_client_error(self):
+	return self.code in service_error.client_errors
+
+    def is_server_error(self):
+	return self.code in service_error.server_errors
Index: cred_printer/cred_printer/util.py
===================================================================
--- cred_printer/cred_printer/util.py	(revision 02dcba536bb4ec0a836dcc142d06a551653ded6c)
+++ cred_printer/cred_printer/util.py	(revision 02dcba536bb4ec0a836dcc142d06a551653ded6c)
@@ -0,0 +1,131 @@
+#!/usr/local/bin/python
+
+import sys
+from M2Crypto import SSL
+
+# Turn off the matching of hostname to certificate ID
+SSL.Connection.clientPostConnectionCheck = None
+
+class ssl_context(SSL.Context):
+    """
+    Simple wrapper around an M2Crypto.SSL.Context to initialize it for use with
+    self-signed certs.
+    """
+    def __init__(self, my_cert, trusted_certs=None, password=None):
+	"""
+	construct a cred_ssl_context
+
+	@param my_cert: PEM file with my certificate in it
+	@param trusted_certs: PEM file with trusted certs in it (optional)
+	"""
+	SSL.Context.__init__(self)
+
+	# load_cert takes a callback to get a password, not a password, so if
+	# the caller provided a password, this creates a nonce callback using a
+	# lambda form.
+	if password != None and not callable(password):
+	    # This is cute.  password = lambda *args: password produces a
+	    # function object that returns itself rather than one that returns
+	    # the object itself.  This is because password is an object
+	    # reference and after the assignment it's a lambda.  So we assign
+	    # to a temp.
+	    pwd = str(password)
+	    password =lambda *args: pwd
+
+	# The calls to str below (and above) are because the underlying SSL
+	# stuff is intolerant of unicode.
+	if password != None:
+	    self.load_cert(str(my_cert), callback=password)
+	else:
+	    self.load_cert(str(my_cert))
+
+	# If no trusted certificates are specified, allow unknown CAs.
+	if trusted_certs: 
+	    self.load_verify_locations(trusted_certs)
+	    self.set_verify(SSL.verify_peer, 10)
+	else:
+	    callb = getattr(SSL.cb, "ssl_verify_callback")
+	    self.set_allow_unknown_ca(True)
+	    self.set_verify(SSL.verify_peer, 10, 
+		    callback=SSL.cb.ssl_verify_callback_allow_unknown_ca)
+
+# Python 2.7 broke the MCrypto 2.1 SSL_Transport.  If this is an older python,
+# use SSL_Transport unchanged, otherwise use a tweaked version.  Export them as
+# SSLTransport.
+if sys.version_info == 2 and sys.version_info < 7:
+    from M2Crypto.m2xmlrpclib import SSL_Transport
+    class SSLTransport(SSL_Transport): pass
+else:
+    # Most of this is directly from M2Crypto
+    import base64, string, sys
+
+    from xmlrpclib import *
+    import M2Crypto
+    import M2Crypto.SSL, M2Crypto.httpslib, M2Crypto.m2urllib
+
+    __version__ = M2Crypto.version
+
+    class SSLTransport(Transport):
+
+	user_agent = "M2Crypto_XMLRPC/%s - %s" % (__version__, 
+		Transport.user_agent)
+
+	def __init__(self, ssl_context=None, *args, **kw):
+	    if getattr(Transport, '__init__', None) is not None:
+		Transport.__init__(self, *args, **kw)
+	    if ssl_context is None:
+		self.ssl_ctx=M2Crypto.SSL.Context('sslv23')
+	    else:
+		self.ssl_ctx=ssl_context
+
+	def request(self, host, handler, request_body, verbose=0):
+	    # Handle username and password.
+	    user_passwd, host_port = M2Crypto.m2urllib.splituser(host)
+	    _host, _port = M2Crypto.m2urllib.splitport(host_port)
+
+	    # This is a difference, was an (obsolete) HTTPS object, but
+	    # HTTPSConnection is more supported and clear --tvf
+	    h = M2Crypto.httpslib.HTTPSConnection(_host, int(_port), 
+		    ssl_context=self.ssl_ctx)
+	    if verbose:
+		h.set_debuglevel(1)
+
+	    # What follows is as in xmlrpclib.Transport. (Except the authz bit.)
+	    h.putrequest("POST", handler)
+
+	    # required by HTTP/1.1
+	    h.putheader("Host", _host)
+
+	    # required by XML-RPC
+	    h.putheader("User-Agent", self.user_agent)
+	    h.putheader("Content-Type", "text/xml")
+	    h.putheader("Content-Length", str(len(request_body)))
+
+	    # Authorisation.
+	    if user_passwd is not None:
+		auth=string.strip(base64.encodestring(user_passwd))
+		h.putheader('Authorization', 'Basic %s' % auth)
+
+	    h.endheaders()
+
+	    if request_body:
+		h.send(request_body)
+
+	    # These are the modifications -- tvf
+
+	    resp = h.getresponse()
+	    errcode = resp.status
+	    errmsg = resp.reason
+
+	    # end mods -- tvf
+
+	    if errcode != 200:
+		raise ProtocolError(
+		    host + handler,
+		    errcode, errmsg,
+		    headers
+		    )
+
+	    self.verbose = verbose
+	    return self.parse_response(resp)
+
