2 # This module implements a general-purpose server layer for geni.
3 # The same basic server should be usable on the registry, component, or
6 # TODO: investigate ways to combine this with existing PLC server?
9 import SimpleXMLRPCServer
16 import SimpleHTTPServer
17 import SimpleXMLRPCServer
20 # SOAP support is optional
23 from SOAPpy.Parser import parseSOAPRPC
24 from SOAPpy.Types import faultType
25 from SOAPpy.NS import NS
26 from SOAPpy.SOAPBuilder import buildSOAP
31 from credential import *
34 from OpenSSL import SSL
37 # See "2.2 Characters" in the XML specification:
39 # #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
41 # [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF]
43 invalid_xml_ascii = map(chr, range(0x0, 0x8) + [0xB, 0xC] + range(0xE, 0x1F))
44 xml_escape_table = string.maketrans("".join(invalid_xml_ascii), "?" * len(invalid_xml_ascii))
46 def xmlrpclib_escape(s, replace = string.replace):
48 xmlrpclib does not handle invalid 7-bit control characters. This
49 function augments xmlrpclib.escape, which by default only replaces
50 '&', '<', and '>' with entities.
53 # This is the standard xmlrpclib.escape function
54 s = replace(s, "&", "&")
55 s = replace(s, "<", "<")
56 s = replace(s, ">", ">",)
58 # Replace invalid 7-bit control characters with '?'
59 return s.translate(xml_escape_table)
61 def xmlrpclib_dump(self, value, write):
63 xmlrpclib cannot marshal instances of subclasses of built-in
64 types. This function overrides xmlrpclib.Marshaller.__dump so that
65 any value that is an instance of one of its acceptable types is
66 marshalled as that type.
68 xmlrpclib also cannot handle invalid 7-bit control characters. See
72 # Use our escape function
73 args = [self, value, write]
74 if isinstance(value, (str, unicode)):
75 args.append(xmlrpclib_escape)
78 # Try for an exact match first
79 f = self.dispatch[type(value)]
81 # Try for an isinstance() match
82 for Type, f in self.dispatch.iteritems():
83 if isinstance(value, Type):
86 raise TypeError, "cannot marshal %s objects" % type(value)
90 xmlrpclib.Marshaller._Marshaller__dump = xmlrpclib_dump
93 # Verification callback for pyOpenSSL. We do our own checking of keys because
94 # we have our own authentication spec. Thus we disable several of the normal
95 # prohibitions that OpenSSL places on certificates
97 def verify_callback(conn, x509, err, depth, preverify):
98 # if the cert has been preverified, then it is ok
100 #print " preverified"
103 # we're only passing single certificates, not chains
105 #print " depth > 0 in verify_callback"
108 # create a Certificate object and load it from the client's x509
109 ctx = conn.get_context()
110 server = ctx.get_app_data()
111 server.peer_cert = Certificate()
112 server.peer_cert.load_from_pyopenssl_x509(x509)
114 # the certificate verification done by openssl checks a number of things
115 # that we aren't interested in, so we look out for those error messages
118 # XXX SMBAKER: I don't know what this error is, but it's being returned
121 #print " X509_V_ERR_CERT_NOT_YET_VALID"
124 # allow self-signed certificates
126 #print " X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT"
129 # allow certs that don't have an issuer
131 #print " X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY"
134 # allow certs that are untrusted
136 #print " X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE"
139 # allow certs that are untrusted
141 #print " X509_V_ERR_CERT_UNTRUSTED"
144 print " error", err, "in verify_callback"
149 # Taken from the web (XXX find reference). Implements an HTTPS xmlrpc server
151 class SecureXMLRPCServer(BaseHTTPServer.HTTPServer,SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
152 def __init__(self, server_address, HandlerClass, key_file, cert_file, logRequests=True):
153 """Secure XML-RPC server.
155 It it very similar to SimpleXMLRPCServer but it uses HTTPS for transporting XML data.
157 self.logRequests = logRequests
159 SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self, True, None)
160 SocketServer.BaseServer.__init__(self, server_address, HandlerClass)
161 ctx = SSL.Context(SSL.SSLv23_METHOD)
162 ctx.use_privatekey_file(key_file)
163 ctx.use_certificate_file(cert_file)
164 ctx.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT, verify_callback)
165 ctx.set_app_data(self)
166 self.socket = SSL.Connection(ctx, socket.socket(self.address_family,
169 self.server_activate()
173 # Convert an exception on the server to a full stack trace and send it to
176 def _dispatch(self, method, params):
178 return SimpleXMLRPCServer.SimpleXMLRPCDispatcher._dispatch(self, method, params)
180 # can't use format_exc() as it is not available in jython yet
182 type, value, tb = sys.exc_info()
183 raise xmlrpclib.Fault(1,''.join(traceback.format_exception(type, value, tb)))
186 # taken from the web (XXX find reference). Implents HTTPS xmlrpc request handler
188 class SecureXMLRpcRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
189 """Secure XML-RPC request handler class.
191 It it very similar to SimpleXMLRPCRequestHandler but it uses HTTPS for transporting XML data.
194 self.encoding = "utf-8"
195 self.connection = self.request
196 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
197 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
199 def handleXMLRPCSOAP(self, source, data):
201 Handle an XML-RPC or SOAP request from the specified source.
204 # Parse request into method name and arguments
206 interface = xmlrpclib
207 (args, method) = xmlrpclib.loads(data)
208 methodresponse = True
210 if SOAPpy is not None:
212 (r, header, body, attrs) = parseSOAPRPC(data, header = 1, body = 1, attrs = 1)
215 # XXX Support named arguments
220 result = self.server._marshaled_dispatch(data, getattr(self, '_dispatch', None))
221 except Exception, fault:
222 # Handle expected faults
223 if interface == xmlrpclib:
225 methodresponse = None
226 elif interface == SOAPpy:
227 result = faultParameter(NS.ENV_T + ":Server", "Method Failed", method)
228 result._setDetail("Fault: %s" % (fault))
232 if interface == xmlrpclib:
233 # XX Shouldnt we be dumping xmlrpc here
235 #data = xmlrpclib.dumps(result, methodresponse = True, encoding = self.encoding, allow_none = 1)
237 elif interface == SOAPpy:
238 data = buildSOAP(kw = {'%sResponse' % method: {'Result': result}}, encoding = self.encoding)
243 """Handles the HTTPS POST request.
245 It was copied out from SimpleXMLRPCServer.py and modified to shutdown the socket cleanly.
250 request = self.rfile.read(int(self.headers["content-length"]))
251 response = self.handleXMLRPCSOAP(None, request)
253 # In previous versions of SimpleXMLRPCServer, _dispatch
254 # could be overridden in this class, instead of in
255 # SimpleXMLRPCDispatcher. To maintain backwards compatibility,
256 # check to see if a subclass implements _dispatch and dispatch
257 # using that method if present.
258 #response = self.server._marshaled_dispatch(request, getattr(self, '_dispatch', None))
259 except: # This should only happen if the module is buggy
260 # internal error, report as HTTP server error
261 self.send_response(500)
265 # got a valid XML RPC response
266 self.send_response(200)
267 self.send_header("Content-type", "text/xml")
268 self.send_header("Content-length", str(len(response)))
270 self.wfile.write(response)
272 # shut down the connection
274 self.connection.shutdown() # Modified here!
277 # Implements an HTTPS XML-RPC server. Generally it is expected that GENI
278 # functions will take a credential string, which is passed to
279 # decode_authentication. Decode_authentication() will verify the validity of
280 # the credential, and verify that the user is using the key that matches the
281 # GID supplied in the credential.
283 class GeniServer(threading.Thread):
286 # Create a new GeniServer object.
288 # @param ip the ip address to listen on
289 # @param port the port to listen on
290 # @param key_file private key filename of registry
291 # @param cert_file certificate filename containing public key
292 # (could be a GID file)
294 def __init__(self, ip, port, key_file, cert_file):
295 threading.Thread.__init__(self)
296 self.key = Keypair(filename = key_file)
297 self.cert = Certificate(filename = cert_file)
298 self.server = SecureXMLRPCServer((ip, port), SecureXMLRpcRequestHandler, key_file, cert_file)
299 self.trusted_cert_list = None
300 self.register_functions()
303 # Decode the credential string that was submitted by the caller. Several
304 # checks are performed to ensure that the credential is valid, and that the
305 # callerGID included in the credential matches the caller that is
306 # connected to the HTTPS connection.
308 def decode_authentication(self, cred_string, operation):
309 self.client_cred = Credential(string = cred_string)
310 self.client_gid = self.client_cred.get_gid_caller()
311 self.object_gid = self.client_cred.get_gid_object()
313 # make sure the client_gid is not blank
314 if not self.client_gid:
315 raise MissingCallerGID(self.client_cred.get_subject())
317 # make sure the client_gid matches client's certificate
318 peer_cert = self.server.peer_cert
319 if not peer_cert.is_pubkey(self.client_gid.get_pubkey()):
320 raise ConnectionKeyGIDMismatch(self.client_gid.get_subject())
322 # make sure the client is allowed to perform the operation
324 if not self.client_cred.can_perform(operation):
325 raise InsufficientRights(operation)
327 if self.trusted_cert_list:
328 self.client_cred.verify_chain(self.trusted_cert_list)
330 self.client_gid.verify_chain(self.trusted_cert_list)
332 self.object_gid.verify_chain(self.trusted_cert_list)
335 # Register functions that will be served by the XMLRPC server. This
336 # function should be overrided by each descendant class.
338 def register_functions(self):
339 self.server.register_function(self.noop)
342 # Sample no-op server function. The no-op function decodes the credential
343 # that was passed to it.
345 def noop(self, cred, anything):
346 self.decode_authentication(cred)
351 # Execute the server, serving requests forever.
354 self.server.serve_forever()