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(" unhandled error %s in verify_callback" % err)
86 # taken from the web (XXX find reference). Implements HTTPS xmlrpc request
89 # python-2.7 http://code.activestate.com/recipes/442473-simple-http-server-supporting-ssl-secure-communica/
90 # python-3.3 https://gist.github.com/ubershmekel/6194556
91 class SecureXMLRpcRequestHandler(xmlrpc.server.SimpleXMLRPCRequestHandler):
93 Secure XML-RPC request handler class.
95 It it very similar to SimpleXMLRPCRequestHandler
96 but it uses HTTPS for transporting XML data.
100 self.connection = self.request
101 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
102 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
104 # xmlrpc.server.SimpleXMLRPCRequestHandler inherits
105 # http.server.BaseHTTPRequestHandler, that already has
106 # the rfile and wfile attributes
110 Handles the HTTPS POST request.
112 It was copied out from SimpleXMLRPCServer.py and modified to shutdown
116 peer_cert = Certificate()
117 peer_cert.load_from_pyopenssl_x509(
118 self.connection.get_peer_certificate())
119 generic = Generic.the_flavour()
120 self.api = generic.make_api(peer_cert=peer_cert,
121 interface=self.server.interface,
122 key_file=self.server.key_file,
123 cert_file=self.server.cert_file,
125 # logger.info("SecureXMLRpcRequestHandler.do_POST:")
126 # logger.info("interface=%s"%self.server.interface)
127 # logger.info("key_file=%s"%self.server.key_file)
128 # logger.info("api=%s"%self.api)
129 # logger.info("server=%s"%self.server)
130 # logger.info("handler=%s"%self)
132 request = self.rfile.read(int(self.headers["content-length"]))
134 remote_ip, remote_port) = self.connection.getpeername()
135 self.api.remote_addr = remote_addr
136 response = self.api.handle(
137 remote_addr, request, self.server.method_map)
138 except Exception as fault:
139 # This should only happen if the module is buggy
140 # internal error, report as HTTP server error
141 logger.log_exc("server.do_POST")
142 response = self.api.prepare_response(fault)
143 # self.send_response(500)
146 # avoid session/connection leaks : do this no matter what
148 self.send_response(200)
149 self.send_header("Content-type", "text/xml")
150 self.send_header("Content-length", str(len(response)))
152 self.wfile.write(response)
154 # close db connection
155 self.api.close_dbsession()
156 # shut down the connection
157 self.connection.shutdown() # Modified here!
160 # Taken from the web (XXX find reference). Implements an HTTPS xmlrpc server
163 class SecureXMLRPCServer(http.server.HTTPServer,
164 xmlrpc.server.SimpleXMLRPCDispatcher):
166 def __init__(self, server_address, HandlerClass,
167 key_file, cert_file, logRequests=True):
169 Secure XML-RPC server.
171 It it very similar to SimpleXMLRPCServer
172 but it uses HTTPS for transporting XML data.
175 f"SecureXMLRPCServer.__init__, server_address={server_address}, "
176 f"cert_file={cert_file}, key_file={key_file}")
177 self.logRequests = logRequests
178 self.interface = None
179 self.key_file = key_file
180 self.cert_file = cert_file
182 # add cache to the request handler
183 HandlerClass.cache = Cache()
184 # for compatibility with python 2.4 (centos53)
185 if sys.version_info < (2, 5):
186 xmlrpc.server.SimpleXMLRPCDispatcher.__init__(self)
188 xmlrpc.server.SimpleXMLRPCDispatcher.__init__(
190 socketserver.BaseServer.__init__(self, server_address, HandlerClass)
191 ctx = SSL.Context(SSL.SSLv23_METHOD)
192 ctx.use_privatekey_file(key_file)
193 ctx.use_certificate_file(cert_file)
194 # If you wanted to verify certs against known CAs..
195 # this is how you would do it
196 # ctx.load_verify_locations('/etc/sfa/trusted_roots/plc.gpo.gid')
198 trusted_cert_files = TrustedRoots(
199 config.get_trustedroots_dir()).get_file_list()
200 for cert_file in trusted_cert_files:
201 ctx.load_verify_locations(cert_file)
202 ctx.set_verify(SSL.VERIFY_PEER |
203 SSL.VERIFY_FAIL_IF_NO_PEER_CERT, verify_callback)
204 ctx.set_verify_depth(5)
205 ctx.set_app_data(self)
206 self.socket = SSL.Connection(ctx, socket.socket(self.address_family,
209 self.server_activate()
213 # Convert an exception on the server to a full stack trace and send it to
216 def _dispatch(self, method, params):
217 logger.debug("SecureXMLRPCServer._dispatch, method=%s" % method)
219 return xmlrpc.server.SimpleXMLRPCDispatcher._dispatch(
220 self, method, params)
222 # can't use format_exc() as it is not available in jython yet
224 type, value, tb = sys.exc_info()
225 raise xmlrpc.client.Fault(1, ''.join(
226 traceback.format_exception(type, value, tb)))
228 # override this one from the python 2.7 code
229 # originally defined in class TCPServer
230 def shutdown_request(self, request):
232 Called to shutdown and close an individual request.
235 # the std python 2.7 code just attempts a
236 # request.shutdown(socket.SHUT_WR)
237 # this works fine with regular sockets
238 # However we are dealing with an instance of
239 # OpenSSL.SSL.Connection instead
240 # This one only supports shutdown(), and in addition this does not
241 # always perform as expected
242 # ---------- std python 2.7 code
244 # explicitly shutdown. socket.close() merely releases
245 # the socket and waits for GC to perform the actual close.
246 request.shutdown(socket.SHUT_WR)
248 pass # some platforms may raise ENOTCONN here
251 # we are dealing with an OpenSSL.Connection object,
252 # try to shut it down but never mind if that fails
258 self.close_request(request)
260 # From Active State code: http://code.activestate.com/recipes/574454/
261 # This is intended as a drop-in replacement for the ThreadingMixIn class in
262 # module SocketServer of the standard lib. Instead of spawning a new thread
263 # for each request, requests are processed by of pool of reusable threads.
266 class ThreadPoolMixIn(socketserver.ThreadingMixIn):
268 use a thread pool instead of a new thread on every request
270 # XX TODO: Make this configurable
272 # numThreads = config.SFA_SERVER_NUM_THREADS
274 allow_reuse_address = True # seems to fix socket.error on server restart
276 def serve_forever(self):
278 Handle one request at a time until doomsday.
280 # set up the threadpool
281 self.requests = Queue()
283 for _ in range(self.numThreads):
284 thread = threading.Thread(target=self.process_request_thread)
290 self.handle_request()
294 def process_request_thread(self):
296 obtain request from queue instead of directly from server socket
299 socketserver.ThreadingMixIn.process_request_thread(
300 self, *self.requests.get())
302 def handle_request(self):
304 simply collect requests and put them on the queue for the workers.
307 request, client_address = self.get_request()
310 if self.verify_request(request, client_address):
311 self.requests.put((request, client_address))
314 class ThreadedServer(ThreadPoolMixIn, SecureXMLRPCServer):