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