Welcome to mirror list, hosted at ThFree Co, Russian Federation.

dev.gajim.org/gajim/gajim.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjunglecow <junglecow@no-mail.com>2006-11-13 17:24:41 +0300
committerjunglecow <junglecow@no-mail.com>2006-11-13 17:24:41 +0300
commitc5ffc7bf5db448bb211c015a20ab763f72e098cb (patch)
tree5940939319f433aee3385c2b924d26fa97cd0f42
parent0f53cbb3f6d00ad5cc5c3460929b283d0531921d (diff)
- Added support for PyOpenSSL (with fallback to old library if not found)
- Also added a wrapper for cleaner compatibility handling of old SSL library - Changed exception handling to be more precise. (Catching Exception. Ish.) - Added lots of debug printing You will need to install pyopenssl (Debian unstable: python-pyopenssl) to try this.
-rw-r--r--src/common/xmpp/transports_nb.py321
1 files changed, 299 insertions, 22 deletions
diff --git a/src/common/xmpp/transports_nb.py b/src/common/xmpp/transports_nb.py
index ad3456e6a..edab59ea5 100644
--- a/src/common/xmpp/transports_nb.py
+++ b/src/common/xmpp/transports_nb.py
@@ -25,6 +25,33 @@ import sys
import os
import errno
+import traceback
+import thread
+
+import logging
+h = logging.StreamHandler()
+f = logging.Formatter('%(asctime)s %(name)s: %(levelname)s: %(message)s')
+h.setFormatter(f)
+log = logging.getLogger('Gajim.transports')
+log.addHandler(h)
+log.setLevel(logging.DEBUG)
+log.propagate = False
+del h, f
+
+USE_PYOPENSSL = False
+
+try:
+ #raise ImportError("Manually disabled PyOpenSSL")
+ import OpenSSL.SSL
+ import OpenSSL.crypto
+ USE_PYOPENSSL = True
+ print "PyOpenSSL loaded."
+except ImportError:
+ # FIXME: Remove these prints before release, replace with a warning dialog.
+ print "=" * 79
+ print "PyOpenSSL not found, falling back to Python builtin SSL objects (insecure)."
+ print "=" * 79
+
# timeout to connect to the server socket, it doesn't include auth
CONNECT_TIMEOUT_SECONDS = 30
@@ -33,7 +60,120 @@ DISCONNECT_TIMEOUT_SECONDS = 10
# size of the buffer which reads data from server
# if lower, more stanzas will be fragmented and processed twice
-RECV_BUFSIZE = 1048576
+RECV_BUFSIZE = 32768 # 2x maximum size of ssl packet, should be plenty
+#RECV_BUFSIZE = 16 # FIXME: (#2634) gajim breaks with this setting: it's inefficient but should work.
+
+def torf(cond, tv, fv):
+ if cond: return tv
+ return fv
+
+class SSLWrapper:
+ def __init__(this, sslobj):
+ this.sslobj = sslobj
+ print "init called with", sslobj
+
+ # We can return None out of this function to signal that no data is
+ # available right now. Better than an exception, which differs
+ # depending on which SSL lib we're using. Unfortunately returning ''
+ # can indicate that the socket has been closed, so to be sure, we avoid
+ # this.
+
+ def recv(this, data, flags=None):
+ raise NotImplementedException()
+
+ def send(this, data, flags=None):
+ raise NotImplementedException()
+
+class PyOpenSSLWrapper(SSLWrapper):
+ '''Wrapper class for PyOpenSSL's recv() and send() methods'''
+ parent = SSLWrapper
+
+ def __init__(this, *args):
+ this.parent.__init__(this, *args)
+
+ def is_numtoolarge(this, e):
+ t = ('asn1 encoding routines', 'a2d_ASN1_OBJECT', 'first num too large')
+ return isinstance(e.args, (list, tuple)) and len(e.args) == 1 and \
+ isinstance(e.args[0], (list, tuple)) and len(e.args[0]) == 2 and \
+ e.args[0][0] == e.args[0][1] == t
+
+ def recv(this, bufsize, flags=None):
+ retval = None
+ try:
+ #print "recv: thread id:", thread.get_ident()
+ if flags is None: retval = this.sslobj.recv(bufsize)
+ else: retval = this.sslobj.recv(bufsize, flags)
+ except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError), e:
+ log.debug("Recv: " + repr(e))
+ except OpenSSL.SSL.Error, e:
+ "Recv: Caught OpenSSL.SSL.Error:"
+ traceback.print_exc()
+ print "Current Stack:"
+ traceback.print_stack()
+ if this.is_numtoolarge(e):
+ # print an error but ignore this exception
+ log.warning("Recv: OpenSSL: asn1enc: first num too large (eaten)")
+ else:
+ raise
+
+ return retval
+
+ def send(this, data, flags=None):
+ try:
+ #print "send: thread id:", thread.get_ident()
+ if flags is None: return this.sslobj.send(data)
+ else: return this.sslobj.send(data, flags)
+ except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError), e:
+ log.debug("Send: " + repr(e))
+ except OpenSSL.SSL.Error, e:
+ print "Send: Caught OpenSSL.SSL.Error:"
+ traceback.print_exc()
+ print "Current Stack:"
+ traceback.print_stack()
+ if this.is_numtoolarge(e):
+ # warn, but ignore this exception
+ log.warning("Send: OpenSSL: asn1enc: first num too large (ignoring)")
+ else:
+ raise
+ return 0
+
+class StdlibSSLWrapper(SSLWrapper):
+ '''Wrapper class for Python's socket.ssl read() and write() methods'''
+ parent = SSLWrapper
+
+ def __init__(this, *args):
+ this.parent.__init__(this, *args)
+
+ def recv(this, bufsize, flags=None):
+ # we simply ignore flags since ssl object doesn't support it
+ retval = None
+ try:
+ retval = this.sslobj.read(bufsize)
+ except socket.sslerror, e:
+ print "Got socket.sslerror"
+ print e, e.args
+ traceback.print_exc()
+ print "Current Stack:"
+ traceback.print_stack()
+ if e.args[0] not in (socket.SSL_ERROR_WANT_READ, socket.SSL_ERROR_WANT_WRITE):
+ raise
+ if retval is None: return ''
+ if retval == '': raise SSLWrapper.SocketShutdown('Connection Closed')
+ return retval
+
+ def send(this, data, flags=None):
+ # we simply ignore flags since ssl object doesn't support it
+ try:
+ return this.sslobj.write(data)
+ except socket.sslerror, e:
+ print "Got socket.sslerror"
+ print e, e.args
+ traceback.print_exc()
+ print "Current Stack:"
+ traceback.print_stack()
+ if e.args[0] not in (socket.SSL_ERROR_WANT_READ, socket.SSL_ERROR_WANT_WRITE):
+ raise
+ return 0
class NonBlockingTcp(PlugIn, IdleObject):
''' This class can be used instead of transports.Tcp in threadless implementations '''
@@ -109,6 +249,7 @@ class NonBlockingTcp(PlugIn, IdleObject):
self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self._sock.setblocking(False)
except:
+ traceback.print_exc()
sys.exc_clear()
if self.on_connect_failure:
self.on_connect_failure()
@@ -161,6 +302,7 @@ class NonBlockingTcp(PlugIn, IdleObject):
self._sock.shutdown(socket.SHUT_RDWR)
self._sock.close()
except:
+ traceback.print_exc()
# socket is already closed
sys.exc_clear()
# socket descriptor cannot be (un)plugged anymore
@@ -204,34 +346,56 @@ class NonBlockingTcp(PlugIn, IdleObject):
def _do_receive(self):
''' Reads all pending incoming data. Calls owner's disconnected() method if appropriate.'''
- received = ''
+ ERR_DISCONN = -2 # Misc error signifying that we got disconnected
+ ERR_OTHER = -1 # Other error
+ received = None
errnum = 0
+ errtxt = 'No Error Set'
try:
# get as many bites, as possible, but not more than RECV_BUFSIZE
received = self._recv(RECV_BUFSIZE)
- except Exception, e:
- if len(e.args) > 0 and isinstance(e.args[0], int):
- errnum = e[0]
- sys.exc_clear()
- # "received" will be empty anyhow
- if errnum == socket.SSL_ERROR_WANT_READ:
- pass
- elif errnum in [errno.ECONNRESET, errno.ENOTCONN, errno.ESHUTDOWN]:
+ except (socket.error, socket.herror, socket.gaierror), e:
+ print "Got socket exception"
+ traceback.print_exc()
+ print "Current Stack:"
+ traceback.print_stack()
+ errnum = e[0]
+ errtxt = str(errnum) + ':' + e[1]
+ except socket.sslerror, e:
+ print "Got unknown socket.sslerror"
+ traceback.print_exc()
+ print "Current Stack:"
+ traceback.print_stack()
+ errnum = ERR_OTHER
+ errtxt = repr("socket.sslerror: " + e.args)
+
+ # Should we really do this? In C, recv() will happily return 0
+ # in nonblocking mode when there is no data waiting, and in
+ # some cases select() will mark the socket for reading when
+ # there is nothing to read, and the socket is still open. For
+ # example, this can happen when the remote sends a zero-length
+ # tcp packet.
+ if received is '':
+ errnum = ERR_DISCONN
+ errtxt = "Connection closed unexpectedly"
+
+ if errnum in (ERR_DISCONN, errno.ECONNRESET, errno.ENOTCONN, errno.ESHUTDOWN):
+ self.DEBUG(errtxt, 'error')
self.pollend()
# don't proccess result, cas it will raise error
return
- elif not received :
- if errnum != socket.SSL_ERROR_EOF:
- # 8 EOF occurred in violation of protocol
- self.DEBUG('Socket error while receiving data', 'error')
- self.pollend()
- if self.state >= 0:
- self.disconnect()
- return
-
+
+ if received is None:
+ if errnum != 0:
+ self.DEBUG(errtxt, 'error')
+ if self.state >= 0:
+ self.disconnect()
+ return
+ received = ''
+
if self.state < 0:
return
-
+
# we have received some bites, stop the timeout!
self.renew_send_timeout()
if self.on_receive:
@@ -268,6 +432,7 @@ class NonBlockingTcp(PlugIn, IdleObject):
self._plug_idle()
self._on_send()
except socket.error, e:
+ traceback.print_exc()
sys.exc_clear()
if e[0] == socket.SSL_ERROR_WANT_WRITE:
return True
@@ -287,6 +452,7 @@ class NonBlockingTcp(PlugIn, IdleObject):
try:
self._sock.connect(self._server)
except socket.error, e:
+ traceback.print_exc()
errnum = e[0]
sys.exc_clear()
# in progress, or would block
@@ -361,6 +527,14 @@ class NonBlockingTcp(PlugIn, IdleObject):
class NonBlockingTLS(PlugIn):
''' TLS connection used to encrypts already estabilished tcp connection.'''
+
+ # from ssl.h (partial extract)
+ ssl_h_bits = { "SSL_ST_CONNECT": 0x1000, "SSL_ST_ACCEPT": 0x2000,
+ "SSL_CB_LOOP": 0x01, "SSL_CB_EXIT": 0x02,
+ "SSL_CB_READ": 0x04, "SSL_CB_WRITE": 0x08,
+ "SSL_CB_ALERT": 0x4000,
+ "SSL_CB_HANDSHAKE_START": 0x10, "SSL_CB_HANDSHAKE_DONE": 0x20}
+
def PlugIn(self, owner, now=0, on_tls_start = None):
''' If the 'now' argument is true then starts using encryption immidiatedly.
If 'now' in false then starts encryption as soon as TLS feature is
@@ -375,6 +549,7 @@ class NonBlockingTLS(PlugIn):
try:
res = self._startSSL()
except Exception, e:
+ traceback.print_exc()
self._owner.socket.pollend()
return
self.tls_start()
@@ -416,18 +591,118 @@ class NonBlockingTLS(PlugIn):
self.tls_start()
raise NodeProcessed
+ def _dumpX509(this, cert):
+ print "Digest (SHA-1):", cert.digest("sha1")
+ print "Digest (MD5):", cert.digest("md5")
+ print "Serial #:", cert.get_serial_number()
+ print "Version:", cert.get_version()
+ print "Expired:", torf(cert.has_expired(), "Yes", "No")
+ print "Subject:"
+ this._dumpX509Name(cert.get_subject())
+ print "Issuer:"
+ this._dumpX509Name(cert.get_issuer())
+ this._dumpPKey(cert.get_pubkey())
+
+ def _dumpX509Name(this, name):
+ print "X509Name:", str(name)
+
+ def _dumpPKey(this, pkey):
+ typedict = {OpenSSL.crypto.TYPE_RSA: "RSA", OpenSSL.crypto.TYPE_DSA: "DSA"}
+ print "PKey bits:", pkey.bits()
+ print "PKey type: %s (%d)" % (typedict.get(pkey.type(), "Unknown"), pkey.type())
+
def _startSSL(self):
''' Immidiatedly switch socket to TLS mode. Used internally.'''
+ print "_startSSL called"
+ if USE_PYOPENSSL: return self._startSSL_pyOpenSSL()
+ return self._startSSL_stdlib()
+
+ def _startSSL_pyOpenSSL(self):
+ print "_startSSL_pyOpenSSL called, thread id:", thread.get_ident()
+ tcpsock = self._owner.Connection
+ # FIXME: should method be configurable?
+ tcpsock._sslContext = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_METHOD)
+ tcpsock._sslContext.set_info_callback(self._ssl_info_callback)
+ tcpsock._sslObj = OpenSSL.SSL.Connection(tcpsock._sslContext, tcpsock._sock)
+ tcpsock._sslObj.set_connect_state() # set to client mode
+
+ wrapper = PyOpenSSLWrapper(tcpsock._sslObj)
+ tcpsock._recv = wrapper.recv
+ tcpsock._send = wrapper.send
+
+ print "Initiating handshake..."
+ #tcpsock._sslObj.setblocking(True)
+ try:
+ self.starttls='in progress'
+ tcpsock._sslObj.do_handshake()
+ except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError), e:
+ log.debug("do_handshake: " + str(e))
+ #tcpsock._sslObj.setblocking(False)
+ #print "Done handshake"
+ print "Async handshake started..."
+
+ def _on_ssl_handshake_done(self):
+ print "Handshake done!"
+ self.starttls='success'
+
+ tcpsock = self._owner.Connection
+ cert = tcpsock._sslObj.get_peer_certificate()
+ peer = cert.get_subject()
+ issuer = cert.get_issuer()
+ tcpsock._sslIssuer = unicode(issuer)
+ tcpsock._sslServer = unicode(peer)
+
+ # FIXME: remove debug prints
+ peercert = tcpsock._sslObj.get_peer_certificate()
+ ciphers = tcpsock._sslObj.get_cipher_list()
+
+ print "Ciphers:", ciphers
+ print "Peer cert:", peercert
+ self._dumpX509(peercert)
+
+ print OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, peercert)
+
+ def _startSSL_stdlib(self):
+ print "_startSSL_stdlib called"
tcpsock=self._owner.Connection
tcpsock._sock.setblocking(True)
tcpsock._sslObj = socket.ssl(tcpsock._sock, None, None)
tcpsock._sock.setblocking(False)
tcpsock._sslIssuer = tcpsock._sslObj.issuer()
tcpsock._sslServer = tcpsock._sslObj.server()
- tcpsock._recv = tcpsock._sslObj.read
- tcpsock._send = tcpsock._sslObj.write
+ wrapper = StdlibSSLWrapper(tcpsock._sslObj)
+ tcpsock._recv = wrapper.recv
+ tcpsock._send = wrapper.send
self.starttls='success'
+ def _ssl_info_callback(this, sslconn, type, st):
+ # Exceptions can't propagate up through this callback, so print them here.
+ try: this._ssl_info_callback_guarded(sslconn, type, st)
+ except: traceback.print_exc()
+
+ def _ssl_info_callback_guarded(this, sslconn, type, st):
+ b = this.ssl_h_bits
+
+ #if type & b['SSL_CB_LOOP']:
+ # if type & SSL_ST_CONNECT: tls_state = "connect"
+ # elif type & SSL_ST_ACCEPT: tls_state = "accept"
+ # else: tls_state = "undefined"
+ # print "tls_state: %s: %s" % (tls_state, sslconn.state_string())
+
+ #if type & b['SSL_CB_ALERT']:
+ # if type & SSL_CB_READ: rdwr = "read"
+ # elif type & SSL_CB_WRITE: rdwr = "write"
+ # else: rdwr = "unknown"
+ # print "tls_alert: %s:%d: %s" % (rdwr, st, sslconn.state_string())
+
+ #mask = ""
+ #for k, v in b.iteritems():
+ # if type & v: mask += " " + k
+ #print "mask:", mask, st
+
+ if type & b['SSL_CB_HANDSHAKE_DONE']:
+ this._on_ssl_handshake_done()
+
def StartTLSHandler(self, conn, starttls):
''' Handle server reply if TLS is allowed to process. Behaves accordingly.
Used internally.'''
@@ -441,6 +716,7 @@ class NonBlockingTLS(PlugIn):
try:
self._startSSL()
except Exception, e:
+ traceback.print_exc()
self._owner.socket.pollend()
return
self._owner.Dispatcher.PlugOut()
@@ -499,6 +775,7 @@ class NBHTTPPROXYsocket(NonBlockingTcp):
try:
proto, code, desc = reply.split('\n')[0].split(' ', 2)
except:
+ traceback.print_exc()
raise error('Invalid proxy reply')
if code <> '200':
self.DEBUG('Invalid proxy reply: %s %s %s' % (proto, code, desc),'error')