#!/usr/bin/python
#
# Higher-level SSL objects used by rpclib
#
# Copyright (c) 2002 Red Hat, Inc.
#
# Author: Mihai Ibanescu <misa@redhat.com>
# Modifications by Dan Williams <dcbw@redhat.com>


from OpenSSL import SSL, crypto
import os, string, time, socket, select


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
    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 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):
        while True:
            # Use select() to simulate a socket timeout without setting the socket
            # to non-blocking mode
            (read, write, error) = select.select([], [self.__dict__["conn"]], [], self.DEFAULT_TIMEOUT)
            if self.__dict__["conn"] in write:
                starttime = time.time()
                while True:
                    try:
                        return self.__dict__["conn"].sendall(data, flags)
                    except SSL.SysCallError, e:
                        if e[0] == 32:      # Broken Pipe
                            self.close()
                        else:
                            raise socket.error(e)
                    except SSL.WantWriteError:
                        time.sleep(0.1)

                    curtime = time.time()
                    if curtime - starttime > self.DEFAULT_TIMEOUT:
                        raise socket.timeout
            else:
                raise socket.timeout
            if self.__dict__["conn"] in error:
                raise socket.error
        return None
    def recv(self, bufsize, flags=0):
        while True:
            # Use select() to simulate a socket timeout without setting the socket
            # to non-blocking mode
            (read, write, error) = select.select([self.__dict__["conn"]], [], [], self.DEFAULT_TIMEOUT)
            if self.__dict__["conn"] in read:
                starttime = time.time()
                while True:
                    try:
                        return self.__dict__["conn"].recv(bufsize, flags)
                    except SSL.ZeroReturnError:
                        return None
                    except SSL.WantReadError:
                        time.sleep(0.1)

                    curtime = time.time()
                    if curtime - starttime > self.DEFAULT_TIMEOUT:
                        raise socket.timeout
            else:
                raise socket.timeout
            if self.__dict__["conn"] in error:
                raise socket.error
        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

