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