Removing GeniClient
[sfa.git] / sfa / util / server.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 SfaAPI 
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 = SfaAPI(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             remote_addr = (remote_ip, remote_port) = self.connection.getpeername()
162             self.api.remote_addr = remote_addr
163             response = self.api.handle(remote_addr, request)
164
165         
166         except Exception, fault:
167             raise
168             # This should only happen if the module is buggy
169             # internal error, report as HTTP server error
170             self.send_response(500)
171             self.end_headers()
172         else:
173             # got a valid XML RPC response
174             self.send_response(200)
175             self.send_header("Content-type", "text/xml")
176             self.send_header("Content-length", str(len(response)))
177             self.end_headers()
178             self.wfile.write(response)
179
180             # shut down the connection
181             self.wfile.flush()
182             self.connection.shutdown() # Modified here!
183
184 ##
185 # Implements an HTTPS XML-RPC server. Generally it is expected that SFA
186 # functions will take a credential string, which is passed to
187 # decode_authentication. Decode_authentication() will verify the validity of
188 # the credential, and verify that the user is using the key that matches the
189 # GID supplied in the credential.
190
191 class SfaServer(threading.Thread):
192
193     ##
194     # Create a new SfaServer object.
195     #
196     # @param ip the ip address to listen on
197     # @param port the port to listen on
198     # @param key_file private key filename of registry
199     # @param cert_file certificate filename containing public key 
200     #   (could be a GID file)
201
202     def __init__(self, ip, port, key_file, cert_file):
203         threading.Thread.__init__(self)
204         self.key = Keypair(filename = key_file)
205         self.cert = Certificate(filename = cert_file)
206         self.server = SecureXMLRPCServer((ip, port), SecureXMLRpcRequestHandler, key_file, cert_file)
207         self.trusted_cert_list = None
208         self.register_functions()
209
210
211     ##
212     # Register functions that will be served by the XMLRPC server. This
213     # function should be overrided by each descendant class.
214
215     def register_functions(self):
216         self.server.register_function(self.noop)
217
218     ##
219     # Sample no-op server function. The no-op function decodes the credential
220     # that was passed to it.
221
222     def noop(self, cred, anything):
223         self.decode_authentication(cred)
224
225         return anything
226
227     ##
228     # Execute the server, serving requests forever. 
229
230     def run(self):
231         self.server.serve_forever()
232
233