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?
13 from Queue import Queue
16 import SimpleXMLRPCServer
17 from OpenSSL import SSL
19 from sfa.util.sfalogging import logger
20 from sfa.util.config import Config
21 from sfa.util.cache import Cache
22 from sfa.trust.certificate import Certificate
23 from sfa.trust.trustedroots import TrustedRoots
24 from sfa.util.py23 import xmlrpc_client
26 # don't hard code an api class anymore here
27 from sfa.generic import Generic
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
35 def verify_callback(conn, x509, err, depth, preverify):
36 # if the cert has been preverified, then it is ok
38 # print " preverified"
41 # the certificate verification done by openssl checks a number of things
42 # that we aren't interested in, so we look out for those error messages
45 # XXX SMBAKER: I don't know what this error is, but it's being returned
46 # xxx thierry: this most likely means the cert has a validity range in the future
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 # ignore X509_V_ERR_CERT_SIGNATURE_FAILURE
80 logger.debug(" error %s in verify_callback" % err)
85 # taken from the web (XXX find reference). Implements HTTPS xmlrpc request
89 class SecureXMLRpcRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
90 """Secure XML-RPC request handler class.
92 It it very similar to SimpleXMLRPCRequestHandler but it uses HTTPS for transporting XML data.
96 self.connection = self.request
97 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
98 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
101 """Handles the HTTPS POST request.
103 It was copied out from SimpleXMLRPCServer.py and modified to shutdown
107 peer_cert = Certificate()
108 peer_cert.load_from_pyopenssl_x509(
109 self.connection.get_peer_certificate())
110 generic = Generic.the_flavour()
111 self.api = generic.make_api(peer_cert=peer_cert,
112 interface=self.server.interface,
113 key_file=self.server.key_file,
114 cert_file=self.server.cert_file,
116 # logger.info("SecureXMLRpcRequestHandler.do_POST:")
117 # logger.info("interface=%s"%self.server.interface)
118 # logger.info("key_file=%s"%self.server.key_file)
119 # logger.info("api=%s"%self.api)
120 # logger.info("server=%s"%self.server)
121 # logger.info("handler=%s"%self)
123 request = self.rfile.read(int(self.headers["content-length"]))
125 remote_ip, remote_port) = self.connection.getpeername()
126 self.api.remote_addr = remote_addr
127 response = self.api.handle(
128 remote_addr, request, self.server.method_map)
129 except Exception as fault:
130 # This should only happen if the module is buggy
131 # internal error, report as HTTP server error
132 logger.log_exc("server.do_POST")
133 response = self.api.prepare_response(fault)
134 # self.send_response(500)
137 # avoid session/connection leaks : do this no matter what
139 self.send_response(200)
140 self.send_header("Content-type", "text/xml")
141 self.send_header("Content-length", str(len(response)))
143 self.wfile.write(response)
145 # close db connection
146 self.api.close_dbsession()
147 # shut down the connection
148 self.connection.shutdown() # Modified here!
151 # Taken from the web (XXX find reference). Implements an HTTPS xmlrpc server
154 class SecureXMLRPCServer(BaseHTTPServer.HTTPServer, SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
156 def __init__(self, server_address, HandlerClass, key_file, cert_file, logRequests=True):
158 Secure XML-RPC server.
160 It it very similar to SimpleXMLRPCServer but it uses HTTPS for transporting XML data.
162 logger.debug("SecureXMLRPCServer.__init__, server_address=%s, "
163 "cert_file=%s, key_file=%s" % (server_address, cert_file, key_file))
164 self.logRequests = logRequests
165 self.interface = None
166 self.key_file = key_file
167 self.cert_file = cert_file
169 # add cache to the request handler
170 HandlerClass.cache = Cache()
171 # for compatibility with python 2.4 (centos53)
172 if sys.version_info < (2, 5):
173 SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self)
175 SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(
177 SocketServer.BaseServer.__init__(self, server_address, HandlerClass)
178 ctx = SSL.Context(SSL.SSLv23_METHOD)
179 ctx.use_privatekey_file(key_file)
180 ctx.use_certificate_file(cert_file)
181 # If you wanted to verify certs against known CAs.. this is how you would do it
182 # ctx.load_verify_locations('/etc/sfa/trusted_roots/plc.gpo.gid')
184 trusted_cert_files = TrustedRoots(
185 config.get_trustedroots_dir()).get_file_list()
186 for cert_file in trusted_cert_files:
187 ctx.load_verify_locations(cert_file)
188 ctx.set_verify(SSL.VERIFY_PEER |
189 SSL.VERIFY_FAIL_IF_NO_PEER_CERT, verify_callback)
190 ctx.set_verify_depth(5)
191 ctx.set_app_data(self)
192 self.socket = SSL.Connection(ctx, socket.socket(self.address_family,
195 self.server_activate()
199 # Convert an exception on the server to a full stack trace and send it to
202 def _dispatch(self, method, params):
203 logger.debug("SecureXMLRPCServer._dispatch, method=%s" % method)
205 return SimpleXMLRPCServer.SimpleXMLRPCDispatcher._dispatch(self, method, params)
207 # can't use format_exc() as it is not available in jython yet
209 type, value, tb = sys.exc_info()
210 raise xmlrpc_client.Fault(1, ''.join(
211 traceback.format_exception(type, value, tb)))
213 # override this one from the python 2.7 code
214 # originally defined in class TCPServer
215 def shutdown_request(self, request):
216 """Called to shutdown and close an individual request."""
218 # the std python 2.7 code just attempts a request.shutdown(socket.SHUT_WR)
219 # this works fine with regular sockets
220 # However we are dealing with an instance of OpenSSL.SSL.Connection instead
221 # This one only supports shutdown(), and in addition this does not
222 # always perform as expected
223 # ---------- std python 2.7 code
225 # explicitly shutdown. socket.close() merely releases
226 # the socket and waits for GC to perform the actual close.
227 request.shutdown(socket.SHUT_WR)
229 pass # some platforms may raise ENOTCONN here
232 # we are dealing with an OpenSSL.Connection object,
233 # try to shut it down but never mind if that fails
239 self.close_request(request)
241 # From Active State code: http://code.activestate.com/recipes/574454/
242 # This is intended as a drop-in replacement for the ThreadingMixIn class in
243 # module SocketServer of the standard lib. Instead of spawning a new thread
244 # for each request, requests are processed by of pool of reusable threads.
247 class ThreadPoolMixIn(SocketServer.ThreadingMixIn):
249 use a thread pool instead of a new thread on every request
251 # XX TODO: Make this configurable
253 # numThreads = config.SFA_SERVER_NUM_THREADS
255 allow_reuse_address = True # seems to fix socket.error on server restart
257 def serve_forever(self):
259 Handle one request at a time until doomsday.
261 # set up the threadpool
262 self.requests = Queue()
264 for x in range(self.numThreads):
265 t = threading.Thread(target=self.process_request_thread)
271 self.handle_request()
275 def process_request_thread(self):
277 obtain request from queue instead of directly from server socket
280 SocketServer.ThreadingMixIn.process_request_thread(
281 self, *self.requests.get())
283 def handle_request(self):
285 simply collect requests and put them on the queue for the workers.
288 request, client_address = self.get_request()
291 if self.verify_request(request, client_address):
292 self.requests.put((request, client_address))
295 class ThreadedServer(ThreadPoolMixIn, SecureXMLRPCServer):