Merge branch 'master' of ssh://git.planet-lab.org/git/sfa
[sfa.git] / sfa / util / componentserver.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 import SocketServer
17 import BaseHTTPServer
18 import SimpleHTTPServer
19 import SimpleXMLRPCServer
20 from OpenSSL import SSL
21 from sfa.trust.certificate import Keypair, Certificate
22 from sfa.trust.credential import *
23 from sfa.util.faults import *
24 from sfa.plc.api import ComponentAPI 
25 from sfa.util.server import verify_callback, ThreadedServer 
26 from sfa.util.debug import log
27
28
29 ##
30 # taken from the web (XXX find reference). Implents HTTPS xmlrpc request handler
31
32 class SecureXMLRpcRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
33     """Secure XML-RPC request handler class.
34
35     It it very similar to SimpleXMLRPCRequestHandler but it uses HTTPS for transporting XML data.
36     """
37     def setup(self):
38         self.connection = self.request
39         self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
40         self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
41
42     def do_POST(self):
43         """Handles the HTTPS POST request.
44
45         It was copied out from SimpleXMLRPCServer.py and modified to shutdown the socket cleanly.
46         """
47         try:
48             peer_cert = Certificate()
49             peer_cert.load_from_pyopenssl_x509(self.connection.get_peer_certificate())
50             self.api = ComponentAPI(peer_cert = peer_cert, 
51                            interface = self.server.interface, 
52                            key_file = self.server.key_file, 
53                            cert_file = self.server.cert_file)
54             # get arguments
55             request = self.rfile.read(int(self.headers["content-length"]))
56             # In previous versions of SimpleXMLRPCServer, _dispatch
57             # could be overridden in this class, instead of in
58             # SimpleXMLRPCDispatcher. To maintain backwards compatibility,
59             # check to see if a subclass implements _dispatch and dispatch
60             # using that method if present.
61             #response = self.server._marshaled_dispatch(request, getattr(self, '_dispatch', None))
62             # XX TODO: Need to get the real remote address
63             remote_addr = (remote_ip, remote_port) = self.connection.getpeername()
64             self.api.remote_addr = remote_addr
65             #remote_addr = (self.rfile.connection.remote_ip, remote_port)
66             #self.api.remote_addr = remote_addr
67             response = self.api.handle(remote_addr, request)
68
69         
70         except Exception, fault:
71             raise
72             # This should only happen if the module is buggy
73             # internal error, report as HTTP server error
74             self.send_response(500)
75             self.end_headers()
76             traceback.print_exc()
77         else:
78             # got a valid XML RPC response
79             self.send_response(200)
80             self.send_header("Content-type", "text/xml")
81             self.send_header("Content-length", str(len(response)))
82             self.end_headers()
83             self.wfile.write(response)
84
85             # shut down the connection
86             self.wfile.flush()
87             self.connection.shutdown() # Modified here!
88
89 ##
90 # Implements an HTTPS XML-RPC server. Generally it is expected that SFA
91 # functions will take a credential string, which is passed to
92 # decode_authentication. Decode_authentication() will verify the validity of
93 # the credential, and verify that the user is using the key that matches the
94 # GID supplied in the credential.
95
96 class ComponentServer(threading.Thread):
97
98     ##
99     # Create a new SfaServer object.
100     #
101     # @param ip the ip address to listen on
102     # @param port the port to listen on
103     # @param key_file private key filename of registry
104     # @param cert_file certificate filename containing public key 
105     #   (could be a GID file)
106
107     def __init__(self, ip, port, key_file, cert_file, api=None):
108         threading.Thread.__init__(self)
109         self.key = Keypair(filename = key_file)
110         self.cert = Certificate(filename = cert_file)
111         self.server = ThreadedServer((ip, port), SecureXMLRpcRequestHandler, key_file, cert_file)
112         self.trusted_cert_list = None
113         self.register_functions()
114
115
116     ##
117     # Register functions that will be served by the XMLRPC server. This
118     # function should be overrided by each descendant class.
119
120     def register_functions(self):
121         self.server.register_function(self.noop)
122
123     ##
124     # Sample no-op server function. The no-op function decodes the credential
125     # that was passed to it.
126
127     def noop(self, cred, anything):
128         self.decode_authentication(cred)
129
130         return anything
131
132     ##
133     # Execute the server, serving requests forever. 
134
135     def run(self):
136         self.server.serve_forever()
137
138