# Higher-level SSL objects used by rpclib # # Copyright (c) 2002 Red Hat, Inc. # # Author: Mihai Ibanescu # Modifications by Dan Williams from OpenSSL import SSL import time, socket, select from CommonErrors import canIgnoreSSLError class SSLConnection: """ This whole class exists just to filter out a parameter passed in to the shutdown() method in SimpleXMLRPC.doPOST() """ DEFAULT_TIMEOUT = 20 def __init__(self, conn): """ Connection is not yet a new-style class, so I'm making a proxy instead of subclassing. """ self.__dict__["conn"] = conn self.__dict__["close_refcount"] = 0 self.__dict__["closed"] = False self.__dict__["timeout"] = self.DEFAULT_TIMEOUT def __del__(self): self.__dict__["conn"].close() def __getattr__(self,name): return getattr(self.__dict__["conn"], name) def __setattr__(self,name, value): setattr(self.__dict__["conn"], name, value) def settimeout(self, timeout): if timeout == None: self.__dict__["timeout"] = self.DEFAULT_TIMEOUT else: self.__dict__["timeout"] = timeout self.__dict__["conn"].settimeout(timeout) def shutdown(self, how=1): """ SimpleXMLRpcServer.doPOST calls shutdown(1), and Connection.shutdown() doesn't take an argument. So we just discard the argument. """ self.__dict__["conn"].shutdown() def accept(self): """ This is the other part of the shutdown() workaround. Since servers create new sockets, we have to infect them with our magic. :) """ c, a = self.__dict__["conn"].accept() return (SSLConnection(c), a) def makefile(self, mode, bufsize): """ We need to use socket._fileobject Because SSL.Connection doesn't have a 'dup'. Not exactly sure WHY this is, but this is backed up by comments in socket.py and SSL/connection.c Since httplib.HTTPSResponse/HTTPConnection depend on the socket being duplicated when they close it, we refcount the socket object and don't actually close until its count is 0. """ self.__dict__["close_refcount"] = self.__dict__["close_refcount"] + 1 return PlgFileObject(self, mode, bufsize) def close(self): if self.__dict__["closed"]: return self.__dict__["close_refcount"] = self.__dict__["close_refcount"] - 1 if self.__dict__["close_refcount"] == 0: self.shutdown() self.__dict__["conn"].close() self.__dict__["closed"] = True def sendall(self, data, flags=0): """ - Use select() to simulate a socket timeout without setting the socket to non-blocking mode. - Don't use pyOpenSSL's sendall() either, since it just loops on WantRead or WantWrite, consuming 100% CPU, and never times out. """ timeout = self.__dict__["timeout"] con = self.__dict__["conn"] (read, write, excpt) = select.select([], [con], [], timeout) if not con in write: raise socket.timeout((110, "Operation timed out.")) starttime = time.time() origlen = len(data) sent = -1 while len(data): curtime = time.time() if curtime - starttime > timeout: raise socket.timeout((110, "Operation timed out.")) try: sent = con.send(data, flags) except SSL.SysCallError, e: if e[0] == 32: # Broken Pipe self.close() sent = 0 else: raise socket.error(e) except (SSL.WantWriteError, SSL.WantReadError): time.sleep(0.2) continue data = data[sent:] return origlen - len(data) def recv(self, bufsize, flags=0): """ Use select() to simulate a socket timeout without setting the socket to non-blocking mode """ timeout = self.__dict__["timeout"] con = self.__dict__["conn"] (read, write, excpt) = select.select([con], [], [], timeout) if not con in read: raise socket.timeout((110, "Operation timed out.")) starttime = time.time() while True: curtime = time.time() if curtime - starttime > timeout: raise socket.timeout((110, "Operation timed out.")) try: return con.recv(bufsize, flags) except SSL.ZeroReturnError: return None except SSL.WantReadError: time.sleep(0.2) except Exception, e: if canIgnoreSSLError(e): return None else: raise e return None class PlgFileObject(socket._fileobject): def close(self): """ socket._fileobject doesn't actually _close_ the socket, which we want it to do, so we have to override. """ try: if self._sock: self.flush() self._sock.close() finally: self._sock = None