renaming the toplevel geni/ package into sfa/
[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.util.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, interface = self.server.interface, key_file = self.server.key_file, cert_file = self.server.cert_file)
145         try:
146             # get arguments
147             request = self.rfile.read(int(self.headers["content-length"]))
148             # In previous versions of SimpleXMLRPCServer, _dispatch
149             # could be overridden in this class, instead of in
150             # SimpleXMLRPCDispatcher. To maintain backwards compatibility,
151             # check to see if a subclass implements _dispatch and dispatch
152             # using that method if present.
153             #response = self.server._marshaled_dispatch(request, getattr(self, '_dispatch', None))
154             # XX TODO: Need to get the real remote address 
155             source = None
156             response = self.api.handle(source, request)
157
158         
159         except Exception, fault:
160             # This should only happen if the module is buggy
161             # internal error, report as HTTP server error
162             self.send_response(500)
163             self.end_headers()
164         else:
165             # got a valid XML RPC response
166             self.send_response(200)
167             self.send_header("Content-type", "text/xml")
168             self.send_header("Content-length", str(len(response)))
169             self.end_headers()
170             self.wfile.write(response)
171
172             # shut down the connection
173             self.wfile.flush()
174             self.connection.shutdown() # Modified here!
175
176 ##
177 # Implements an HTTPS XML-RPC server. Generally it is expected that GENI
178 # functions will take a credential string, which is passed to
179 # decode_authentication. Decode_authentication() will verify the validity of
180 # the credential, and verify that the user is using the key that matches the
181 # GID supplied in the credential.
182
183 class GeniServer(threading.Thread):
184
185     ##
186     # Create a new GeniServer object.
187     #
188     # @param ip the ip address to listen on
189     # @param port the port to listen on
190     # @param key_file private key filename of registry
191     # @param cert_file certificate filename containing public key 
192     #   (could be a GID file)
193
194     def __init__(self, ip, port, key_file, cert_file):
195         threading.Thread.__init__(self)
196         self.key = Keypair(filename = key_file)
197         self.cert = Certificate(filename = cert_file)
198         self.server = SecureXMLRPCServer((ip, port), SecureXMLRpcRequestHandler, key_file, cert_file)
199         self.trusted_cert_list = None
200         self.register_functions()
201
202
203     ##
204     # Register functions that will be served by the XMLRPC server. This
205     # function should be overrided by each descendant class.
206
207     def register_functions(self):
208         self.server.register_function(self.noop)
209
210     ##
211     # Sample no-op server function. The no-op function decodes the credential
212     # that was passed to it.
213
214     def noop(self, cred, anything):
215         self.decode_authentication(cred)
216
217         return anything
218
219     ##
220     # Execute the server, serving requests forever. 
221
222     def run(self):
223         self.server.serve_forever()
224
225