2 # This module implements a general-purpose server layer for sfa.
3 # The same basic server should be usable on the registry, component, or
6 # TODO: investigate ways to combine this with existing PLC server?
18 import SimpleHTTPServer
19 import SimpleXMLRPCServer
20 from OpenSSL import SSL
21 from Queue import Queue
22 from sfa.trust.certificate import Keypair, Certificate
23 from sfa.trust.credential import *
24 from sfa.util.faults import *
25 from sfa.plc.api import SfaAPI
26 from sfa.util.debug import log
29 # Verification callback for pyOpenSSL. We do our own checking of keys because
30 # we have our own authentication spec. Thus we disable several of the normal
31 # prohibitions that OpenSSL places on certificates
33 def verify_callback(conn, x509, err, depth, preverify):
34 # if the cert has been preverified, then it is ok
39 # we're only passing single certificates, not chains
41 #print " depth > 0 in verify_callback"
44 # the certificate verification done by openssl checks a number of things
45 # that we aren't interested in, so we look out for those error messages
48 # XXX SMBAKER: I don't know what this error is, but it's being returned
51 #print " X509_V_ERR_CERT_NOT_YET_VALID"
54 # allow self-signed certificates
56 #print " X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT"
59 # allow certs that don't have an issuer
61 #print " X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY"
64 # allow certs that are untrusted
66 #print " X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE"
69 # allow certs that are untrusted
71 #print " X509_V_ERR_CERT_UNTRUSTED"
74 print " error", err, "in verify_callback"
79 # taken from the web (XXX find reference). Implents HTTPS xmlrpc request handler
80 class SecureXMLRpcRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
81 """Secure XML-RPC request handler class.
83 It it very similar to SimpleXMLRPCRequestHandler but it uses HTTPS for transporting XML data.
86 self.connection = self.request
87 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
88 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
91 """Handles the HTTPS POST request.
93 It was copied out from SimpleXMLRPCServer.py and modified to shutdown the socket cleanly.
96 peer_cert = Certificate()
97 peer_cert.load_from_pyopenssl_x509(self.connection.get_peer_certificate())
98 self.api = SfaAPI(peer_cert = peer_cert,
99 interface = self.server.interface,
100 key_file = self.server.key_file,
101 cert_file = self.server.cert_file)
103 request = self.rfile.read(int(self.headers["content-length"]))
104 remote_addr = (remote_ip, remote_port) = self.connection.getpeername()
105 self.api.remote_addr = remote_addr
106 response = self.api.handle(remote_addr, request, self.server.method_map)
109 except Exception, fault:
110 # This should only happen if the module is buggy
111 # internal error, report as HTTP server error
112 self.send_response(500)
114 traceback.print_exc()
116 # got a valid XML RPC response
117 self.send_response(200)
118 self.send_header("Content-type", "text/xml")
119 self.send_header("Content-length", str(len(response)))
121 self.wfile.write(response)
123 # shut down the connection
125 self.connection.shutdown() # Modified here!
128 # Taken from the web (XXX find reference). Implements an HTTPS xmlrpc server
129 class SecureXMLRPCServer(BaseHTTPServer.HTTPServer,SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
130 def __init__(self, server_address, HandlerClass, key_file, cert_file, logRequests=True):
131 """Secure XML-RPC server.
133 It it very similar to SimpleXMLRPCServer but it uses HTTPS for transporting XML data.
135 self.logRequests = logRequests
136 self.interface = None
137 self.key_file = key_file
138 self.cert_file = cert_file
141 #for compatibility with python 2.4 (centos53)
142 if sys.version_info < (2, 5):
143 SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self)
145 SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self, True, None)
146 SocketServer.BaseServer.__init__(self, server_address, HandlerClass)
147 ctx = SSL.Context(SSL.SSLv23_METHOD)
148 ctx.use_privatekey_file(key_file)
149 ctx.use_certificate_file(cert_file)
150 ctx.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT, verify_callback)
151 ctx.set_app_data(self)
152 self.socket = SSL.Connection(ctx, socket.socket(self.address_family,
155 self.server_activate()
159 # Convert an exception on the server to a full stack trace and send it to
162 def _dispatch(self, method, params):
164 return SimpleXMLRPCServer.SimpleXMLRPCDispatcher._dispatch(self, method, params)
166 # can't use format_exc() as it is not available in jython yet
168 type, value, tb = sys.exc_info()
169 raise xmlrpclib.Fault(1,''.join(traceback.format_exception(type, value, tb)))
171 ## From Active State code: http://code.activestate.com/recipes/574454/
172 # This is intended as a drop-in replacement for the ThreadingMixIn class in
173 # module SocketServer of the standard lib. Instead of spawning a new thread
174 # for each request, requests are processed by of pool of reusable threads.
175 class ThreadPoolMixIn(SocketServer.ThreadingMixIn):
177 use a thread pool instead of a new thread on every request
179 # XX TODO: Make this configurable
181 # numThreads = config.SFA_SERVER_NUM_THREADS
183 allow_reuse_address = True # seems to fix socket.error on server restart
185 def serve_forever(self):
187 Handle one request at a time until doomsday.
189 # set up the threadpool
190 self.requests = Queue()
192 for x in range(self.numThreads):
193 t = threading.Thread(target = self.process_request_thread)
199 self.handle_request()
204 def process_request_thread(self):
206 obtain request from queue instead of directly from server socket
209 SocketServer.ThreadingMixIn.process_request_thread(self, *self.requests.get())
212 def handle_request(self):
214 simply collect requests and put them on the queue for the workers.
217 request, client_address = self.get_request()
220 if self.verify_request(request, client_address):
221 self.requests.put((request, client_address))
223 class ThreadedServer(ThreadPoolMixIn, SecureXMLRPCServer):
226 # Implements an HTTPS XML-RPC server. Generally it is expected that SFA
227 # functions will take a credential string, which is passed to
228 # decode_authentication. Decode_authentication() will verify the validity of
229 # the credential, and verify that the user is using the key that matches the
230 # GID supplied in the credential.
232 class SfaServer(threading.Thread):
235 # Create a new SfaServer object.
237 # @param ip the ip address to listen on
238 # @param port the port to listen on
239 # @param key_file private key filename of registry
240 # @param cert_file certificate filename containing public key
241 # (could be a GID file)
243 def __init__(self, ip, port, key_file, cert_file):
244 threading.Thread.__init__(self)
245 self.key = Keypair(filename = key_file)
246 self.cert = Certificate(filename = cert_file)
247 #self.server = SecureXMLRPCServer((ip, port), SecureXMLRpcRequestHandler, key_file, cert_file)
248 self.server = ThreadedServer((ip, port), SecureXMLRpcRequestHandler, key_file, cert_file)
249 self.trusted_cert_list = None
250 self.register_functions()
254 # Register functions that will be served by the XMLRPC server. This
255 # function should be overridden by each descendant class.
257 def register_functions(self):
258 self.server.register_function(self.noop)
261 # Sample no-op server function. The no-op function decodes the credential
262 # that was passed to it.
264 def noop(self, cred, anything):
265 self.decode_authentication(cred)
270 # Execute the server, serving requests forever.
273 self.server.serve_forever()