c4cd60a5642986ee9134e9418914a81c9f0623c4
[sfa.git] / util / geniserver.py
1 # geniserver.py
2 #
3 # geniwrapper server
4 #
5 # implements a general-purpose server layer for geni. This should be usable on
6 # the registry, component, or other interfaces.
7 #
8 # TODO: investigate ways to combine this with existing PLC server?
9
10 import SimpleXMLRPCServer
11
12 import SocketServer
13 import BaseHTTPServer\r
14 import SimpleHTTPServer\r
15 import SimpleXMLRPCServer\r
16 \r
17 from excep import *\r
18 from cert import *\r
19 from credential import *\r
20 \r
21 import socket, os\r
22 from OpenSSL import SSL\r
23 \r
24 # verify_callback\r
25 #\r
26 # verification callback for pyOpenSSL. We do our own checking of keys because\r
27 # we have our own authentication spec. Thus we disable several of the normal\r
28 # prohibitions that OpenSSL places on certificates\r
29 \r
30 def verify_callback(conn, x509, err, depth, preverify):\r
31     # if the cert has been preverified, then it is ok\r
32     if preverify:
33        #print "  preverified"
34        return 1
35
36     # we're only passing single certificates, not chains
37     if depth > 0:
38        #print "  depth > 0 in verify_callback"
39        return 0
40
41     # create a Certificate object and load it from the client's x509
42     ctx = conn.get_context()
43     server = ctx.get_app_data()
44     server.peer_cert = Certificate()
45     server.peer_cert.load_from_pyopenssl_x509(x509)
46
47     # the certificate verification done by openssl checks a number of things
48     # that we aren't interested in, so we look out for those error messages
49     # and ignore them
50
51     # allow self-signed certificates
52     if err == 18:
53        #print "  X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT"
54        return 1
55
56     # allow certs that don't have an issuer
57     if err == 20:
58        #print "  X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY"
59        return 1
60
61     # allow certs that are untrusted
62     if err == 21:
63        #print "  X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE"
64        return 1
65
66     # allow certs that are untrusted
67     if err == 27:
68        #print "  X509_V_ERR_CERT_UNTRUSTED"
69        return 1
70
71     print "  error", err, "in verify_callback"
72
73     return 0\r
74 \r
75 # SecureXMLServer\r
76 #\r
77 # taken from the web (XXX find reference). Implements an HTTPS xmlrpc server\r
78
79 class SecureXMLRPCServer(BaseHTTPServer.HTTPServer,SimpleXMLRPCServer.SimpleXMLRPCDispatcher):\r
80     def __init__(self, server_address, HandlerClass, key_file, cert_file, logRequests=True):\r
81         """Secure XML-RPC server.\r
82 \r
83         It it very similar to SimpleXMLRPCServer but it uses HTTPS for transporting XML data.\r
84         """\r
85         self.logRequests = logRequests\r
86 \r
87         SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self, None, None)\r
88         SocketServer.BaseServer.__init__(self, server_address, HandlerClass)\r
89         ctx = SSL.Context(SSL.SSLv23_METHOD)\r
90         ctx.use_privatekey_file(key_file)\r
91         ctx.use_certificate_file(cert_file)\r
92         ctx.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT, verify_callback)\r
93         ctx.set_app_data(self)\r
94         self.socket = SSL.Connection(ctx, socket.socket(self.address_family,\r
95                                                         self.socket_type))\r
96         self.server_bind()\r
97         self.server_activate()\r
98 \r
99 # SecureXMLRpcRequestHandler\r
100 #\r
101 # taken from the web (XXX find reference). Implents HTTPS xmlrpc request handler\r
102 \r
103 class SecureXMLRpcRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):\r
104     """Secure XML-RPC request handler class.\r
105 \r
106     It it very similar to SimpleXMLRPCRequestHandler but it uses HTTPS for transporting XML data.\r
107     """\r
108     def setup(self):\r
109         self.connection = self.request\r
110         self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)\r
111         self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)\r
112 \r
113     def do_POST(self):\r
114         """Handles the HTTPS POST request.\r
115 \r
116         It was copied out from SimpleXMLRPCServer.py and modified to shutdown the socket cleanly.\r
117         """\r
118 \r
119         try:\r
120             # get arguments\r
121             data = self.rfile.read(int(self.headers["content-length"]))\r
122             # In previous versions of SimpleXMLRPCServer, _dispatch\r
123             # could be overridden in this class, instead of in\r
124             # SimpleXMLRPCDispatcher. To maintain backwards compatibility,\r
125             # check to see if a subclass implements _dispatch and dispatch\r
126             # using that method if present.\r
127             response = self.server._marshaled_dispatch(\r
128                     data, getattr(self, '_dispatch', None)\r
129                 )\r
130         except: # This should only happen if the module is buggy\r
131             # internal error, report as HTTP server error\r
132             self.send_response(500)\r
133             self.end_headers()\r
134         else:\r
135             # got a valid XML RPC response\r
136             self.send_response(200)\r
137             self.send_header("Content-type", "text/xml")\r
138             self.send_header("Content-length", str(len(response)))\r
139             self.end_headers()\r
140             self.wfile.write(response)\r
141 \r
142             # shut down the connection\r
143             self.wfile.flush()\r
144             self.connection.shutdown() # Modified here!\r
145
146 # GeniServer
147 #
148 # Class for a general purpose geni server.
149 #
150 # Implements an HTTPS XML-RPC server. Generally it is expected that GENI
151 # functions will take a credential string, which is passed to
152 # decode_authentication. Decode_authentication() will verify the validity of
153 # the credential, and verify that the user is using the key that matches the
154 # GID supplied in the credential.
155
156 class GeniServer():
157     def __init__(self, ip, port, key_file, cert_file):
158         self.key = Keypair(filename = key_file)
159         self.cert = Certificate(filename = cert_file)
160         self.server = SecureXMLRPCServer((ip, port), SecureXMLRpcRequestHandler, key_file, cert_file)
161         self.register_functions()
162
163     def decode_authentication(self, cred_string):
164         self.client_cred = Credential(string = cred_string)
165         self.client_gid = self.client_cred.get_gid_caller()
166         self.object_gid = self.client_cred.get_gid_object()
167
168         # make sure the client_gid is not blank
169         if not self.client_gid:
170             raise MissingCallerGID(self.client_cred.get_subject())
171
172         # make sure the client_gid matches the certificate that the client is using
173         peer_cert = self.server.peer_cert
174         if not peer_cert.is_pubkey(self.client_gid.get_pubkey()):
175             raise ConnectionKeyGIDMismatch(self.client_gid.get_subject())
176
177     # register_functions override this to add more functions
178     def register_functions(self):
179         self.server.register_function(self.noop)
180
181     def noop(self, cred, anything):
182         self.decode_authentication(cred)
183
184         return anything
185
186     def run(self):
187         self.server.serve_forever()
188
189