added TODO
[sfa.git] / sfa / util / server.py
1 ##
2 # This module implements a general-purpose server layer for sfa.
3 # The same basic server should be usable on the registry, component, or
4 # other interfaces.
5 #
6 # TODO: investigate ways to combine this with existing PLC server?
7 ##
8
9 ### $Id$
10 ### $URL$
11
12 import sys
13 import traceback
14 import threading
15 import socket, os
16 import SocketServer
17 import BaseHTTPServer
18 import SimpleHTTPServer
19 import SimpleXMLRPCServer
20 from OpenSSL import SSL
21 from Queue import Queue
22 from sfa.trust.certificate import Keypair, Certificate
23 from sfa.trust.credential import *
24 from sfa.util.faults import *
25 from sfa.plc.api import SfaAPI 
26 from sfa.util.debug import log
27
28 ##
29 # Verification callback for pyOpenSSL. We do our own checking of keys because
30 # we have our own authentication spec. Thus we disable several of the normal
31 # prohibitions that OpenSSL places on certificates
32
33 def verify_callback(conn, x509, err, depth, preverify):
34     # if the cert has been preverified, then it is ok
35     if preverify:
36        #print "  preverified"
37        return 1
38
39     # we're only passing single certificates, not chains
40     if depth > 0:
41        #print "  depth > 0 in verify_callback"
42        return 0
43
44     # create a Certificate object and load it from the client's x509
45     ctx = conn.get_context()
46     server = ctx.get_app_data()
47     server.peer_cert = Certificate()
48     server.peer_cert.load_from_pyopenssl_x509(x509)
49
50     # the certificate verification done by openssl checks a number of things
51     # that we aren't interested in, so we look out for those error messages
52     # and ignore them
53
54     # XXX SMBAKER: I don't know what this error is, but it's being returned
55     # by newer pl nodes.
56     if err == 9:
57        #print "  X509_V_ERR_CERT_NOT_YET_VALID"
58        return 1
59
60     # allow self-signed certificates
61     if err == 18:
62        #print "  X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT"
63        return 1
64
65     # allow certs that don't have an issuer
66     if err == 20:
67        #print "  X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY"
68        return 1
69
70     # allow certs that are untrusted
71     if err == 21:
72        #print "  X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE"
73        return 1
74
75     # allow certs that are untrusted
76     if err == 27:
77        #print "  X509_V_ERR_CERT_UNTRUSTED"
78        return 1
79
80     print "  error", err, "in verify_callback"
81
82     return 0
83
84 ##
85 # taken from the web (XXX find reference). Implents HTTPS xmlrpc request handler
86 class SecureXMLRpcRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
87     """Secure XML-RPC request handler class.
88
89     It it very similar to SimpleXMLRPCRequestHandler but it uses HTTPS for transporting XML data.
90     """
91     def setup(self):
92         self.connection = self.request
93         self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
94         self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
95
96     def do_POST(self):
97         """Handles the HTTPS POST request.
98
99         It was copied out from SimpleXMLRPCServer.py and modified to shutdown the socket cleanly.
100         """
101         try:
102             self.api = SfaAPI(peer_cert = self.server.peer_cert, 
103                               interface = self.server.interface, 
104                               key_file = self.server.key_file, 
105                               cert_file = self.server.cert_file)
106             # get arguments
107             request = self.rfile.read(int(self.headers["content-length"]))
108             # In previous versions of SimpleXMLRPCServer, _dispatch
109             # could be overridden in this class, instead of in
110             # SimpleXMLRPCDispatcher. To maintain backwards compatibility,
111             # check to see if a subclass implements _dispatch and dispatch
112             # using that method if present.
113             #response = self.server._marshaled_dispatch(request, getattr(self, '_dispatch', None))
114             remote_addr = (remote_ip, remote_port) = self.connection.getpeername()
115             self.api.remote_addr = remote_addr
116             response = self.api.handle(remote_addr, request)
117
118         
119         except Exception, fault:
120             raise
121             # This should only happen if the module is buggy
122             # internal error, report as HTTP server error
123             self.send_response(500)
124             self.end_headers()
125         else:
126             # got a valid XML RPC response
127             self.send_response(200)
128             self.send_header("Content-type", "text/xml")
129             self.send_header("Content-length", str(len(response)))
130             self.end_headers()
131             self.wfile.write(response)
132
133             # shut down the connection
134             self.wfile.flush()
135             self.connection.shutdown() # Modified here!
136
137 ##
138 # Taken from the web (XXX find reference). Implements an HTTPS xmlrpc server
139 class SecureXMLRPCServer(BaseHTTPServer.HTTPServer,SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
140     def __init__(self, server_address, HandlerClass, key_file, cert_file, logRequests=True):
141         """Secure XML-RPC server.
142
143         It it very similar to SimpleXMLRPCServer but it uses HTTPS for transporting XML data.
144         """
145         self.logRequests = logRequests
146         self.interface = None
147         self.key_file = key_file
148         self.cert_file = cert_file
149         #for compatibility with python 2.4 (centos53)
150         if sys.version_info < (2, 5):
151             SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self)
152         else:
153            SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self, True, None)
154         SocketServer.BaseServer.__init__(self, server_address, HandlerClass)
155         ctx = SSL.Context(SSL.SSLv23_METHOD)
156         ctx.use_privatekey_file(key_file)
157         ctx.use_certificate_file(cert_file)
158         ctx.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT, verify_callback)
159         ctx.set_app_data(self)
160         self.socket = SSL.Connection(ctx, socket.socket(self.address_family,
161                                                         self.socket_type))
162         self.server_bind()
163         self.server_activate()
164
165     # _dispatch
166     #
167     # Convert an exception on the server to a full stack trace and send it to
168     # the client.
169
170     def _dispatch(self, method, params):
171         try:
172             return SimpleXMLRPCServer.SimpleXMLRPCDispatcher._dispatch(self, method, params)
173         except:
174             # can't use format_exc() as it is not available in jython yet
175             # (evein in trunk).
176             type, value, tb = sys.exc_info()
177             raise xmlrpclib.Fault(1,''.join(traceback.format_exception(type, value, tb)))
178
179 ## From Active State code: http://code.activestate.com/recipes/574454/
180 # This is intended as a drop-in replacement for the ThreadingMixIn class in 
181 # module SocketServer of the standard lib. Instead of spawning a new thread 
182 # for each request, requests are processed by of pool of reusable threads.
183 class ThreadPoolMixIn(SocketServer.ThreadingMixIn):
184     """
185     use a thread pool instead of a new thread on every request
186     """
187     # XX TODO: Make this configurable
188     # config = Config()
189     # numThreads = config.SFA_SERVER_NUM_THREADS
190     numThreads = 25
191     allow_reuse_address = True  # seems to fix socket.error on server restart
192
193     def serve_forever(self):
194         """
195         Handle one request at a time until doomsday.
196         """
197         # set up the threadpool
198         self.requests = Queue(self.numThreads)
199
200         for x in range(self.numThreads):
201             t = threading.Thread(target = self.process_request_thread)
202             t.setDaemon(1)
203             t.start()
204
205         # server main loop
206         while True:
207             self.handle_request()
208             
209         self.server_close()
210
211     
212     def process_request_thread(self):
213         """
214         obtain request from queue instead of directly from server socket
215         """
216         while True:
217             SocketServer.ThreadingMixIn.process_request_thread(self, *self.requests.get())
218
219     
220     def handle_request(self):
221         """
222         simply collect requests and put them on the queue for the workers.
223         """
224         try:
225             request, client_address = self.get_request()
226         except socket.error:
227             return
228         if self.verify_request(request, client_address):
229             self.requests.put((request, client_address))
230
231 class ThreadedServer(ThreadPoolMixIn, SecureXMLRPCServer):
232     pass
233 ##
234 # Implements an HTTPS XML-RPC server. Generally it is expected that SFA
235 # functions will take a credential string, which is passed to
236 # decode_authentication. Decode_authentication() will verify the validity of
237 # the credential, and verify that the user is using the key that matches the
238 # GID supplied in the credential.
239
240 class SfaServer(threading.Thread):
241
242     ##
243     # Create a new SfaServer object.
244     #
245     # @param ip the ip address to listen on
246     # @param port the port to listen on
247     # @param key_file private key filename of registry
248     # @param cert_file certificate filename containing public key 
249     #   (could be a GID file)
250
251     def __init__(self, ip, port, key_file, cert_file):
252         threading.Thread.__init__(self)
253         self.key = Keypair(filename = key_file)
254         self.cert = Certificate(filename = cert_file)
255         #self.server = SecureXMLRPCServer((ip, port), SecureXMLRpcRequestHandler, key_file, cert_file)
256         self.server = ThreadedServer((ip, port), SecureXMLRpcRequestHandler, key_file, cert_file)
257         self.trusted_cert_list = None
258         self.register_functions()
259
260
261     ##
262     # Register functions that will be served by the XMLRPC server. This
263     # function should be overrided by each descendant class.
264
265     def register_functions(self):
266         self.server.register_function(self.noop)
267
268     ##
269     # Sample no-op server function. The no-op function decodes the credential
270     # that was passed to it.
271
272     def noop(self, cred, anything):
273         self.decode_authentication(cred)
274
275         return anything
276
277     ##
278     # Execute the server, serving requests forever. 
279
280     def run(self):
281         self.server.serve_forever()
282
283