review logging again; code that runs in client and/or server now logs in the right...
[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 from sfa.util.sfalogging import sfa_logger
29
30 ##
31 # Verification callback for pyOpenSSL. We do our own checking of keys because
32 # we have our own authentication spec. Thus we disable several of the normal
33 # prohibitions that OpenSSL places on certificates
34
35 def verify_callback(conn, x509, err, depth, preverify):
36     # if the cert has been preverified, then it is ok
37     if preverify:
38        #print "  preverified"
39        return 1
40
41
42     # the certificate verification done by openssl checks a number of things
43     # that we aren't interested in, so we look out for those error messages
44     # and ignore them
45
46     # XXX SMBAKER: I don't know what this error is, but it's being returned
47     # by newer pl nodes.
48     if err == 9:
49        #print "  X509_V_ERR_CERT_NOT_YET_VALID"
50        return 1
51
52     # allow self-signed certificates
53     if err == 18:
54        #print "  X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT"
55        return 1
56
57     # allow certs that don't have an issuer
58     if err == 20:
59        #print "  X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY"
60        return 1
61
62     # allow chained certs with self-signed roots
63     if err == 19:
64         return 1
65     
66     # allow certs that are untrusted
67     if err == 21:
68        #print "  X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE"
69        return 1
70
71     # allow certs that are untrusted
72     if err == 27:
73        #print "  X509_V_ERR_CERT_UNTRUSTED"
74        return 1
75
76     print "  error", err, "in verify_callback"
77
78     return 0
79
80 ##
81 # taken from the web (XXX find reference). Implents HTTPS xmlrpc request handler
82 class SecureXMLRpcRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
83     """Secure XML-RPC request handler class.
84
85     It it very similar to SimpleXMLRPCRequestHandler but it uses HTTPS for transporting XML data.
86     """
87     def setup(self):
88         self.connection = self.request
89         self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
90         self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
91
92     def do_POST(self):
93         """Handles the HTTPS POST request.
94
95         It was copied out from SimpleXMLRPCServer.py and modified to shutdown 
96         the socket cleanly.
97         """
98         try:
99             peer_cert = Certificate()
100             peer_cert.load_from_pyopenssl_x509(self.connection.get_peer_certificate())
101             self.api = SfaAPI(peer_cert = peer_cert, 
102                               interface = self.server.interface, 
103                               key_file = self.server.key_file, 
104                               cert_file = self.server.cert_file,
105                               cache = self.cache)
106             # get arguments
107             request = self.rfile.read(int(self.headers["content-length"]))
108             remote_addr = (remote_ip, remote_port) = self.connection.getpeername()
109             self.api.remote_addr = remote_addr            
110             response = self.api.handle(remote_addr, request, self.server.method_map)
111         except Exception, fault:
112             # This should only happen if the module is buggy
113             # internal error, report as HTTP server error
114             sfa_error.log_exc("server.do_POST")
115             response = self.api.prepare_response(fault)
116             #self.send_response(500)
117             #self.end_headers()
118        
119         # got a valid 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         sfa_logger().debug("SecureXMLRPCServer.__init__, server_address=%s, cert_file=%s"%(server_address,cert_file))
139         self.logRequests = logRequests
140         self.interface = None
141         self.key_file = key_file
142         self.cert_file = cert_file
143         self.method_map = {}
144         # add cache to the request handler
145         HandlerClass.cache = Cache()
146         #for compatibility with python 2.4 (centos53)
147         if sys.version_info < (2, 5):
148             SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self)
149         else:
150            SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self, True, None)
151         SocketServer.BaseServer.__init__(self, server_address, HandlerClass)
152         ctx = SSL.Context(SSL.SSLv23_METHOD)
153         ctx.use_privatekey_file(key_file)        
154         ctx.use_certificate_file(cert_file)
155         # If you wanted to verify certs against known CAs.. this is how you would do it
156         #ctx.load_verify_locations('/etc/sfa/trusted_roots/plc.gpo.gid')
157         ctx.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT, verify_callback)
158         ctx.set_verify_depth(5)
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         sfa_logger().debug("SecureXMLRPCServer._dispatch, method=%s"%method)
172         try:
173             return SimpleXMLRPCServer.SimpleXMLRPCDispatcher._dispatch(self, method, params)
174         except:
175             # can't use format_exc() as it is not available in jython yet
176             # (evein in trunk).
177             type, value, tb = sys.exc_info()
178             raise xmlrpclib.Fault(1,''.join(traceback.format_exception(type, value, tb)))
179
180 ## From Active State code: http://code.activestate.com/recipes/574454/
181 # This is intended as a drop-in replacement for the ThreadingMixIn class in 
182 # module SocketServer of the standard lib. Instead of spawning a new thread 
183 # for each request, requests are processed by of pool of reusable threads.
184 class ThreadPoolMixIn(SocketServer.ThreadingMixIn):
185     """
186     use a thread pool instead of a new thread on every request
187     """
188     # XX TODO: Make this configurable
189     # config = Config()
190     # numThreads = config.SFA_SERVER_NUM_THREADS
191     numThreads = 25
192     allow_reuse_address = True  # seems to fix socket.error on server restart
193
194     def serve_forever(self):
195         """
196         Handle one request at a time until doomsday.
197         """
198         # set up the threadpool
199         self.requests = Queue()
200
201         for x in range(self.numThreads):
202             t = threading.Thread(target = self.process_request_thread)
203             t.setDaemon(1)
204             t.start()
205
206         # server main loop
207         while True:
208             self.handle_request()
209             
210         self.server_close()
211
212     
213     def process_request_thread(self):
214         """
215         obtain request from queue instead of directly from server socket
216         """
217         while True:
218             SocketServer.ThreadingMixIn.process_request_thread(self, *self.requests.get())
219
220     
221     def handle_request(self):
222         """
223         simply collect requests and put them on the queue for the workers.
224         """
225         try:
226             request, client_address = self.get_request()
227         except socket.error:
228             return
229         if self.verify_request(request, client_address):
230             self.requests.put((request, client_address))
231
232 class ThreadedServer(ThreadPoolMixIn, SecureXMLRPCServer):
233     pass
234 ##
235 # Implements an HTTPS XML-RPC server. Generally it is expected that SFA
236 # functions will take a credential string, which is passed to
237 # decode_authentication. Decode_authentication() will verify the validity of
238 # the credential, and verify that the user is using the key that matches the
239 # GID supplied in the credential.
240
241 class SfaServer(threading.Thread):
242
243     ##
244     # Create a new SfaServer object.
245     #
246     # @param ip the ip address to listen on
247     # @param port the port to listen on
248     # @param key_file private key filename of registry
249     # @param cert_file certificate filename containing public key 
250     #   (could be a GID file)
251
252     def __init__(self, ip, port, key_file, cert_file,interface):
253         threading.Thread.__init__(self)
254         self.key = Keypair(filename = key_file)
255         self.cert = Certificate(filename = cert_file)
256         #self.server = SecureXMLRPCServer((ip, port), SecureXMLRpcRequestHandler, key_file, cert_file)
257         self.server = ThreadedServer((ip, port), SecureXMLRpcRequestHandler, key_file, cert_file)
258         self.server.interface=interface
259         self.trusted_cert_list = None
260         self.register_functions()
261         sfa_logger().info("Starting SfaServer, interface=%s"%interface)
262
263     ##
264     # Register functions that will be served by the XMLRPC server. This
265     # function should be overridden by each descendant class.
266
267     def register_functions(self):
268         self.server.register_function(self.noop)
269
270     ##
271     # Sample no-op server function. The no-op function decodes the credential
272     # that was passed to it.
273
274     def noop(self, cred, anything):
275         self.decode_authentication(cred)
276         return anything
277
278     ##
279     # Execute the server, serving requests forever. 
280
281     def run(self):
282         self.server.serve_forever()
283
284