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
28 from sfa.util.sfalogging import sfa_logger
31 # Verification callback for pyOpenSSL. We do our own checking of keys because
32 # we have our own authentication spec. Thus we disable several of the normal
33 # prohibitions that OpenSSL places on certificates
35 def verify_callback(conn, x509, err, depth, preverify):
36 # if the cert has been preverified, then it is ok
42 # the certificate verification done by openssl checks a number of things
43 # that we aren't interested in, so we look out for those error messages
46 # XXX SMBAKER: I don't know what this error is, but it's being returned
49 #print " X509_V_ERR_CERT_NOT_YET_VALID"
52 # allow self-signed certificates
54 #print " X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT"
57 # allow certs that don't have an issuer
59 #print " X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY"
62 # allow chained certs with self-signed roots
66 # allow certs that are untrusted
68 #print " X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE"
71 # allow certs that are untrusted
73 #print " X509_V_ERR_CERT_UNTRUSTED"
76 print " error", err, "in verify_callback"
81 # taken from the web (XXX find reference). Implents HTTPS xmlrpc request handler
82 class SecureXMLRpcRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
83 """Secure XML-RPC request handler class.
85 It it very similar to SimpleXMLRPCRequestHandler but it uses HTTPS for transporting XML data.
88 self.connection = self.request
89 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
90 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
93 """Handles the HTTPS POST request.
95 It was copied out from SimpleXMLRPCServer.py and modified to shutdown
99 peer_cert = Certificate()
100 peer_cert.load_from_pyopenssl_x509(self.connection.get_peer_certificate())
101 self.api = SfaAPI(peer_cert = peer_cert,
102 interface = self.server.interface,
103 key_file = self.server.key_file,
104 cert_file = self.server.cert_file,
107 request = self.rfile.read(int(self.headers["content-length"]))
108 remote_addr = (remote_ip, remote_port) = self.connection.getpeername()
109 self.api.remote_addr = remote_addr
110 response = self.api.handle(remote_addr, request, self.server.method_map)
111 except Exception, fault:
112 # This should only happen if the module is buggy
113 # internal error, report as HTTP server error
114 sfa_logger().log_exc("server.do_POST")
115 response = self.api.prepare_response(fault)
116 #self.send_response(500)
119 # got a valid 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 sfa_logger().debug("SecureXMLRPCServer.__init__, server_address=%s, cert_file=%s"%(server_address,cert_file))
139 self.logRequests = logRequests
140 self.interface = None
141 self.key_file = key_file
142 self.cert_file = cert_file
144 # add cache to the request handler
145 HandlerClass.cache = Cache()
146 #for compatibility with python 2.4 (centos53)
147 if sys.version_info < (2, 5):
148 SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self)
150 SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self, True, None)
151 SocketServer.BaseServer.__init__(self, server_address, HandlerClass)
152 ctx = SSL.Context(SSL.SSLv23_METHOD)
153 ctx.use_privatekey_file(key_file)
154 ctx.use_certificate_file(cert_file)
155 # If you wanted to verify certs against known CAs.. this is how you would do it
156 #ctx.load_verify_locations('/etc/sfa/trusted_roots/plc.gpo.gid')
157 ctx.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT, verify_callback)
158 ctx.set_verify_depth(5)
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):
171 sfa_logger().debug("SecureXMLRPCServer._dispatch, method=%s"%method)
173 return SimpleXMLRPCServer.SimpleXMLRPCDispatcher._dispatch(self, method, params)
175 # can't use format_exc() as it is not available in jython yet
177 type, value, tb = sys.exc_info()
178 raise xmlrpclib.Fault(1,''.join(traceback.format_exception(type, value, tb)))
180 ## From Active State code: http://code.activestate.com/recipes/574454/
181 # This is intended as a drop-in replacement for the ThreadingMixIn class in
182 # module SocketServer of the standard lib. Instead of spawning a new thread
183 # for each request, requests are processed by of pool of reusable threads.
184 class ThreadPoolMixIn(SocketServer.ThreadingMixIn):
186 use a thread pool instead of a new thread on every request
188 # XX TODO: Make this configurable
190 # numThreads = config.SFA_SERVER_NUM_THREADS
192 allow_reuse_address = True # seems to fix socket.error on server restart
194 def serve_forever(self):
196 Handle one request at a time until doomsday.
198 # set up the threadpool
199 self.requests = Queue()
201 for x in range(self.numThreads):
202 t = threading.Thread(target = self.process_request_thread)
208 self.handle_request()
213 def process_request_thread(self):
215 obtain request from queue instead of directly from server socket
218 SocketServer.ThreadingMixIn.process_request_thread(self, *self.requests.get())
221 def handle_request(self):
223 simply collect requests and put them on the queue for the workers.
226 request, client_address = self.get_request()
229 if self.verify_request(request, client_address):
230 self.requests.put((request, client_address))
232 class ThreadedServer(ThreadPoolMixIn, SecureXMLRPCServer):
235 # Implements an HTTPS XML-RPC server. Generally it is expected that SFA
236 # functions will take a credential string, which is passed to
237 # decode_authentication. Decode_authentication() will verify the validity of
238 # the credential, and verify that the user is using the key that matches the
239 # GID supplied in the credential.
241 class SfaServer(threading.Thread):
244 # Create a new SfaServer object.
246 # @param ip the ip address to listen on
247 # @param port the port to listen on
248 # @param key_file private key filename of registry
249 # @param cert_file certificate filename containing public key
250 # (could be a GID file)
252 def __init__(self, ip, port, key_file, cert_file,interface):
253 threading.Thread.__init__(self)
254 self.key = Keypair(filename = key_file)
255 self.cert = Certificate(filename = cert_file)
256 #self.server = SecureXMLRPCServer((ip, port), SecureXMLRpcRequestHandler, key_file, cert_file)
257 self.server = ThreadedServer((ip, port), SecureXMLRpcRequestHandler, key_file, cert_file)
258 self.server.interface=interface
259 self.trusted_cert_list = None
260 self.register_functions()
261 sfa_logger().info("Starting SfaServer, interface=%s"%interface)
264 # Register functions that will be served by the XMLRPC server. This
265 # function should be overridden by each descendant class.
267 def register_functions(self):
268 self.server.register_function(self.noop)
271 # Sample no-op server function. The no-op function decodes the credential
272 # that was passed to it.
274 def noop(self, cred, anything):
275 self.decode_authentication(cred)
279 # Execute the server, serving requests forever.
282 self.server.serve_forever()