Jean-Paul Calderone | 897bc25 | 2008-02-18 20:50:23 -0500 | [diff] [blame] | 1 | """ |
| 2 | SecureXMLRPCServer module using pyOpenSSL 0.5 |
| 3 | Written 0907.2002 |
| 4 | by Michal Wallace |
| 5 | http://www.sabren.net/ |
| 6 | |
| 7 | This acts exactly like SimpleXMLRPCServer |
| 8 | from the standard python library, but |
| 9 | uses secure connections. The technique |
| 10 | and classes should work for any SocketServer |
| 11 | style server. However, the code has not |
| 12 | been extensively tested. |
| 13 | |
| 14 | This code is in the public domain. |
| 15 | It is provided AS-IS WITH NO WARRANTY WHATSOEVER. |
| 16 | """ |
| 17 | import SocketServer |
| 18 | import os, socket |
| 19 | import SimpleXMLRPCServer |
| 20 | from OpenSSL import SSL |
| 21 | |
| 22 | class SSLWrapper: |
| 23 | """ |
| 24 | This whole class exists just to filter out a parameter |
| 25 | passed in to the shutdown() method in SimpleXMLRPC.doPOST() |
| 26 | """ |
| 27 | def __init__(self, conn): |
| 28 | """ |
| 29 | Connection is not yet a new-style class, |
| 30 | so I'm making a proxy instead of subclassing. |
| 31 | """ |
| 32 | self.__dict__["conn"] = conn |
| 33 | def __getattr__(self,name): |
| 34 | return getattr(self.__dict__["conn"], name) |
| 35 | def __setattr__(self,name, value): |
| 36 | setattr(self.__dict__["conn"], name, value) |
| 37 | def shutdown(self, how=1): |
| 38 | """ |
| 39 | SimpleXMLRpcServer.doPOST calls shutdown(1), |
| 40 | and Connection.shutdown() doesn't take |
| 41 | an argument. So we just discard the argument. |
| 42 | """ |
| 43 | self.__dict__["conn"].shutdown() |
| 44 | def accept(self): |
| 45 | """ |
| 46 | This is the other part of the shutdown() workaround. |
| 47 | Since servers create new sockets, we have to infect |
| 48 | them with our magic. :) |
| 49 | """ |
| 50 | c, a = self.__dict__["conn"].accept() |
| 51 | return (SSLWrapper(c), a) |
| 52 | |
| 53 | |
| 54 | |
| 55 | class SecureTCPServer(SocketServer.TCPServer): |
| 56 | """ |
| 57 | Just like TCPServer, but use a socket. |
| 58 | This really ought to let you specify the key and certificate files. |
| 59 | """ |
| 60 | def __init__(self, server_address, RequestHandlerClass): |
| 61 | SocketServer.BaseServer.__init__(self, server_address, RequestHandlerClass) |
| 62 | |
| 63 | ## Same as normal, but make it secure: |
| 64 | ctx = SSL.Context(SSL.SSLv23_METHOD) |
| 65 | ctx.set_options(SSL.OP_NO_SSLv2) |
| 66 | |
| 67 | dir = os.curdir |
| 68 | ctx.use_privatekey_file (os.path.join(dir, 'server.pkey')) |
| 69 | ctx.use_certificate_file(os.path.join(dir, 'server.cert')) |
| 70 | |
| 71 | self.socket = SSLWrapper(SSL.Connection(ctx, socket.socket(self.address_family, |
| 72 | self.socket_type))) |
| 73 | self.server_bind() |
| 74 | self.server_activate() |
| 75 | |
| 76 | |
| 77 | class SecureXMLRPCRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler): |
| 78 | def setup(self): |
| 79 | """ |
| 80 | We need to use socket._fileobject Because SSL.Connection |
| 81 | doesn't have a 'dup'. Not exactly sure WHY this is, but |
| 82 | this is backed up by comments in socket.py and SSL/connection.c |
| 83 | """ |
| 84 | self.connection = self.request # for doPOST |
| 85 | self.rfile = socket._fileobject(self.request, "rb", self.rbufsize) |
| 86 | self.wfile = socket._fileobject(self.request, "wb", self.wbufsize) |
| 87 | |
| 88 | |
| 89 | class SecureXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer, SecureTCPServer): |
| 90 | def __init__(self, addr, |
| 91 | requestHandler=SecureXMLRPCRequestHandler, |
| 92 | logRequests=1): |
| 93 | """ |
| 94 | This is the exact same code as SimpleXMLRPCServer.__init__ |
| 95 | except it calls SecureTCPServer.__init__ instead of plain |
| 96 | old TCPServer.__init__ |
| 97 | """ |
| 98 | self.funcs = {} |
| 99 | self.logRequests = logRequests |
| 100 | self.instance = None |
| 101 | SecureTCPServer.__init__(self, addr, requestHandler) |
| 102 | |