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