root / test / functionnal / lib / xmlrpclibssl.py @ 720a8d4eb21aff8147e5a529cee1e336cf5ad539

View | Annotate | Download (9.8 KB)

1
# This Python file uses the following encoding: utf-8
2
3
# Copyright (c) 2009 Lost Oasis, IELO <info@ielo.net>
4
#
5
# This file is part of ZINN.
6
# 
7
# ZINN is free software: you can redistribute it and/or modify it under
8
# the terms of the GNU General Public License as published by the Free
9
# Software Foundation, either version 3 of the License, or (at your
10
# option) any later version.
11
# 
12
# ZINN is distributed in the hope that it will be useful, but WITHOUT
13
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15
# for more details.
16
# 
17
# You should have received a copy of the GNU General Public License
18
# along with ZINN.  If not, see <http://www.gnu.org/licenses/>.
19
20
import OpenSSL
21
from OpenSSL import SSL
22
from OpenSSL import crypto
23
import time, socket, select
24
import httplib, urllib, xmlrpclib, os, sys
25
26
27
class SSLConnection:
28
    """
29
    This whole class exists just to filter out a parameter
30
    passed in to the shutdown() method in SimpleXMLRPC.doPOST()
31
    """
32
33
    DEFAULT_TIMEOUT = 5
34
35
    def __init__(self, conn):
36
        """
37
        Connection is not yet a new-style class,
38
        so I'm making a proxy instead of subclassing.
39
        """
40
        self.__dict__["conn"] = conn
41
        self.__dict__["close_refcount"] = 1
42
        self.__dict__["closed"] = False
43
        self.__dict__["timeout"] = self.DEFAULT_TIMEOUT
44
45
    def __del__(self):
46
        self.__dict__["conn"].close()
47
48
    def __getattr__(self,name):
49
        return getattr(self.__dict__["conn"], name)
50
51
    def __setattr__(self,name, value):
52
        setattr(self.__dict__["conn"], name, value)
53
54
    def settimeout(self, timeout):
55
        if timeout == None:
56
            self.__dict__["timeout"] = self.DEFAULT_TIMEOUT
57
        else:
58
            self.__dict__["timeout"] = timeout
59
        self.__dict__["conn"].settimeout(timeout)
60
61
    def shutdown(self, how=1):
62
        """
63
        SimpleXMLRpcServer.doPOST calls shutdown(1),
64
        and Connection.shutdown() doesn't take
65
        an argument. So we just discard the argument.
66
        """
67
        self.__dict__["conn"].shutdown()
68
69
    def accept(self):
70
        """
71
        This is the other part of the shutdown() workaround.
72
        Since servers create new sockets, we have to infect
73
        them with our magic. :)
74
        """
75
        c, a = self.__dict__["conn"].accept()
76
        return (SSLConnection(c), a)
77
78
    def makefile(self, mode, bufsize):
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
        Since httplib.HTTPSResponse/HTTPConnection depend on the
85
        socket being duplicated when they close it, we refcount the
86
        socket object and don't actually close until its count is 0.
87
        """
88
        self.__dict__["close_refcount"] = self.__dict__["close_refcount"] + 1
89
        return PlgFileObject(self, mode, bufsize)
90
91
    def close(self):
92
        if self.__dict__["closed"]:
93
            return
94
        self.__dict__["close_refcount"] = self.__dict__["close_refcount"] - 1
95
        if self.__dict__["close_refcount"] == 0:
96
            self.shutdown()
97
            self.__dict__["conn"].close()
98
            self.__dict__["closed"] = True
99
100
    def sendall(self, data, flags=0):
101
        """
102
        - Use select() to simulate a socket timeout without setting the socket
103
            to non-blocking mode.
104
        - Don't use pyOpenSSL's sendall() either, since it just loops on WantRead
105
            or WantWrite, consuming 100% CPU, and never times out.
106
        """
107
        timeout = self.__dict__["timeout"]
108
        con = self.__dict__["conn"]
109
        (read, write, excpt) = select.select([], [con], [], timeout)
110
        if not con in write:
111
            raise socket.timeout((110, "Operation timed out."))
112
113
        starttime = time.time()
114
        origlen = len(data)
115
        sent = -1
116
        while len(data):
117
            curtime = time.time()
118
            if curtime - starttime > timeout:
119
                raise socket.timeout((110, "Operation timed out."))
120
121
            try:
122
                sent = con.send(data, flags)
123
            except SSL.SysCallError, e:
124
                if e[0] == 32:      # Broken Pipe
125
                    self.close()
126
                    sent = 0
127
                else:
128
                    raise socket.error(e)
129
            except (SSL.WantWriteError, SSL.WantReadError):
130
                time.sleep(0.2)
131
                continue
132
133
            data = data[sent:]
134
        return origlen - len(data)
135
136
    def recv(self, bufsize, flags=0):
137
        """
138
        Use select() to simulate a socket timeout without setting the socket
139
        to non-blocking mode
140
        """
141
        timeout = self.__dict__["timeout"]
142
        con = self.__dict__["conn"]
143
        (read, write, excpt) = select.select([con], [], [], timeout)
144
        if not con in read:
145
            raise socket.timeout((110, "Operation timed out."))
146
147
        starttime = time.time()
148
        while True:
149
            curtime = time.time()
150
            if curtime - starttime > timeout:
151
                raise socket.timeout((110, "Operation timed out."))
152
153
            try:
154
                return con.recv(bufsize, flags)
155
            except SSL.ZeroReturnError:
156
                return None
157
            except SSL.WantReadError:
158
                time.sleep(0.2)
159
            except Exception, e:
160
                    raise e
161
        return None
162
163
164
class PlgFileObject(socket._fileobject):
165
    def close(self):
166
        """
167
        socket._fileobject doesn't actually _close_ the socket,
168
        which we want it to do, so we have to override.
169
        """
170
        try:
171
            if self._sock:
172
                self.flush()
173
                self._sock.close()
174
        finally:
175
            self._sock = None
176
177
class SSL_Transport(xmlrpclib.Transport):
178
179
    user_agent = "pyOpenSSL_XMLRPC/%s - %s" % ('0.1',
180
    xmlrpclib.Transport.user_agent)
181
182
    def __init__(self, ssl_context, timeout=None, use_datetime=0):
183
        if sys.version_info[:3] >= (2, 5, 0):
184
            xmlrpclib.Transport.__init__(self, use_datetime)
185
        self.ssl_ctx=ssl_context
186
        self._timeout = timeout
187
188
    def make_connection(self, host):
189
        # Handle username and password.
190
        try:
191
            host, extra_headers, x509 = self.get_host_info(host)
192
        except AttributeError:
193
            # Yay for Python 2.2
194
            pass
195
        _host, _port = urllib.splitport(host)
196
        return HTTPS(_host, int(_port), ssl_context=self.ssl_ctx, timeout=self._timeout)
197
198
def our_verify(connection, x509, errNum, errDepth, preverifyOK):
199
    # print "Verify: errNum = %s, errDepth = %s, preverifyOK = %s" % (errNum,
200
    # errDepth, preverifyOK)
201
202
    # preverifyOK should tell us whether or not the client's certificate
203
    # correctly authenticates against the CA chain
204
    return preverifyOK
205
206
207
def CreateSSLContext(pkey, cert, ca_cert, passphrase=None):
208
    for f in pkey, cert, ca_cert:
209
        if f and not os.access(f, os.R_OK):
210
            print "%s does not exist or is not readable." % f
211
            os._exit(1)
212
213
    try:
214
        #ctx = SSL.Context(SSL.TLSv1_METHOD)   # SSLv3 only
215
        ctx = SSL.Context(SSL.SSLv3_METHOD) 
216
        ctx.use_certificate_file(cert)
217
218
        if isinstance(passphrase, str):
219
            try:
220
                key = crypto.load_privatekey(crypto.FILETYPE_PEM,
221
                                                 open(pkey).read(), passphrase)
222
                ctx.use_privatekey(key)
223
            except Exception, e:
224
                raise xmlrpclib.Fault(-32999, e)
225
        else:
226
            ctx.use_privatekey_file(pkey)
227
228
        ctx.load_client_ca(ca_cert)
229
        ctx.load_verify_locations(ca_cert)
230
        verify = SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT
231
        ctx.set_verify(verify, our_verify)
232
        ctx.set_verify_depth(10)
233
        #ctx.set_options(SSL.OP_NO_SSLv2 | SSL.OP_NO_TLSv1)
234
    except OpenSSL.SSL.Error, e:
235
        for i in e[0]:
236
            if i[-1] == 'bad decrypt':
237
                raise xmlrpclib.Fault(-32999, 'Bad password or password decrypt')
238
        else:
239
            raise xmlrpclib.Fault(-32999, e)
240
    return ctx
241
242
243
class HTTPSConnection(httplib.HTTPConnection):
244
    "This class allows communication via SSL."
245
246
    response_class = httplib.HTTPResponse
247
248
    def __init__(self, host, port=None, ssl_context=None, strict=None, timeout=None):
249
        httplib.HTTPConnection.__init__(self, host, port, strict)
250
        self.ssl_ctx = ssl_context
251
        self._timeout = timeout
252
253
    def connect(self):
254
        try:
255
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
256
            con = SSL.Connection(self.ssl_ctx, sock)
257
            self.sock = SSLConnection(con)
258
            if sys.version_info[:3] >= (2, 3, 0):
259
                self.sock.settimeout(self._timeout)
260
            self.sock.connect((self.host, self.port))
261
        except socket.error, (value,message):
262
            raise
263
264
class HTTPS(httplib.HTTP):
265
    """Compatibility with 1.5 httplib interface
266
267
    Python 1.5.2 did not have an HTTPS class, but it defined an
268
    interface for sending http requests that is also useful for
269
    https.
270
    """
271
272
    _connection_class = HTTPSConnection
273
274
    def __init__(self, host='', port=None, ssl_context=None, strict=None, timeout=None):
275
        self._setup(self._connection_class(host, port, ssl_context, strict, timeout))
276
277
278
class SSLXMLRPCServerProxy(xmlrpclib.ServerProxy):
279
    def __init__(self, uri, pkey_file, cert_file, ca_cert_file, timeout=None,
280
                                                              passphrase=None):
281
        self.ctx = CreateSSLContext(pkey_file, cert_file, ca_cert_file,
282
                                                         passphrase=passphrase)
283
        xmlrpclib.ServerProxy.__init__(self, uri,
284
                SSL_Transport(ssl_context=self.ctx, timeout=timeout), allow_none=True)
285
286
# vim: tabstop=4 expandtab shiftwidth=4 textwidth=79