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
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
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
47 # has a validity range in the future
50 # print " X509_V_ERR_CERT_NOT_YET_VALID"
53 # allow self-signed certificates
55 # print " X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT"
58 # allow certs that don't have an issuer
60 # print " X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY"
63 # allow chained certs with self-signed roots
67 # allow certs that are untrusted
69 # print " X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE"
72 # allow certs that are untrusted
74 # print " X509_V_ERR_CERT_UNTRUSTED"
77 # ignore X509_V_ERR_CERT_SIGNATURE_FAILURE
81 logger.debug(" error %s in verify_callback" % err)
86 # taken from the web (XXX find reference). Implements HTTPS xmlrpc request
90 class SecureXMLRpcRequestHandler(xmlrpc.server.SimpleXMLRPCRequestHandler):
92 Secure XML-RPC request handler class.
94 It it very similar to SimpleXMLRPCRequestHandler
95 but it uses HTTPS for transporting XML data.
99 self.connection = self.request
100 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
101 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
105 Handles the HTTPS POST request.
107 It was copied out from SimpleXMLRPCServer.py and modified to shutdown
111 peer_cert = Certificate()
112 peer_cert.load_from_pyopenssl_x509(
113 self.connection.get_peer_certificate())
114 generic = Generic.the_flavour()
115 self.api = generic.make_api(peer_cert=peer_cert,
116 interface=self.server.interface,
117 key_file=self.server.key_file,
118 cert_file=self.server.cert_file,
120 # logger.info("SecureXMLRpcRequestHandler.do_POST:")
121 # logger.info("interface=%s"%self.server.interface)
122 # logger.info("key_file=%s"%self.server.key_file)
123 # logger.info("api=%s"%self.api)
124 # logger.info("server=%s"%self.server)
125 # logger.info("handler=%s"%self)
127 request = self.rfile.read(int(self.headers["content-length"]))
129 remote_ip, remote_port) = self.connection.getpeername()
130 self.api.remote_addr = remote_addr
131 response = self.api.handle(
132 remote_addr, request, self.server.method_map)
133 except Exception as fault:
134 # This should only happen if the module is buggy
135 # internal error, report as HTTP server error
136 logger.log_exc("server.do_POST")
137 response = self.api.prepare_response(fault)
138 # self.send_response(500)
141 # avoid session/connection leaks : do this no matter what
143 self.send_response(200)
144 self.send_header("Content-type", "text/xml")
145 self.send_header("Content-length", str(len(response)))
147 self.wfile.write(response)
149 # close db connection
150 self.api.close_dbsession()
151 # shut down the connection
152 self.connection.shutdown() # Modified here!
155 # Taken from the web (XXX find reference). Implements an HTTPS xmlrpc server
158 class SecureXMLRPCServer(http.server.HTTPServer,
159 xmlrpc.server.SimpleXMLRPCDispatcher):
161 def __init__(self, server_address, HandlerClass,
162 key_file, cert_file, logRequests=True):
164 Secure XML-RPC server.
166 It it very similar to SimpleXMLRPCServer
167 but it uses HTTPS for transporting XML data.
170 f"SecureXMLRPCServer.__init__, server_address={server_address}, "
171 f"cert_file={cert_file}, key_file={key_file}")
172 self.logRequests = logRequests
173 self.interface = None
174 self.key_file = key_file
175 self.cert_file = cert_file
177 # add cache to the request handler
178 HandlerClass.cache = Cache()
179 # for compatibility with python 2.4 (centos53)
180 if sys.version_info < (2, 5):
181 xmlrpc.server.SimpleXMLRPCDispatcher.__init__(self)
183 xmlrpc.server.SimpleXMLRPCDispatcher.__init__(
185 socketserver.BaseServer.__init__(self, server_address, HandlerClass)
186 ctx = SSL.Context(SSL.SSLv23_METHOD)
187 ctx.use_privatekey_file(key_file)
188 ctx.use_certificate_file(cert_file)
189 # If you wanted to verify certs against known CAs..
190 # this is how you would do it
191 # ctx.load_verify_locations('/etc/sfa/trusted_roots/plc.gpo.gid')
193 trusted_cert_files = TrustedRoots(
194 config.get_trustedroots_dir()).get_file_list()
195 for cert_file in trusted_cert_files:
196 ctx.load_verify_locations(cert_file)
197 ctx.set_verify(SSL.VERIFY_PEER |
198 SSL.VERIFY_FAIL_IF_NO_PEER_CERT, verify_callback)
199 ctx.set_verify_depth(5)
200 ctx.set_app_data(self)
201 self.socket = SSL.Connection(ctx, socket.socket(self.address_family,
204 self.server_activate()
208 # Convert an exception on the server to a full stack trace and send it to
211 def _dispatch(self, method, params):
212 logger.debug("SecureXMLRPCServer._dispatch, method=%s" % method)
214 return xmlrpc.server.SimpleXMLRPCDispatcher._dispatch(
215 self, method, params)
217 # can't use format_exc() as it is not available in jython yet
219 type, value, tb = sys.exc_info()
220 raise xmlrpc.client.Fault(1, ''.join(
221 traceback.format_exception(type, value, tb)))
223 # override this one from the python 2.7 code
224 # originally defined in class TCPServer
225 def shutdown_request(self, request):
227 Called to shutdown and close an individual request.
230 # the std python 2.7 code just attempts a
231 # request.shutdown(socket.SHUT_WR)
232 # this works fine with regular sockets
233 # However we are dealing with an instance of
234 # OpenSSL.SSL.Connection instead
235 # This one only supports shutdown(), and in addition this does not
236 # always perform as expected
237 # ---------- std python 2.7 code
239 # explicitly shutdown. socket.close() merely releases
240 # the socket and waits for GC to perform the actual close.
241 request.shutdown(socket.SHUT_WR)
243 pass # some platforms may raise ENOTCONN here
246 # we are dealing with an OpenSSL.Connection object,
247 # try to shut it down but never mind if that fails
253 self.close_request(request)
255 # From Active State code: http://code.activestate.com/recipes/574454/
256 # This is intended as a drop-in replacement for the ThreadingMixIn class in
257 # module SocketServer of the standard lib. Instead of spawning a new thread
258 # for each request, requests are processed by of pool of reusable threads.
261 class ThreadPoolMixIn(socketserver.ThreadingMixIn):
263 use a thread pool instead of a new thread on every request
265 # XX TODO: Make this configurable
267 # numThreads = config.SFA_SERVER_NUM_THREADS
269 allow_reuse_address = True # seems to fix socket.error on server restart
271 def serve_forever(self):
273 Handle one request at a time until doomsday.
275 # set up the threadpool
276 self.requests = Queue()
278 for _ in range(self.numThreads):
279 thread = threading.Thread(target=self.process_request_thread)
285 self.handle_request()
289 def process_request_thread(self):
291 obtain request from queue instead of directly from server socket
294 socketserver.ThreadingMixIn.process_request_thread(
295 self, *self.requests.get())
297 def handle_request(self):
299 simply collect requests and put them on the queue for the workers.
302 request, client_address = self.get_request()
305 if self.verify_request(request, client_address):
306 self.requests.put((request, client_address))
309 class ThreadedServer(ThreadPoolMixIn, SecureXMLRPCServer):