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.trustedroots import TrustedRoots
22 from sfa.util.config import Config
23 from sfa.trust.credential import *
24 from sfa.util.faults import *
25 from sfa.plc.api import SfaAPI
26 from sfa.util.cache import Cache
27 from sfa.util.sfalogging import logger
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
34 def verify_callback(conn, x509, err, depth, preverify):
35 # if the cert has been preverified, then it is ok
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
48 #print " X509_V_ERR_CERT_NOT_YET_VALID"
51 # allow self-signed certificates
53 #print " X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT"
56 # allow certs that don't have an issuer
58 #print " X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY"
61 # allow chained certs with self-signed roots
65 # allow certs that are untrusted
67 #print " X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE"
70 # allow certs that are untrusted
72 #print " X509_V_ERR_CERT_UNTRUSTED"
75 print " error", err, "in verify_callback"
80 # taken from the web (XXX find reference). Implements HTTPS xmlrpc request handler
81 class SecureXMLRpcRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
82 """Secure XML-RPC request handler class.
84 It it very similar to SimpleXMLRPCRequestHandler but it uses HTTPS for transporting XML data.
87 self.connection = self.request
88 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
89 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
92 """Handles the HTTPS POST request.
94 It was copied out from SimpleXMLRPCServer.py and modified to shutdown
98 peer_cert = Certificate()
99 peer_cert.load_from_pyopenssl_x509(self.connection.get_peer_certificate())
100 self.api = SfaAPI(peer_cert = peer_cert,
101 interface = self.server.interface,
102 key_file = self.server.key_file,
103 cert_file = self.server.cert_file,
106 request = self.rfile.read(int(self.headers["content-length"]))
107 remote_addr = (remote_ip, remote_port) = self.connection.getpeername()
108 self.api.remote_addr = remote_addr
109 response = self.api.handle(remote_addr, request, self.server.method_map)
110 except Exception, fault:
111 # This should only happen if the module is buggy
112 # internal error, report as HTTP server error
113 logger.log_exc("server.do_POST")
114 response = self.api.prepare_response(fault)
115 #self.send_response(500)
118 # got a valid response
119 self.send_response(200)
120 self.send_header("Content-type", "text/xml")
121 self.send_header("Content-length", str(len(response)))
123 self.wfile.write(response)
125 # shut down the connection
127 self.connection.shutdown() # Modified here!
130 # Taken from the web (XXX find reference). Implements an HTTPS xmlrpc server
131 class SecureXMLRPCServer(BaseHTTPServer.HTTPServer,SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
132 def __init__(self, server_address, HandlerClass, key_file, cert_file, logRequests=True):
133 """Secure XML-RPC server.
135 It it very similar to SimpleXMLRPCServer but it uses HTTPS for transporting XML data.
137 logger.debug("SecureXMLRPCServer.__init__, server_address=%s, cert_file=%s"%(server_address,cert_file))
138 self.logRequests = logRequests
139 self.interface = None
140 self.key_file = key_file
141 self.cert_file = cert_file
143 # add cache to the request handler
144 HandlerClass.cache = Cache()
145 #for compatibility with python 2.4 (centos53)
146 if sys.version_info < (2, 5):
147 SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self)
149 SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self, True, None)
150 SocketServer.BaseServer.__init__(self, server_address, HandlerClass)
151 ctx = SSL.Context(SSL.SSLv23_METHOD)
152 ctx.use_privatekey_file(key_file)
153 ctx.use_certificate_file(cert_file)
154 # If you wanted to verify certs against known CAs.. this is how you would do it
155 #ctx.load_verify_locations('/etc/sfa/trusted_roots/plc.gpo.gid')
157 trusted_cert_files = TrustedRoots(config.get_trustedroots_dir()).get_file_list()
158 for cert_file in trusted_cert_files:
159 ctx.load_verify_locations(cert_file)
160 ctx.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT, verify_callback)
161 ctx.set_verify_depth(5)
162 ctx.set_app_data(self)
163 self.socket = SSL.Connection(ctx, socket.socket(self.address_family,
166 self.server_activate()
170 # Convert an exception on the server to a full stack trace and send it to
173 def _dispatch(self, method, params):
174 logger.debug("SecureXMLRPCServer._dispatch, method=%s"%method)
176 return SimpleXMLRPCServer.SimpleXMLRPCDispatcher._dispatch(self, method, params)
178 # can't use format_exc() as it is not available in jython yet
180 type, value, tb = sys.exc_info()
181 raise xmlrpclib.Fault(1,''.join(traceback.format_exception(type, value, tb)))
183 # override this one from the python 2.7 code
184 # originally defined in class TCPServer
185 def shutdown_request(self, request):
186 """Called to shutdown and close an individual request."""
188 # the std python 2.7 code just attempts a request.shutdown(socket.SHUT_WR)
189 # this works fine with regular sockets
190 # However we are dealing with an instance of OpenSSL.SSL.Connection instead
191 # This one only supports shutdown(), and in addition this does not
192 # always perform as expected
193 # ---------- std python 2.7 code
195 #explicitly shutdown. socket.close() merely releases
196 #the socket and waits for GC to perform the actual close.
197 request.shutdown(socket.SHUT_WR)
199 pass #some platforms may raise ENOTCONN here
202 # we are dealing with an OpenSSL.Connection object,
203 # try to shut it down but never mind if that fails
204 try: request.shutdown()
207 self.close_request(request)
209 ## From Active State code: http://code.activestate.com/recipes/574454/
210 # This is intended as a drop-in replacement for the ThreadingMixIn class in
211 # module SocketServer of the standard lib. Instead of spawning a new thread
212 # for each request, requests are processed by of pool of reusable threads.
213 class ThreadPoolMixIn(SocketServer.ThreadingMixIn):
215 use a thread pool instead of a new thread on every request
217 # XX TODO: Make this configurable
219 # numThreads = config.SFA_SERVER_NUM_THREADS
221 allow_reuse_address = True # seems to fix socket.error on server restart
223 def serve_forever(self):
225 Handle one request at a time until doomsday.
227 # set up the threadpool
228 self.requests = Queue()
230 for x in range(self.numThreads):
231 t = threading.Thread(target = self.process_request_thread)
237 self.handle_request()
242 def process_request_thread(self):
244 obtain request from queue instead of directly from server socket
247 SocketServer.ThreadingMixIn.process_request_thread(self, *self.requests.get())
250 def handle_request(self):
252 simply collect requests and put them on the queue for the workers.
255 request, client_address = self.get_request()
258 if self.verify_request(request, client_address):
259 self.requests.put((request, client_address))
261 class ThreadedServer(ThreadPoolMixIn, SecureXMLRPCServer):
264 # Implements an HTTPS XML-RPC server. Generally it is expected that SFA
265 # functions will take a credential string, which is passed to
266 # decode_authentication. Decode_authentication() will verify the validity of
267 # the credential, and verify that the user is using the key that matches the
268 # GID supplied in the credential.
270 class SfaServer(threading.Thread):
273 # Create a new SfaServer object.
275 # @param ip the ip address to listen on
276 # @param port the port to listen on
277 # @param key_file private key filename of registry
278 # @param cert_file certificate filename containing public key
279 # (could be a GID file)
281 def __init__(self, ip, port, key_file, cert_file,interface):
282 threading.Thread.__init__(self)
283 self.key = Keypair(filename = key_file)
284 self.cert = Certificate(filename = cert_file)
285 #self.server = SecureXMLRPCServer((ip, port), SecureXMLRpcRequestHandler, key_file, cert_file)
286 self.server = ThreadedServer((ip, port), SecureXMLRpcRequestHandler, key_file, cert_file)
287 self.server.interface=interface
288 self.trusted_cert_list = None
289 self.register_functions()
290 logger.info("Starting SfaServer, interface=%s"%interface)
293 # Register functions that will be served by the XMLRPC server. This
294 # function should be overridden by each descendant class.
296 def register_functions(self):
297 self.server.register_function(self.noop)
300 # Sample no-op server function. The no-op function decodes the credential
301 # that was passed to it.
303 def noop(self, cred, anything):
304 self.decode_authentication(cred)
308 # Execute the server, serving requests forever.
311 self.server.serve_forever()