93c3e81430b75d24e86bb5cbcf11807e2eef2eab
[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 from sfa.util.sfalogging import logger
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
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
43     # and ignore them
44
45     # XXX SMBAKER: I don't know what this error is, but it's being returned
46     # by newer pl nodes.
47     if err == 9:
48        #print "  X509_V_ERR_CERT_NOT_YET_VALID"
49        return 1
50
51     # allow self-signed certificates
52     if err == 18:
53        #print "  X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT"
54        return 1
55
56     # allow certs that don't have an issuer
57     if err == 20:
58        #print "  X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY"
59        return 1
60
61     # allow chained certs with self-signed roots
62     if err == 19:
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, 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             traceback.print_exc()
114             response = self.api.prepare_response(fault)
115             #self.send_response(500)
116             #self.end_headers()
117        
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)))
122         self.end_headers()
123         self.wfile.write(response)
124
125         # shut down the connection
126         self.wfile.flush()
127         self.connection.shutdown() # Modified here!
128
129 ##
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.
134
135         It it very similar to SimpleXMLRPCServer but it uses HTTPS for transporting XML data.
136         """
137         self.logRequests = logRequests
138         self.interface = None
139         self.key_file = key_file
140         self.cert_file = cert_file
141         self.method_map = {}
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         # If you wanted to verify certs against known CAs.. this is how you would do it
154         #ctx.load_verify_locations('/etc/sfa/trusted_roots/plc.gpo.gid')
155         ctx.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT, verify_callback)
156         ctx.set_verify_depth(5)
157         ctx.set_app_data(self)
158         self.socket = SSL.Connection(ctx, socket.socket(self.address_family,
159                                                         self.socket_type))
160         self.server_bind()
161         self.server_activate()
162
163     # _dispatch
164     #
165     # Convert an exception on the server to a full stack trace and send it to
166     # the client.
167
168     def _dispatch(self, method, params):
169         try:
170             return SimpleXMLRPCServer.SimpleXMLRPCDispatcher._dispatch(self, method, params)
171         except:
172             # can't use format_exc() as it is not available in jython yet
173             # (evein in trunk).
174             type, value, tb = sys.exc_info()
175             raise xmlrpclib.Fault(1,''.join(traceback.format_exception(type, value, tb)))
176
177 ## From Active State code: http://code.activestate.com/recipes/574454/
178 # This is intended as a drop-in replacement for the ThreadingMixIn class in 
179 # module SocketServer of the standard lib. Instead of spawning a new thread 
180 # for each request, requests are processed by of pool of reusable threads.
181 class ThreadPoolMixIn(SocketServer.ThreadingMixIn):
182     """
183     use a thread pool instead of a new thread on every request
184     """
185     # XX TODO: Make this configurable
186     # config = Config()
187     # numThreads = config.SFA_SERVER_NUM_THREADS
188     numThreads = 25
189     allow_reuse_address = True  # seems to fix socket.error on server restart
190
191     def serve_forever(self):
192         """
193         Handle one request at a time until doomsday.
194         """
195         # set up the threadpool
196         self.requests = Queue()
197
198         for x in range(self.numThreads):
199             t = threading.Thread(target = self.process_request_thread)
200             t.setDaemon(1)
201             t.start()
202
203         # server main loop
204         while True:
205             self.handle_request()
206             
207         self.server_close()
208
209     
210     def process_request_thread(self):
211         """
212         obtain request from queue instead of directly from server socket
213         """
214         while True:
215             SocketServer.ThreadingMixIn.process_request_thread(self, *self.requests.get())
216
217     
218     def handle_request(self):
219         """
220         simply collect requests and put them on the queue for the workers.
221         """
222         try:
223             request, client_address = self.get_request()
224         except socket.error:
225             return
226         if self.verify_request(request, client_address):
227             self.requests.put((request, client_address))
228
229 class ThreadedServer(ThreadPoolMixIn, SecureXMLRPCServer):
230     pass
231 ##
232 # Implements an HTTPS XML-RPC server. Generally it is expected that SFA
233 # functions will take a credential string, which is passed to
234 # decode_authentication. Decode_authentication() will verify the validity of
235 # the credential, and verify that the user is using the key that matches the
236 # GID supplied in the credential.
237
238 class SfaServer(threading.Thread):
239
240     ##
241     # Create a new SfaServer object.
242     #
243     # @param ip the ip address to listen on
244     # @param port the port to listen on
245     # @param key_file private key filename of registry
246     # @param cert_file certificate filename containing public key 
247     #   (could be a GID file)
248
249     def __init__(self, ip, port, key_file, cert_file):
250         threading.Thread.__init__(self)
251         self.key = Keypair(filename = key_file)
252         self.cert = Certificate(filename = cert_file)
253         #self.server = SecureXMLRPCServer((ip, port), SecureXMLRpcRequestHandler, key_file, cert_file)
254         self.server = ThreadedServer((ip, port), SecureXMLRpcRequestHandler, key_file, cert_file)
255         self.trusted_cert_list = None
256         self.register_functions()
257
258
259     ##
260     # Register functions that will be served by the XMLRPC server. This
261     # function should be overridden by each descendant class.
262
263     def register_functions(self):
264         self.server.register_function(self.noop)
265
266     ##
267     # Sample no-op server function. The no-op function decodes the credential
268     # that was passed to it.
269
270     def noop(self, cred, anything):
271         self.decode_authentication(cred)
272         return anything
273
274     ##
275     # Execute the server, serving requests forever. 
276
277     def run(self):
278         self.server.serve_forever()
279
280