bunch of cleanups & fixes all over the place
[sfa.git] / sfa / util / geniserver.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
17 import SocketServer
18 import BaseHTTPServer
19 import SimpleHTTPServer
20 import SimpleXMLRPCServer
21
22 from OpenSSL import SSL
23
24 from sfa.trust.certificate import Keypair, Certificate
25 from sfa.trust.credential import *
26
27 from sfa.util.faults import *
28 from sfa.plc.api import GeniAPI 
29 from sfa.util.debug import log
30
31 ##
32 # Verification callback for pyOpenSSL. We do our own checking of keys because
33 # we have our own authentication spec. Thus we disable several of the normal
34 # prohibitions that OpenSSL places on certificates
35
36 def verify_callback(conn, x509, err, depth, preverify):
37     # if the cert has been preverified, then it is ok
38     if preverify:
39        #print "  preverified"
40        return 1
41
42     # we're only passing single certificates, not chains
43     if depth > 0:
44        #print "  depth > 0 in verify_callback"
45        return 0
46
47     # create a Certificate object and load it from the client's x509
48     ctx = conn.get_context()
49     server = ctx.get_app_data()
50     server.peer_cert = Certificate()
51     server.peer_cert.load_from_pyopenssl_x509(x509)
52
53     # the certificate verification done by openssl checks a number of things
54     # that we aren't interested in, so we look out for those error messages
55     # and ignore them
56
57     # XXX SMBAKER: I don't know what this error is, but it's being returned
58     # by newer pl nodes.
59     if err == 9:
60        #print "  X509_V_ERR_CERT_NOT_YET_VALID"
61        return 1
62
63     # allow self-signed certificates
64     if err == 18:
65        #print "  X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT"
66        return 1
67
68     # allow certs that don't have an issuer
69     if err == 20:
70        #print "  X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY"
71        return 1
72
73     # allow certs that are untrusted
74     if err == 21:
75        #print "  X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE"
76        return 1
77
78     # allow certs that are untrusted
79     if err == 27:
80        #print "  X509_V_ERR_CERT_UNTRUSTED"
81        return 1
82
83     print "  error", err, "in verify_callback"
84
85     return 0
86
87 ##
88 # Taken from the web (XXX find reference). Implements an HTTPS xmlrpc server
89
90 class SecureXMLRPCServer(BaseHTTPServer.HTTPServer,SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
91     def __init__(self, server_address, HandlerClass, key_file, cert_file, logRequests=True):
92         """Secure XML-RPC server.
93
94         It it very similar to SimpleXMLRPCServer but it uses HTTPS for transporting XML data.
95         """
96         self.logRequests = logRequests
97         self.interface = None
98         self.key_file = key_file
99         self.cert_file = cert_file
100         SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self, True, None)
101         SocketServer.BaseServer.__init__(self, server_address, HandlerClass)
102         ctx = SSL.Context(SSL.SSLv23_METHOD)
103         ctx.use_privatekey_file(key_file)
104         ctx.use_certificate_file(cert_file)
105         ctx.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT, verify_callback)
106         ctx.set_app_data(self)
107         self.socket = SSL.Connection(ctx, socket.socket(self.address_family,
108                                                         self.socket_type))
109         self.server_bind()
110         self.server_activate()
111
112     # _dispatch
113     #
114     # Convert an exception on the server to a full stack trace and send it to
115     # the client.
116
117     def _dispatch(self, method, params):
118         try:
119             return SimpleXMLRPCServer.SimpleXMLRPCDispatcher._dispatch(self, method, params)
120         except:
121             # can't use format_exc() as it is not available in jython yet
122             # (evein in trunk).
123             type, value, tb = sys.exc_info()
124             raise xmlrpclib.Fault(1,''.join(traceback.format_exception(type, value, tb)))
125
126 ##
127 # taken from the web (XXX find reference). Implents HTTPS xmlrpc request handler
128
129 class SecureXMLRpcRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
130     """Secure XML-RPC request handler class.
131
132     It it very similar to SimpleXMLRPCRequestHandler but it uses HTTPS for transporting XML data.
133     """
134     def setup(self):
135         self.connection = self.request
136         self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
137         self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
138
139     def do_POST(self):
140         """Handles the HTTPS POST request.
141
142         It was copied out from SimpleXMLRPCServer.py and modified to shutdown the socket cleanly.
143         """
144         self.api = GeniAPI(peer_cert = self.server.peer_cert, 
145                            interface = self.server.interface, 
146                            key_file = self.server.key_file, 
147                            cert_file = self.server.cert_file)
148         try:
149             # get arguments
150             request = self.rfile.read(int(self.headers["content-length"]))
151             # In previous versions of SimpleXMLRPCServer, _dispatch
152             # could be overridden in this class, instead of in
153             # SimpleXMLRPCDispatcher. To maintain backwards compatibility,
154             # check to see if a subclass implements _dispatch and dispatch
155             # using that method if present.
156             #response = self.server._marshaled_dispatch(request, getattr(self, '_dispatch', None))
157             # XX TODO: Need to get the real remote address 
158             source = None
159             response = self.api.handle(source, request)
160
161         
162         except Exception, fault:
163             # This should only happen if the module is buggy
164             # internal error, report as HTTP server error
165             self.send_response(500)
166             self.end_headers()
167         else:
168             # got a valid XML RPC response
169             self.send_response(200)
170             self.send_header("Content-type", "text/xml")
171             self.send_header("Content-length", str(len(response)))
172             self.end_headers()
173             self.wfile.write(response)
174
175             # shut down the connection
176             self.wfile.flush()
177             self.connection.shutdown() # Modified here!
178
179 ##
180 # Implements an HTTPS XML-RPC server. Generally it is expected that GENI
181 # functions will take a credential string, which is passed to
182 # decode_authentication. Decode_authentication() will verify the validity of
183 # the credential, and verify that the user is using the key that matches the
184 # GID supplied in the credential.
185
186 class GeniServer(threading.Thread):
187
188     ##
189     # Create a new GeniServer object.
190     #
191     # @param ip the ip address to listen on
192     # @param port the port to listen on
193     # @param key_file private key filename of registry
194     # @param cert_file certificate filename containing public key 
195     #   (could be a GID file)
196
197     def __init__(self, ip, port, key_file, cert_file):
198         threading.Thread.__init__(self)
199         self.key = Keypair(filename = key_file)
200         self.cert = Certificate(filename = cert_file)
201         self.server = SecureXMLRPCServer((ip, port), SecureXMLRpcRequestHandler, key_file, cert_file)
202         self.trusted_cert_list = None
203         self.register_functions()
204
205
206     ##
207     # Register functions that will be served by the XMLRPC server. This
208     # function should be overrided by each descendant class.
209
210     def register_functions(self):
211         self.server.register_function(self.noop)
212
213     ##
214     # Sample no-op server function. The no-op function decodes the credential
215     # that was passed to it.
216
217     def noop(self, cred, anything):
218         self.decode_authentication(cred)
219
220         return anything
221
222     ##
223     # Execute the server, serving requests forever. 
224
225     def run(self):
226         self.server.serve_forever()
227
228