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