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 # create a Certificate object and load it from the client's x509
45 ctx = conn.get_context()
46 server = ctx.get_app_data()
47 server.peer_cert = Certificate()
48 server.peer_cert.load_from_pyopenssl_x509(x509)
50 # the certificate verification done by openssl checks a number of things
51 # that we aren't interested in, so we look out for those error messages
54 # XXX SMBAKER: I don't know what this error is, but it's being returned
57 #print " X509_V_ERR_CERT_NOT_YET_VALID"
60 # allow self-signed certificates
62 #print " X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT"
65 # allow certs that don't have an issuer
67 #print " X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY"
70 # allow certs that are untrusted
72 #print " X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE"
75 # allow certs that are untrusted
77 #print " X509_V_ERR_CERT_UNTRUSTED"
80 print " error", err, "in verify_callback"
85 # taken from the web (XXX find reference). Implents HTTPS xmlrpc request handler
86 class SecureXMLRpcRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
87 """Secure XML-RPC request handler class.
89 It it very similar to SimpleXMLRPCRequestHandler but it uses HTTPS for transporting XML data.
92 self.connection = self.request
93 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
94 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
97 """Handles the HTTPS POST request.
99 It was copied out from SimpleXMLRPCServer.py and modified to shutdown the socket cleanly.
102 self.api = SfaAPI(peer_cert = self.server.peer_cert,
103 interface = self.server.interface,
104 key_file = self.server.key_file,
105 cert_file = self.server.cert_file)
107 request = self.rfile.read(int(self.headers["content-length"]))
108 # In previous versions of SimpleXMLRPCServer, _dispatch
109 # could be overridden in this class, instead of in
110 # SimpleXMLRPCDispatcher. To maintain backwards compatibility,
111 # check to see if a subclass implements _dispatch and dispatch
112 # using that method if present.
113 #response = self.server._marshaled_dispatch(request, getattr(self, '_dispatch', None))
114 remote_addr = (remote_ip, remote_port) = self.connection.getpeername()
115 self.api.remote_addr = remote_addr
116 response = self.api.handle(remote_addr, request)
119 except Exception, fault:
121 # This should only happen if the module is buggy
122 # internal error, report as HTTP server error
123 self.send_response(500)
126 # got a valid XML RPC response
127 self.send_response(200)
128 self.send_header("Content-type", "text/xml")
129 self.send_header("Content-length", str(len(response)))
131 self.wfile.write(response)
133 # shut down the connection
135 self.connection.shutdown() # Modified here!
138 # Taken from the web (XXX find reference). Implements an HTTPS xmlrpc server
139 class SecureXMLRPCServer(BaseHTTPServer.HTTPServer,SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
140 def __init__(self, server_address, HandlerClass, key_file, cert_file, logRequests=True):
141 """Secure XML-RPC server.
143 It it very similar to SimpleXMLRPCServer but it uses HTTPS for transporting XML data.
145 self.logRequests = logRequests
146 self.interface = None
147 self.key_file = key_file
148 self.cert_file = cert_file
149 #for compatibility with python 2.4 (centos53)
150 if sys.version_info < (2, 5):
151 SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self)
153 SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self, True, None)
154 SocketServer.BaseServer.__init__(self, server_address, HandlerClass)
155 ctx = SSL.Context(SSL.SSLv23_METHOD)
156 ctx.use_privatekey_file(key_file)
157 ctx.use_certificate_file(cert_file)
158 ctx.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT, verify_callback)
159 ctx.set_app_data(self)
160 self.socket = SSL.Connection(ctx, socket.socket(self.address_family,
163 self.server_activate()
167 # Convert an exception on the server to a full stack trace and send it to
170 def _dispatch(self, method, params):
172 return SimpleXMLRPCServer.SimpleXMLRPCDispatcher._dispatch(self, method, params)
174 # can't use format_exc() as it is not available in jython yet
176 type, value, tb = sys.exc_info()
177 raise xmlrpclib.Fault(1,''.join(traceback.format_exception(type, value, tb)))
179 ## From Active State code: http://code.activestate.com/recipes/574454/
180 # This is intended as a drop-in replacement for the ThreadingMixIn class in
181 # module SocketServer of the standard lib. Instead of spawning a new thread
182 # for each request, requests are processed by of pool of reusable threads.
183 class ThreadPoolMixIn(SocketServer.ThreadingMixIn):
185 use a thread pool instead of a new thread on every request
187 # XX TODO: Make this configurable
189 # numThreads = config.SFA_SERVER_NUM_THREADS
191 allow_reuse_address = True # seems to fix socket.error on server restart
193 def serve_forever(self):
195 Handle one request at a time until doomsday.
197 # set up the threadpool
198 self.requests = Queue(self.numThreads)
200 for x in range(self.numThreads):
201 t = threading.Thread(target = self.process_request_thread)
207 self.handle_request()
212 def process_request_thread(self):
214 obtain request from queue instead of directly from server socket
217 SocketServer.ThreadingMixIn.process_request_thread(self, *self.requests.get())
220 def handle_request(self):
222 simply collect requests and put them on the queue for the workers.
225 request, client_address = self.get_request()
228 if self.verify_request(request, client_address):
229 self.requests.put((request, client_address))
231 class ThreadedServer(ThreadPoolMixIn, SecureXMLRPCServer):
234 # Implements an HTTPS XML-RPC server. Generally it is expected that SFA
235 # functions will take a credential string, which is passed to
236 # decode_authentication. Decode_authentication() will verify the validity of
237 # the credential, and verify that the user is using the key that matches the
238 # GID supplied in the credential.
240 class SfaServer(threading.Thread):
243 # Create a new SfaServer object.
245 # @param ip the ip address to listen on
246 # @param port the port to listen on
247 # @param key_file private key filename of registry
248 # @param cert_file certificate filename containing public key
249 # (could be a GID file)
251 def __init__(self, ip, port, key_file, cert_file):
252 threading.Thread.__init__(self)
253 self.key = Keypair(filename = key_file)
254 self.cert = Certificate(filename = cert_file)
255 #self.server = SecureXMLRPCServer((ip, port), SecureXMLRpcRequestHandler, key_file, cert_file)
256 self.server = ThreadedServer((ip, port), SecureXMLRpcRequestHandler, key_file, cert_file)
257 self.trusted_cert_list = None
258 self.register_functions()
262 # Register functions that will be served by the XMLRPC server. This
263 # function should be overrided by each descendant class.
265 def register_functions(self):
266 self.server.register_function(self.noop)
269 # Sample no-op server function. The no-op function decodes the credential
270 # that was passed to it.
272 def noop(self, cred, anything):
273 self.decode_authentication(cred)
278 # Execute the server, serving requests forever.
281 self.server.serve_forever()