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