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 SimpleHTTPServer
17 import SimpleXMLRPCServer
18 from OpenSSL import SSL
20 from sfa.trust.certificate import Keypair, Certificate
21 from sfa.trust.credential import *
22 from sfa.util.faults import *
23 from sfa.plc.api import SfaAPI
24 from sfa.util.cache import Cache
25 from sfa.util.sfalogging import sfa_logger
28 # Verification callback for pyOpenSSL. We do our own checking of keys because
29 # we have our own authentication spec. Thus we disable several of the normal
30 # prohibitions that OpenSSL places on certificates
32 def verify_callback(conn, x509, err, depth, preverify):
33 # if the cert has been preverified, then it is ok
39 # the certificate verification done by openssl checks a number of things
40 # that we aren't interested in, so we look out for those error messages
43 # XXX SMBAKER: I don't know what this error is, but it's being returned
46 #print " X509_V_ERR_CERT_NOT_YET_VALID"
49 # allow self-signed certificates
51 #print " X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT"
54 # allow certs that don't have an issuer
56 #print " X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY"
59 # allow chained certs with self-signed roots
63 # allow certs that are untrusted
65 #print " X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE"
68 # allow certs that are untrusted
70 #print " X509_V_ERR_CERT_UNTRUSTED"
73 print " error", err, "in verify_callback"
78 # taken from the web (XXX find reference). Implements HTTPS xmlrpc request handler
79 class SecureXMLRpcRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
80 """Secure XML-RPC request handler class.
82 It it very similar to SimpleXMLRPCRequestHandler but it uses HTTPS for transporting XML data.
85 self.connection = self.request
86 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
87 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
90 """Handles the HTTPS POST request.
92 It was copied out from SimpleXMLRPCServer.py and modified to shutdown
96 peer_cert = Certificate()
97 peer_cert.load_from_pyopenssl_x509(self.connection.get_peer_certificate())
98 self.api = SfaAPI(peer_cert = peer_cert,
99 interface = self.server.interface,
100 key_file = self.server.key_file,
101 cert_file = self.server.cert_file,
104 request = self.rfile.read(int(self.headers["content-length"]))
105 remote_addr = (remote_ip, remote_port) = self.connection.getpeername()
106 self.api.remote_addr = remote_addr
107 response = self.api.handle(remote_addr, request, self.server.method_map)
108 except Exception, fault:
109 # This should only happen if the module is buggy
110 # internal error, report as HTTP server error
111 sfa_logger().log_exc("server.do_POST")
112 response = self.api.prepare_response(fault)
113 #self.send_response(500)
116 # got a valid response
117 self.send_response(200)
118 self.send_header("Content-type", "text/xml")
119 self.send_header("Content-length", str(len(response)))
121 self.wfile.write(response)
123 # shut down the connection
125 self.connection.shutdown() # Modified here!
128 # Taken from the web (XXX find reference). Implements an HTTPS xmlrpc server
129 class SecureXMLRPCServer(BaseHTTPServer.HTTPServer,SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
130 def __init__(self, server_address, HandlerClass, key_file, cert_file, logRequests=True):
131 """Secure XML-RPC server.
133 It it very similar to SimpleXMLRPCServer but it uses HTTPS for transporting XML data.
135 sfa_logger().debug("SecureXMLRPCServer.__init__, server_address=%s, cert_file=%s"%(server_address,cert_file))
136 self.logRequests = logRequests
137 self.interface = None
138 self.key_file = key_file
139 self.cert_file = cert_file
141 # add cache to the request handler
142 HandlerClass.cache = Cache()
143 #for compatibility with python 2.4 (centos53)
144 if sys.version_info < (2, 5):
145 SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self)
147 SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self, True, None)
148 SocketServer.BaseServer.__init__(self, server_address, HandlerClass)
149 ctx = SSL.Context(SSL.SSLv23_METHOD)
150 ctx.use_privatekey_file(key_file)
151 ctx.use_certificate_file(cert_file)
152 # If you wanted to verify certs against known CAs.. this is how you would do it
153 #ctx.load_verify_locations('/etc/sfa/trusted_roots/plc.gpo.gid')
154 ctx.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT, verify_callback)
155 ctx.set_verify_depth(5)
156 ctx.set_app_data(self)
157 self.socket = SSL.Connection(ctx, socket.socket(self.address_family,
160 self.server_activate()
164 # Convert an exception on the server to a full stack trace and send it to
167 def _dispatch(self, method, params):
168 sfa_logger().debug("SecureXMLRPCServer._dispatch, method=%s"%method)
170 return SimpleXMLRPCServer.SimpleXMLRPCDispatcher._dispatch(self, method, params)
172 # can't use format_exc() as it is not available in jython yet
174 type, value, tb = sys.exc_info()
175 raise xmlrpclib.Fault(1,''.join(traceback.format_exception(type, value, tb)))
177 # override this one from the python 2.7 code
178 # originally defined in class TCPServer
179 def shutdown_request(self, request):
180 """Called to shutdown and close an individual request."""
182 # the std python 2.7 code just attempts a request.shutdown(socket.SHUT_WR)
183 # this works fine with regular sockets
184 # However we are dealing with an instance of OpenSSL.SSL.Connection instead
185 # This one only supports shutdown(), and in addition this does not
186 # always perform as expected
187 # ---------- std python 2.7 code
189 #explicitly shutdown. socket.close() merely releases
190 #the socket and waits for GC to perform the actual close.
191 request.shutdown(socket.SHUT_WR)
193 pass #some platforms may raise ENOTCONN here
196 # we are dealing with an OpenSSL.Connection object,
197 # try to shut it down but never mind if that fails
198 try: request.shutdown()
201 self.close_request(request)
203 ## From Active State code: http://code.activestate.com/recipes/574454/
204 # This is intended as a drop-in replacement for the ThreadingMixIn class in
205 # module SocketServer of the standard lib. Instead of spawning a new thread
206 # for each request, requests are processed by of pool of reusable threads.
207 class ThreadPoolMixIn(SocketServer.ThreadingMixIn):
209 use a thread pool instead of a new thread on every request
211 # XX TODO: Make this configurable
213 # numThreads = config.SFA_SERVER_NUM_THREADS
215 allow_reuse_address = True # seems to fix socket.error on server restart
217 def serve_forever(self):
219 Handle one request at a time until doomsday.
221 # set up the threadpool
222 self.requests = Queue()
224 for x in range(self.numThreads):
225 t = threading.Thread(target = self.process_request_thread)
231 self.handle_request()
236 def process_request_thread(self):
238 obtain request from queue instead of directly from server socket
241 SocketServer.ThreadingMixIn.process_request_thread(self, *self.requests.get())
244 def handle_request(self):
246 simply collect requests and put them on the queue for the workers.
249 request, client_address = self.get_request()
252 if self.verify_request(request, client_address):
253 self.requests.put((request, client_address))
255 class ThreadedServer(ThreadPoolMixIn, SecureXMLRPCServer):
258 # Implements an HTTPS XML-RPC server. Generally it is expected that SFA
259 # functions will take a credential string, which is passed to
260 # decode_authentication. Decode_authentication() will verify the validity of
261 # the credential, and verify that the user is using the key that matches the
262 # GID supplied in the credential.
264 class SfaServer(threading.Thread):
267 # Create a new SfaServer object.
269 # @param ip the ip address to listen on
270 # @param port the port to listen on
271 # @param key_file private key filename of registry
272 # @param cert_file certificate filename containing public key
273 # (could be a GID file)
275 def __init__(self, ip, port, key_file, cert_file,interface):
276 threading.Thread.__init__(self)
277 self.key = Keypair(filename = key_file)
278 self.cert = Certificate(filename = cert_file)
279 #self.server = SecureXMLRPCServer((ip, port), SecureXMLRpcRequestHandler, key_file, cert_file)
280 self.server = ThreadedServer((ip, port), SecureXMLRpcRequestHandler, key_file, cert_file)
281 self.server.interface=interface
282 self.trusted_cert_list = None
283 self.register_functions()
284 sfa_logger().info("Starting SfaServer, interface=%s"%interface)
287 # Register functions that will be served by the XMLRPC server. This
288 # function should be overridden by each descendant class.
290 def register_functions(self):
291 self.server.register_function(self.noop)
294 # Sample no-op server function. The no-op function decodes the credential
295 # that was passed to it.
297 def noop(self, cred, anything):
298 self.decode_authentication(cred)
302 # Execute the server, serving requests forever.
305 self.server.serve_forever()