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.cache import Cache
27 from sfa.util.debug import log
30 # Verification callback for pyOpenSSL. We do our own checking of keys because
31 # we have our own authentication spec. Thus we disable several of the normal
32 # prohibitions that OpenSSL places on certificates
34 def verify_callback(conn, x509, err, depth, preverify):
35 # if the cert has been preverified, then it is ok
40 # we're only passing single certificates, not chains
42 #print " depth > 0 in verify_callback"
45 # the certificate verification done by openssl checks a number of things
46 # that we aren't interested in, so we look out for those error messages
49 # XXX SMBAKER: I don't know what this error is, but it's being returned
52 #print " X509_V_ERR_CERT_NOT_YET_VALID"
55 # allow self-signed certificates
57 #print " X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT"
60 # allow certs that don't have an issuer
62 #print " X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY"
65 # allow certs that are untrusted
67 #print " X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE"
70 # allow certs that are untrusted
72 #print " X509_V_ERR_CERT_UNTRUSTED"
75 print " error", err, "in verify_callback"
80 # taken from the web (XXX find reference). Implents HTTPS xmlrpc request handler
81 class SecureXMLRpcRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
82 """Secure XML-RPC request handler class.
84 It it very similar to SimpleXMLRPCRequestHandler but it uses HTTPS for transporting XML data.
87 self.connection = self.request
88 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
89 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
92 """Handles the HTTPS POST request.
94 It was copied out from SimpleXMLRPCServer.py and modified to shutdown
98 peer_cert = Certificate()
99 peer_cert.load_from_pyopenssl_x509(self.connection.get_peer_certificate())
100 self.api = SfaAPI(peer_cert = peer_cert,
101 interface = self.server.interface,
102 key_file = self.server.key_file,
103 cert_file = self.server.cert_file,
106 request = self.rfile.read(int(self.headers["content-length"]))
107 remote_addr = (remote_ip, remote_port) = self.connection.getpeername()
108 self.api.remote_addr = remote_addr
109 response = self.api.handle(remote_addr, request)
112 except Exception, fault:
113 # This should only happen if the module is buggy
114 # internal error, report as HTTP server error
115 self.send_response(500)
117 traceback.print_exc()
119 # got a valid XML RPC response
120 self.send_response(200)
121 self.send_header("Content-type", "text/xml")
122 self.send_header("Content-length", str(len(response)))
124 self.wfile.write(response)
126 # shut down the connection
128 self.connection.shutdown() # Modified here!
131 # Taken from the web (XXX find reference). Implements an HTTPS xmlrpc server
132 class SecureXMLRPCServer(BaseHTTPServer.HTTPServer,SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
133 def __init__(self, server_address, HandlerClass, key_file, cert_file, logRequests=True):
134 """Secure XML-RPC server.
136 It it very similar to SimpleXMLRPCServer but it uses HTTPS for transporting XML data.
138 self.logRequests = logRequests
139 self.interface = None
140 self.key_file = key_file
141 self.cert_file = cert_file
142 # add cache to the request handler
143 HandlerClass.cache = Cache()
144 #for compatibility with python 2.4 (centos53)
145 if sys.version_info < (2, 5):
146 SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self)
148 SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self, True, None)
149 SocketServer.BaseServer.__init__(self, server_address, HandlerClass)
150 ctx = SSL.Context(SSL.SSLv23_METHOD)
151 ctx.use_privatekey_file(key_file)
152 ctx.use_certificate_file(cert_file)
153 ctx.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT, verify_callback)
154 ctx.set_app_data(self)
155 self.socket = SSL.Connection(ctx, socket.socket(self.address_family,
158 self.server_activate()
162 # Convert an exception on the server to a full stack trace and send it to
165 def _dispatch(self, method, params):
167 return SimpleXMLRPCServer.SimpleXMLRPCDispatcher._dispatch(self, method, params)
169 # can't use format_exc() as it is not available in jython yet
171 type, value, tb = sys.exc_info()
172 raise xmlrpclib.Fault(1,''.join(traceback.format_exception(type, value, tb)))
174 ## From Active State code: http://code.activestate.com/recipes/574454/
175 # This is intended as a drop-in replacement for the ThreadingMixIn class in
176 # module SocketServer of the standard lib. Instead of spawning a new thread
177 # for each request, requests are processed by of pool of reusable threads.
178 class ThreadPoolMixIn(SocketServer.ThreadingMixIn):
180 use a thread pool instead of a new thread on every request
182 # XX TODO: Make this configurable
184 # numThreads = config.SFA_SERVER_NUM_THREADS
186 allow_reuse_address = True # seems to fix socket.error on server restart
188 def serve_forever(self):
190 Handle one request at a time until doomsday.
192 # set up the threadpool
193 self.requests = Queue()
195 for x in range(self.numThreads):
196 t = threading.Thread(target = self.process_request_thread)
202 self.handle_request()
207 def process_request_thread(self):
209 obtain request from queue instead of directly from server socket
212 SocketServer.ThreadingMixIn.process_request_thread(self, *self.requests.get())
215 def handle_request(self):
217 simply collect requests and put them on the queue for the workers.
220 request, client_address = self.get_request()
223 if self.verify_request(request, client_address):
224 self.requests.put((request, client_address))
226 class ThreadedServer(ThreadPoolMixIn, SecureXMLRPCServer):
229 # Implements an HTTPS XML-RPC server. Generally it is expected that SFA
230 # functions will take a credential string, which is passed to
231 # decode_authentication. Decode_authentication() will verify the validity of
232 # the credential, and verify that the user is using the key that matches the
233 # GID supplied in the credential.
235 class SfaServer(threading.Thread):
238 # Create a new SfaServer object.
240 # @param ip the ip address to listen on
241 # @param port the port to listen on
242 # @param key_file private key filename of registry
243 # @param cert_file certificate filename containing public key
244 # (could be a GID file)
246 def __init__(self, ip, port, key_file, cert_file):
247 threading.Thread.__init__(self)
248 self.key = Keypair(filename = key_file)
249 self.cert = Certificate(filename = cert_file)
250 #self.server = SecureXMLRPCServer((ip, port), SecureXMLRpcRequestHandler, key_file, cert_file)
251 self.server = ThreadedServer((ip, port), SecureXMLRpcRequestHandler, key_file, cert_file)
252 self.trusted_cert_list = None
253 self.register_functions()
257 # Register functions that will be served by the XMLRPC server. This
258 # function should be overrided by each descendant class.
260 def register_functions(self):
261 self.server.register_function(self.noop)
264 # Sample no-op server function. The no-op function decodes the credential
265 # that was passed to it.
267 def noop(self, cred, anything):
268 self.decode_authentication(cred)
273 # Execute the server, serving requests forever.
276 self.server.serve_forever()