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