catch syntax/logic issue correctly -> thanks to greg
[certmaster.git] / certmaster / SSLConnection.py
1 # Higher-level SSL objects used by rpclib
2 #
3 # Copyright (c) 2002 Red Hat, Inc.
4 #
5 # Author: Mihai Ibanescu <misa@redhat.com>
6 # Modifications by Dan Williams <dcbw@redhat.com>
7
8
9 from OpenSSL import SSL
10 import time, socket, select
11 from CommonErrors import canIgnoreSSLError
12
13
14 class SSLConnection:
15 """
16 This whole class exists just to filter out a parameter
17 passed in to the shutdown() method in SimpleXMLRPC.doPOST()
18 """
19
20 DEFAULT_TIMEOUT = 20
21
22 def __init__(self, conn):
23 """
24 Connection is not yet a new-style class,
25 so I'm making a proxy instead of subclassing.
26 """
27 self.__dict__["conn"] = conn
28 self.__dict__["close_refcount"] = 0
29 self.__dict__["closed"] = False
30 self.__dict__["timeout"] = self.DEFAULT_TIMEOUT
31
32 def __del__(self):
33 self.__dict__["conn"].close()
34
35 def __getattr__(self,name):
36 return getattr(self.__dict__["conn"], name)
37
38 def __setattr__(self,name, value):
39 setattr(self.__dict__["conn"], name, value)
40
41 def settimeout(self, timeout):
42 if timeout == None:
43 self.__dict__["timeout"] = self.DEFAULT_TIMEOUT
44 else:
45 self.__dict__["timeout"] = timeout
46 self.__dict__["conn"].settimeout(timeout)
47
48 def shutdown(self, how=1):
49 """
50 SimpleXMLRpcServer.doPOST calls shutdown(1),
51 and Connection.shutdown() doesn't take
52 an argument. So we just discard the argument.
53 """
54 self.__dict__["conn"].shutdown()
55
56 def accept(self):
57 """
58 This is the other part of the shutdown() workaround.
59 Since servers create new sockets, we have to infect
60 them with our magic. :)
61 """
62 c, a = self.__dict__["conn"].accept()
63 return (SSLConnection(c), a)
64
65 def makefile(self, mode, bufsize):
66 """
67 We need to use socket._fileobject Because SSL.Connection
68 doesn't have a 'dup'. Not exactly sure WHY this is, but
69 this is backed up by comments in socket.py and SSL/connection.c
70
71 Since httplib.HTTPSResponse/HTTPConnection depend on the
72 socket being duplicated when they close it, we refcount the
73 socket object and don't actually close until its count is 0.
74 """
75 self.__dict__["close_refcount"] = self.__dict__["close_refcount"] + 1
76 return PlgFileObject(self, mode, bufsize)
77
78 def close(self):
79 if self.__dict__["closed"]:
80 return
81 self.__dict__["close_refcount"] = self.__dict__["close_refcount"] - 1
82 if self.__dict__["close_refcount"] == 0:
83 self.shutdown()
84 self.__dict__["conn"].close()
85 self.__dict__["closed"] = True
86
87 def sendall(self, data, flags=0):
88 """
89 - Use select() to simulate a socket timeout without setting the socket
90 to non-blocking mode.
91 - Don't use pyOpenSSL's sendall() either, since it just loops on WantRead
92 or WantWrite, consuming 100% CPU, and never times out.
93 """
94 timeout = self.__dict__["timeout"]
95 con = self.__dict__["conn"]
96 (read, write, excpt) = select.select([], [con], [], timeout)
97 if not con in write:
98 raise socket.timeout((110, "Operation timed out."))
99
100 if hasattr(data, 'tobytes'):
101 data = data.tobytes()
102
103 starttime = time.time()
104 origlen = len(data)
105 sent = -1
106 while len(data):
107 curtime = time.time()
108 if curtime - starttime > timeout:
109 raise socket.timeout((110, "Operation timed out."))
110
111 try:
112 sent = con.send(data, flags)
113 except SSL.SysCallError, e:
114 if e[0] == 32: # Broken Pipe
115 self.close()
116 sent = 0
117 else:
118 raise socket.error(e)
119 except (SSL.WantWriteError, SSL.WantReadError):
120 time.sleep(0.2)
121 continue
122
123 data = data[sent:]
124 return origlen - len(data)
125
126 def recv(self, bufsize, flags=0):
127 """
128 Use select() to simulate a socket timeout without setting the socket
129 to non-blocking mode
130 """
131 timeout = self.__dict__["timeout"]
132 con = self.__dict__["conn"]
133 (read, write, excpt) = select.select([con], [], [], timeout)
134 if not con in read:
135 raise socket.timeout((110, "Operation timed out."))
136
137 starttime = time.time()
138 while True:
139 curtime = time.time()
140 if curtime - starttime > timeout:
141 raise socket.timeout((110, "Operation timed out."))
142
143 try:
144 return con.recv(bufsize, flags)
145 except SSL.ZeroReturnError:
146 return None
147 except SSL.WantReadError:
148 time.sleep(0.2)
149 except Exception, e:
150 if canIgnoreSSLError(e):
151 return None
152 else:
153 raise e
154 return None
155
156
157 class PlgFileObject(socket._fileobject):
158 def close(self):
159 """
160 socket._fileobject doesn't actually _close_ the socket,
161 which we want it to do, so we have to override.
162 """
163 try:
164 if self._sock:
165 self.flush()
166 self._sock.close()
167 finally:
168 self._sock = None