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
23 from sfa.trust.certificate import Keypair, Certificate
24 from sfa.trust.credential import *
25 from sfa.util.faults import *
26 from sfa.plc.api import SfaAPI
27 from sfa.util.cache import Cache
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
40 # the certificate verification done by openssl checks a number of things
41 # that we aren't interested in, so we look out for those error messages
44 # XXX SMBAKER: I don't know what this error is, but it's being returned
47 #print " X509_V_ERR_CERT_NOT_YET_VALID"
50 # allow self-signed certificates
52 #print " X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT"
55 # allow certs that don't have an issuer
57 #print " X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY"
60 # allow chained certs with self-signed roots
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
97 peer_cert = Certificate()
98 peer_cert.load_from_pyopenssl_x509(self.connection.get_peer_certificate())
99 self.api = SfaAPI(peer_cert = peer_cert,
100 interface = self.server.interface,
101 key_file = self.server.key_file,
102 cert_file = self.server.cert_file,
105 request = self.rfile.read(int(self.headers["content-length"]))
106 remote_addr = (remote_ip, remote_port) = self.connection.getpeername()
107 self.api.remote_addr = remote_addr
108 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 sfa_error.log_exc("server.do_POST")
113 response = self.api.prepare_response(fault)
114 #self.send_response(500)
117 # got a valid response
118 self.send_response(200)
119 self.send_header("Content-type", "text/xml")
120 self.send_header("Content-length", str(len(response)))
122 self.wfile.write(response)
124 # shut down the connection
126 self.connection.shutdown() # Modified here!
129 # Taken from the web (XXX find reference). Implements an HTTPS xmlrpc server
130 class SecureXMLRPCServer(BaseHTTPServer.HTTPServer,SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
131 def __init__(self, server_address, HandlerClass, key_file, cert_file, logRequests=True):
132 """Secure XML-RPC server.
134 It it very similar to SimpleXMLRPCServer but it uses HTTPS for transporting XML data.
136 self.logRequests = logRequests
137 self.interface = None
138 self.key_file = key_file
139 self.cert_file = cert_file
141 # add cache to the request handler
142 HandlerClass.cache = Cache()
143 #for compatibility with python 2.4 (centos53)
144 if sys.version_info < (2, 5):
145 SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self)
147 SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self, True, None)
148 SocketServer.BaseServer.__init__(self, server_address, HandlerClass)
149 ctx = SSL.Context(SSL.SSLv23_METHOD)
150 ctx.use_privatekey_file(key_file)
151 ctx.use_certificate_file(cert_file)
152 # If you wanted to verify certs against known CAs.. this is how you would do it
153 #ctx.load_verify_locations('/etc/sfa/trusted_roots/plc.gpo.gid')
154 ctx.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT, verify_callback)
155 ctx.set_verify_depth(5)
156 ctx.set_app_data(self)
157 self.socket = SSL.Connection(ctx, socket.socket(self.address_family,
160 self.server_activate()
164 # Convert an exception on the server to a full stack trace and send it to
167 def _dispatch(self, method, params):
169 return SimpleXMLRPCServer.SimpleXMLRPCDispatcher._dispatch(self, method, params)
171 # can't use format_exc() as it is not available in jython yet
173 type, value, tb = sys.exc_info()
174 raise xmlrpclib.Fault(1,''.join(traceback.format_exception(type, value, tb)))
176 ## From Active State code: http://code.activestate.com/recipes/574454/
177 # This is intended as a drop-in replacement for the ThreadingMixIn class in
178 # module SocketServer of the standard lib. Instead of spawning a new thread
179 # for each request, requests are processed by of pool of reusable threads.
180 class ThreadPoolMixIn(SocketServer.ThreadingMixIn):
182 use a thread pool instead of a new thread on every request
184 # XX TODO: Make this configurable
186 # numThreads = config.SFA_SERVER_NUM_THREADS
188 allow_reuse_address = True # seems to fix socket.error on server restart
190 def serve_forever(self):
192 Handle one request at a time until doomsday.
194 # set up the threadpool
195 self.requests = Queue()
197 for x in range(self.numThreads):
198 t = threading.Thread(target = self.process_request_thread)
204 self.handle_request()
209 def process_request_thread(self):
211 obtain request from queue instead of directly from server socket
214 SocketServer.ThreadingMixIn.process_request_thread(self, *self.requests.get())
217 def handle_request(self):
219 simply collect requests and put them on the queue for the workers.
222 request, client_address = self.get_request()
225 if self.verify_request(request, client_address):
226 self.requests.put((request, client_address))
228 class ThreadedServer(ThreadPoolMixIn, SecureXMLRPCServer):
231 # Implements an HTTPS XML-RPC server. Generally it is expected that SFA
232 # functions will take a credential string, which is passed to
233 # decode_authentication. Decode_authentication() will verify the validity of
234 # the credential, and verify that the user is using the key that matches the
235 # GID supplied in the credential.
237 class SfaServer(threading.Thread):
240 # Create a new SfaServer object.
242 # @param ip the ip address to listen on
243 # @param port the port to listen on
244 # @param key_file private key filename of registry
245 # @param cert_file certificate filename containing public key
246 # (could be a GID file)
248 def __init__(self, ip, port, key_file, cert_file):
249 threading.Thread.__init__(self)
250 self.key = Keypair(filename = key_file)
251 self.cert = Certificate(filename = cert_file)
252 #self.server = SecureXMLRPCServer((ip, port), SecureXMLRpcRequestHandler, key_file, cert_file)
253 self.server = ThreadedServer((ip, port), SecureXMLRpcRequestHandler, key_file, cert_file)
254 self.trusted_cert_list = None
255 self.register_functions()
259 # Register functions that will be served by the XMLRPC server. This
260 # function should be overridden by each descendant class.
262 def register_functions(self):
263 self.server.register_function(self.noop)
266 # Sample no-op server function. The no-op function decodes the credential
267 # that was passed to it.
269 def noop(self, cred, anything):
270 self.decode_authentication(cred)
274 # Execute the server, serving requests forever.
277 self.server.serve_forever()