checked in initial versions
[sfa.git] / util / geniserver.py
diff --git a/util/geniserver.py b/util/geniserver.py
new file mode 100644 (file)
index 0000000..6a8eb87
--- /dev/null
@@ -0,0 +1,188 @@
+# geniserver.py
+#
+# geniwrapper server
+#
+# implements a general-purpose server layer for geni. This should be usable on
+# the registry, component, or other interfaces.
+#
+# TODO: investigate ways to combine this with existing PLC server?
+
+import SimpleXMLRPCServer
+
+import SocketServer
+import BaseHTTPServer\r
+import SimpleHTTPServer\r
+import SimpleXMLRPCServer\r
+\r
+from excep import *\r
+from cert import *\r
+from credential import *\r
+\r
+import socket, os\r
+from OpenSSL import SSL\r
+\r
+# verify_callback\r
+#\r
+# verification callback for pyOpenSSL. We do our own checking of keys because\r
+# we have our own authentication spec. Thus we disable several of the normal\r
+# prohibitions that OpenSSL places on certificates\r
+\r
+def verify_callback(conn, x509, err, depth, preverify):\r
+    # if the cert has been preverified, then it is ok\r
+    if preverify:
+       #print "  preverified"
+       return 1
+
+    # we're only passing single certificates, not chains
+    if depth > 0:
+       #print "  depth > 0 in verify_callback"
+       return 0
+
+    # create a Certificate object and load it from the client's x509
+    ctx = conn.get_context()
+    server = ctx.get_app_data()
+    server.peer_cert = Certificate()
+    server.peer_cert.load_from_pyopenssl_x509(x509)
+
+    # the certificate verification done by openssl checks a number of things
+    # that we aren't interested in, so we look out for those error messages
+    # and ignore them
+
+    # allow self-signed certificates
+    if err == 18:
+       #print "  X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT"
+       return 1
+
+    # allow certs that don't have an issuer
+    if err == 20:
+       #print "  X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY"
+       return 1
+
+    # allow certs that are untrusted
+    if err == 21:
+       #print "  X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE"
+       return 1
+
+    # allow certs that are untrusted
+    if err == 27:
+       #print "  X509_V_ERR_CERT_UNTRUSTED"
+       return 1
+
+    print "  error", err, "in verify_callback"
+
+    return 0\r
+\r
+# SecureXMLServer\r
+#\r
+# taken from the web (XXX find reference). Implements an HTTPS xmlrpc server\r
+
+class SecureXMLRPCServer(BaseHTTPServer.HTTPServer,SimpleXMLRPCServer.SimpleXMLRPCDispatcher):\r
+    def __init__(self, server_address, HandlerClass, key_file, cert_file, logRequests=True):\r
+        """Secure XML-RPC server.\r
+\r
+        It it very similar to SimpleXMLRPCServer but it uses HTTPS for transporting XML data.\r
+        """\r
+        self.logRequests = logRequests\r
+\r
+        SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self, None, None)\r
+        SocketServer.BaseServer.__init__(self, server_address, HandlerClass)\r
+        ctx = SSL.Context(SSL.SSLv23_METHOD)\r
+        ctx.use_privatekey_file(key_file)\r
+        ctx.use_certificate_file(cert_file)\r
+        ctx.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT, verify_callback)\r
+        ctx.set_app_data(self)\r
+        self.socket = SSL.Connection(ctx, socket.socket(self.address_family,\r
+                                                        self.socket_type))\r
+        self.server_bind()\r
+        self.server_activate()\r
+\r
+# SecureXMLRpcRequestHandler\r
+#\r
+# taken from the web (XXX find reference). Implents HTTPS xmlrpc request handler\r
+\r
+class SecureXMLRpcRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):\r
+    """Secure XML-RPC request handler class.\r
+\r
+    It it very similar to SimpleXMLRPCRequestHandler but it uses HTTPS for transporting XML data.\r
+    """\r
+    def setup(self):\r
+        self.connection = self.request\r
+        self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)\r
+        self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)\r
+\r
+    def do_POST(self):\r
+        """Handles the HTTPS POST request.\r
+\r
+        It was copied out from SimpleXMLRPCServer.py and modified to shutdown the socket cleanly.\r
+        """\r
+\r
+        try:\r
+            # get arguments\r
+            data = self.rfile.read(int(self.headers["content-length"]))\r
+            # In previous versions of SimpleXMLRPCServer, _dispatch\r
+            # could be overridden in this class, instead of in\r
+            # SimpleXMLRPCDispatcher. To maintain backwards compatibility,\r
+            # check to see if a subclass implements _dispatch and dispatch\r
+            # using that method if present.\r
+            response = self.server._marshaled_dispatch(\r
+                    data, getattr(self, '_dispatch', None)\r
+                )\r
+        except: # This should only happen if the module is buggy\r
+            # internal error, report as HTTP server error\r
+            self.send_response(500)\r
+            self.end_headers()\r
+        else:\r
+            # got a valid XML RPC response\r
+            self.send_response(200)\r
+            self.send_header("Content-type", "text/xml")\r
+            self.send_header("Content-length", str(len(response)))\r
+            self.end_headers()\r
+            self.wfile.write(response)\r
+\r
+            # shut down the connection\r
+            self.wfile.flush()\r
+            self.connection.shutdown() # Modified here!\r
+
+# GeniServer
+#
+# Class for a general purpose geni server.
+#
+# Implements an HTTPS XML-RPC server. Generally it is expected that GENI
+# functions will take a credential string, which is passed to
+# decode_authentication. Decode_authentication() will verify the validity of
+# the credential, and verify that the user is using the key that matches the
+# GID supplied in the credential.
+
+class GeniServer():
+    def __init__(self, ip, port, key_file, cert_file):
+        self.key = Keypair(filename = key_file)
+        self.cert = Certificate(filename = cert_file)
+        self.server = SecureXMLRPCServer((ip, port), SecureXMLRpcRequestHandler, key_file, cert_file)
+        self.register_functions()
+
+    def decode_authentication(self, cred_string):
+        self.client_cred = Credential(string = cred_string)
+        self.client_gid = self.client_cred.get_gid_caller()
+
+        # make sure the client_gid is not blank
+        if not self.client_gid:
+            raise MissingCallerGID(self.client_cred.get_subject())
+
+        # make sure the client_gid matches the certificate that the client is using
+        peer_cert = self.server.peer_cert
+        if not peer_cert.is_pubkey(self.client_gid.get_pubkey()):
+            raise ConnectionKeyGIDMismatch(self.client_gid.get_subject())
+
+    # register_functions override this to add more functions
+    def register_functions(self):
+        self.server.register_function(self.noop)
+
+    def noop(self, cred, anything):
+        self.decode_authentication(cred)
+
+        return anything
+
+    def run(self):
+        self.server.serve_forever()
+
+