Author: tomk
Date: 2008-07-01 01:02:12 +0200 (Tue, 01 Jul 2008)
New Revision: 9870
Added:
branches/bosh_support/src/common/xmpp/tls_nb.py
Modified:
branches/bosh_support/src/common/xmpp/client.py
branches/bosh_support/src/common/xmpp/client_nb.py
branches/bosh_support/src/common/xmpp/transports_nb.py
Log:
moved TLS and SSL classes from transports_nb to new tls_nb module, fixed HTTP CONNECT proxy transport
Modified: branches/bosh_support/src/common/xmpp/client.py
===================================================================
--- branches/bosh_support/src/common/xmpp/client.py 2008-06-30 22:23:07 UTC (rev 9869)
+++ branches/bosh_support/src/common/xmpp/client.py 2008-06-30 23:02:12 UTC (rev 9870)
@@ -70,7 +70,9 @@
self._old_owners_methods.append(owner.__dict__[method.__name__])
owner.__dict__[method.__name__]=method
owner.__dict__[self.__class__.__name__]=self
- if self.__class__.__dict__.has_key('plugin'): return self.plugin(owner)
+ # following will not work for classes inheriting plugin()
+ #if self.__class__.__dict__.has_key('plugin'): return self.plugin(owner)
+ if hasattr(self,'plugin'): return self.plugin(owner)
def PlugOut(self):
""" Unregister all our staff from main instance and detach from it. """
Modified: branches/bosh_support/src/common/xmpp/client_nb.py
===================================================================
--- branches/bosh_support/src/common/xmpp/client_nb.py 2008-06-30 22:23:07 UTC (rev 9869)
+++ branches/bosh_support/src/common/xmpp/client_nb.py 2008-06-30 23:02:12 UTC (rev 9870)
@@ -25,7 +25,7 @@
import debug
import random
-import transports_nb, dispatcher_nb, auth_nb, roster_nb, protocol
+import transports_nb, tls_nb, dispatcher_nb, auth_nb, roster_nb, protocol
from client import *
import logging
@@ -184,10 +184,11 @@
bosh_uri = proxy['host'],
bosh_port = tcp_port)
else:
- self.socket = transports_nb.NBHTTPProxySocket(
- on_disconnect=self.on_disconnect,
- proxy_creds=(None, None),
- xmpp_server=(self.Server, self.Port))
+ # HTTP CONNECT to proxy from environment variables
+ self.socket = transports_nb.NBHTTPProxySocket(
+ on_disconnect=self.on_disconnect,
+ proxy_creds=(None, None),
+ xmpp_server=(self.Server, self.Port))
else:
self._on_tcp_failure = self._on_connect_failure
tcp_server=self.Server
@@ -432,7 +433,7 @@
self._on_connect()
return
# otherwise start TLS
- transports_nb.NonBlockingTLS().PlugIn(
+ tls_nb.NonBlockingTLS().PlugIn(
self,
on_tls_success=lambda: self._xmpp_connect(socket_type='tls'),
on_tls_failure=self._on_connect_failure)
Added: branches/bosh_support/src/common/xmpp/tls_nb.py
===================================================================
--- branches/bosh_support/src/common/xmpp/tls_nb.py (rev 0)
+++ branches/bosh_support/src/common/xmpp/tls_nb.py 2008-06-30 23:02:12 UTC (rev 9870)
@@ -0,0 +1,437 @@
+## tls_nb.py
+## based on transports_nb.py
+##
+## Copyright (C) 2003-2004 Alexey "Snake" Nezhdanov
+## modified by Dimitur Kirov <dkirov(a)gmail.com>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2, or (at your option)
+## any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+
+import socket
+from client import PlugIn
+from protocol import *
+
+import sys
+import os
+import errno
+import time
+
+import traceback
+
+import logging
+
+log = logging.getLogger('gajim.c.x.tls_nb')
+consoleloghandler = logging.StreamHandler()
+consoleloghandler.setLevel(logging.DEBUG)
+consoleloghandler.setFormatter(
+ logging.Formatter('%(levelname)s: %(message)s')
+)
+log.setLevel(logging.DEBUG)
+log.addHandler(consoleloghandler)
+log.propagate = False
+# I don't need to load gajim.py just because of few TLS variables, so I changed
+# %s/common\.gajim\.DATA_DIR/\'\.\.\/data\'/c
+# %s/common\.gajim\.MY_CACERTS/\'\%s\/\.gajim\/cacerts\.pem\' \% os\.environ\[\'HOME\'\]/c
+
+# To change it back do:
+# %s/\'\.\.\/data\'/common\.gajim\.DATA_DIR/c
+# %s/\'%s\/\.gajim\/cacerts\.pem\'\ %\ os\.environ\[\'HOME\'\]/common\.gajim\.MY_CACERTS/c
+# TODO: make the paths configurable - as constructor parameters or sth
+
+# import common.gajim
+
+USE_PYOPENSSL = False
+
+try:
+ #raise ImportError("Manually disabled PyOpenSSL")
+ import OpenSSL.SSL
+ import OpenSSL.crypto
+ USE_PYOPENSSL = True
+ log.info("PyOpenSSL loaded")
+except ImportError:
+ log.debug("Import of PyOpenSSL failed:", exc_info=True)
+
+ # FIXME: Remove these prints before release, replace with a warning dialog.
+ print >> sys.stderr, "=" * 79
+ print >> sys.stderr, "PyOpenSSL not found, falling back to Python builtin SSL objects (insecure)."
+ print >> sys.stderr, "=" * 79
+
+
+def torf(cond, tv, fv):
+ if cond: return tv
+ return fv
+
+def gattr(obj, attr, default=None):
+ try:
+ return getattr(obj, attr)
+ except:
+ return default
+
+class SSLWrapper:
+ class Error(IOError):
+ def __init__(self, sock=None, exc=None, errno=None, strerror=None, peer=None):
+ self.parent = IOError
+
+ errno = errno or gattr(exc, 'errno')
+ strerror = strerror or gattr(exc, 'strerror') or gattr(exc, 'args')
+ if not isinstance(strerror, basestring): strerror = repr(strerror)
+
+ self.sock = sock
+ self.exc = exc
+ self.peer = peer
+ self.exc_name = None
+ self.exc_args = None
+ self.exc_str = None
+ self.exc_repr = None
+
+ if self.exc is not None:
+ self.exc_name = str(self.exc.__class__)
+ self.exc_args = gattr(self.exc, 'args')
+ self.exc_str = str(self.exc)
+ self.exc_repr = repr(self.exc)
+ if not errno:
+ try:
+ if isinstance(exc, OpenSSL.SSL.SysCallError):
+ if self.exc_args[0] > 0:
+ errno = self.exc_args[0]
+ strerror = self.exc_args[1]
+ except: pass
+
+ self.parent.__init__(self, errno, strerror)
+
+ if self.peer is None and sock is not None:
+ try:
+ ppeer = self.sock.getpeername()
+ if len(ppeer) == 2 and isinstance(ppeer[0], basestring) \
+ and isinstance(ppeer[1], int):
+ self.peer = ppeer
+ except: pass
+
+ def __str__(self):
+ s = str(self.__class__)
+ if self.peer: s += " for %s:%d" % self.peer
+ if self.errno is not None: s += ": [Errno: %d]" % self.errno
+ if self.strerror: s += " (%s)" % self.strerror
+ if self.exc_name:
+ s += ", Caused by %s" % self.exc_name
+ if self.exc_str:
+ if self.strerror: s += "(%s)" % self.exc_str
+ else: s += "(%s)" % str(self.exc_args)
+ return s
+
+ def __init__(self, sslobj, sock=None):
+ self.sslobj = sslobj
+ self.sock = sock
+ log.debug("%s.__init__ called with %s", self.__class__, sslobj)
+
+ def recv(self, data, flags=None):
+ """ Receive wrapper for SSL object
+
+ 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 by returning None. """
+
+ raise NotImplementedException()
+
+ def send(self, data, flags=None, now = False):
+ raise NotImplementedException()
+
+class PyOpenSSLWrapper(SSLWrapper):
+ '''Wrapper class for PyOpenSSL's recv() and send() methods'''
+
+ def __init__(self, *args):
+ self.parent = SSLWrapper
+ self.parent.__init__(self, *args)
+
+ def is_numtoolarge(self, 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(self, bufsize, flags=None):
+ retval = None
+ try:
+ if flags is None: retval = self.sslobj.recv(bufsize)
+ else: retval = self.sslobj.recv(bufsize, flags)
+ except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError), e:
+ pass
+ # log.debug("Recv: " + repr(e))
+ except OpenSSL.SSL.SysCallError, e:
+ log.debug("Recv: Got OpenSSL.SSL.SysCallError: " + repr(e), exc_info=True)
+ #traceback.print_exc()
+ raise SSLWrapper.Error(self.sock or self.sslobj, e)
+ except OpenSSL.SSL.Error, e:
+ if self.is_numtoolarge(e):
+ # warn, but ignore this exception
+ log.warning("Recv: OpenSSL: asn1enc: first num too large (ignored)")
+ else:
+ log.debug("Recv: Caught OpenSSL.SSL.Error:", exc_info=True)
+ #traceback.print_exc()
+ #print "Current Stack:"
+ #traceback.print_stack()
+ raise SSLWrapper.Error(self.sock or self.sslobj, e)
+ return retval
+
+ def send(self, data, flags=None, now = False):
+ try:
+ if flags is None: return self.sslobj.send(data)
+ else: return self.sslobj.send(data, flags)
+ except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError), e:
+ #log.debug("Send: " + repr(e))
+ time.sleep(0.1) # prevent 100% CPU usage
+ except OpenSSL.SSL.SysCallError, e:
+ log.error("Send: Got OpenSSL.SSL.SysCallError: " + repr(e), exc_info=True)
+ #traceback.print_exc()
+ raise SSLWrapper.Error(self.sock or self.sslobj, e)
+ except OpenSSL.SSL.Error, e:
+ if self.is_numtoolarge(e):
+ # warn, but ignore this exception
+ log.warning("Send: OpenSSL: asn1enc: first num too large (ignored)")
+ else:
+ log.error("Send: Caught OpenSSL.SSL.Error:", exc_info=True)
+ #traceback.print_exc()
+ #print "Current Stack:"
+ #traceback.print_stack()
+ raise SSLWrapper.Error(self.sock or self.sslobj, e)
+ return 0
+
+class StdlibSSLWrapper(SSLWrapper):
+ '''Wrapper class for Python's socket.ssl read() and write() methods'''
+
+ def __init__(self, *args):
+ self.parent = SSLWrapper
+ self.parent.__init__(self, *args)
+
+ def recv(self, bufsize, flags=None):
+ # we simply ignore flags since ssl object doesn't support it
+ try:
+ return self.sslobj.read(bufsize)
+ except socket.sslerror, e:
+ #log.debug("Recv: Caught socket.sslerror:", exc_info=True)
+ #traceback.print_exc()
+ if e.args[0] not in (socket.SSL_ERROR_WANT_READ, socket.SSL_ERROR_WANT_WRITE):
+ raise SSLWrapper.Error(self.sock or self.sslobj, e)
+ return None
+
+ def send(self, data, flags=None, now = False):
+ # we simply ignore flags since ssl object doesn't support it
+ try:
+ return self.sslobj.write(data)
+ except socket.sslerror, e:
+ #log.debug("Send: Caught socket.sslerror:", exc_info=True)
+ #traceback.print_exc()
+ if e.args[0] not in (socket.SSL_ERROR_WANT_READ, socket.SSL_ERROR_WANT_WRITE):
+ raise SSLWrapper.Error(self.sock or self.sslobj, e)
+ return 0
+
+
+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, on_tls_success, on_tls_failure, now=0):
+ ''' If the 'now' argument is true then starts using encryption immidiatedly.
+ If 'now' in false then starts encryption as soon as TLS feature is
+ declared by the server (if it were already declared - it is ok).
+ '''
+ if owner.__dict__.has_key('NonBlockingTLS'):
+ return # Already enabled.
+ PlugIn.PlugIn(self, owner)
+ DBG_LINE='NonBlockingTLS'
+ self.on_tls_success = on_tls_success
+ self.on_tls_faliure = on_tls_failure
+ if now:
+ try:
+ res = self._startSSL()
+ except Exception, e:
+ log.error("PlugIn: while trying _startSSL():", exc_info=True)
+ #traceback.print_exc()
+ self._owner.socket.pollend()
+ return
+ on_tls_success()
+ return res
+ if self._owner.Dispatcher.Stream.features:
+ try:
+ self.FeaturesHandler(self._owner.Dispatcher, self._owner.Dispatcher.Stream.features)
+ except NodeProcessed:
+ pass
+ else:
+ self._owner.RegisterHandlerOnce('features',self.FeaturesHandler, xmlns=NS_STREAMS)
+ self.starttls = None
+
+ def plugout(self,now=0):
+ ''' Unregisters TLS handler's from owner's dispatcher. Take note that encription
+ can not be stopped once started. You can only break the connection and start over.'''
+ # if dispatcher is not plugged we cannot (un)register handlers
+ if self._owner.__dict__.has_key('Dispatcher'):
+ self._owner.UnregisterHandler('features', self.FeaturesHandler,xmlns=NS_STREAMS)
+ self._owner.Dispatcher.PlugOut()
+ self._owner = None
+
+ def FeaturesHandler(self, conn, feats):
+ ''' Used to analyse server <features/> tag for TLS support.
+ If TLS is supported starts the encryption negotiation. Used internally '''
+ if not feats.getTag('starttls', namespace=NS_TLS):
+ self.DEBUG("TLS unsupported by remote server.", 'warn')
+ self.on_tls_failure("TLS unsupported by remote server.")
+ return
+ self.DEBUG("TLS supported by remote server. Requesting TLS start.", 'ok')
+ self._owner.RegisterHandlerOnce('proceed', self.StartTLSHandler, xmlns=NS_TLS)
+ self._owner.RegisterHandlerOnce('failure', self.StartTLSHandler, xmlns=NS_TLS)
+ self._owner.send('<starttls xmlns="%s"/>' % NS_TLS)
+ raise NodeProcessed
+
+ def _dumpX509(self, cert, stream=sys.stderr):
+ print >> stream, "Digest (SHA-1):", cert.digest("sha1")
+ print >> stream, "Digest (MD5):", cert.digest("md5")
+ print >> stream, "Serial #:", cert.get_serial_number()
+ print >> stream, "Version:", cert.get_version()
+ print >> stream, "Expired:", torf(cert.has_expired(), "Yes", "No")
+ print >> stream, "Subject:"
+ self._dumpX509Name(cert.get_subject(), stream)
+ print >> stream, "Issuer:"
+ self._dumpX509Name(cert.get_issuer(), stream)
+ self._dumpPKey(cert.get_pubkey(), stream)
+
+ def _dumpX509Name(self, name, stream=sys.stderr):
+ print >> stream, "X509Name:", str(name)
+
+ def _dumpPKey(self, pkey, stream=sys.stderr):
+ typedict = {OpenSSL.crypto.TYPE_RSA: "RSA", OpenSSL.crypto.TYPE_DSA: "DSA"}
+ print >> stream, "PKey bits:", pkey.bits()
+ print >> stream, "PKey type: %s (%d)" % (typedict.get(pkey.type(), "Unknown"), pkey.type())
+
+ def _startSSL(self):
+ ''' Immidiatedly switch socket to TLS mode. Used internally.'''
+ log.debug("_startSSL called")
+ if USE_PYOPENSSL: return self._startSSL_pyOpenSSL()
+ return self._startSSL_stdlib()
+
+ def _startSSL_pyOpenSSL(self):
+ #log.debug("_startSSL_pyOpenSSL called, thread id: %s", str(thread.get_ident()))
+ log.debug("_startSSL_pyOpenSSL called")
+ tcpsock = self._owner.Connection
+ # FIXME: should method be configurable?
+ tcpsock._sslContext = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_METHOD)
+ #tcpsock._sslContext = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
+ tcpsock.ssl_errnum = 0
+ tcpsock._sslContext.set_verify(OpenSSL.SSL.VERIFY_PEER, self._ssl_verify_callback)
+ cacerts = os.path.join('../data', 'other', 'cacerts.pem')
+ try:
+ tcpsock._sslContext.load_verify_locations(cacerts)
+ except:
+ log.warning('Unable to load SSL certificats from file %s' % \
+ os.path.abspath(cacerts))
+ # load users certs
+ if os.path.isfile('%s/.gajim/cacerts.pem' % os.environ['HOME']):
+ store = tcpsock._sslContext.get_cert_store()
+ f = open('%s/.gajim/cacerts.pem' % os.environ['HOME'])
+ lines = f.readlines()
+ i = 0
+ begin = -1
+ for line in lines:
+ if 'BEGIN CERTIFICATE' in line:
+ begin = i
+ elif 'END CERTIFICATE' in line and begin > -1:
+ cert = ''.join(lines[begin:i+2])
+ try:
+ X509cert = OpenSSL.crypto.load_certificate(
+ OpenSSL.crypto.FILETYPE_PEM, cert)
+ store.add_cert(X509cert)
+ except OpenSSL.crypto.Error, exception_obj:
+ log.warning('Unable to load a certificate from file %s: %s' %\
+ ('%s/.gajim/cacerts.pem' % os.environ['HOME'], exception_obj.args[0][0][2]))
+ except:
+ log.warning(
+ 'Unknown error while loading certificate from file %s' % \
+ '%s/.gajim/cacerts.pem' % os.environ['HOME'])
+ begin = -1
+ i += 1
+ 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
+
+ log.debug("Initiating handshake...")
+ # FIXME: Figure out why _connect_success is called before the
+ # SSL handshake is completed in STARTTLS mode. See #2838.
+ tcpsock._sslObj.setblocking(True)
+ try:
+ self.starttls='in progress'
+ tcpsock._sslObj.do_handshake()
+ except:
+ log.error('Error while TLS handshake: ', exc_info=True)
+ self.on_tls_failure('Error while TLS Handshake')
+ return
+ tcpsock._sslObj.setblocking(False)
+ log.debug("Synchronous handshake completed")
+ #log.debug("Async handshake started...")
+
+ # fake it, for now
+ self.starttls='success'
+
+ def _startSSL_stdlib(self):
+ log.debug("_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()
+ wrapper = StdlibSSLWrapper(tcpsock._sslObj, tcpsock._sock)
+ tcpsock._recv = wrapper.recv
+ tcpsock._send = wrapper.send
+ self.starttls='success'
+
+ def _ssl_verify_callback(self, sslconn, cert, errnum, depth, ok):
+ # Exceptions can't propagate up through this callback, so print them here.
+ try:
+ self._owner.Connection.ssl_fingerprint_sha1 = cert.digest('sha1')
+ if errnum == 0:
+ return True
+ self._owner.Connection.ssl_errnum = errnum
+ self._owner.Connection.ssl_cert_pem = OpenSSL.crypto.dump_certificate(
+ OpenSSL.crypto.FILETYPE_PEM, cert)
+ return True
+ except:
+ log.error("Exception caught in _ssl_info_callback:", exc_info=True)
+ traceback.print_exc() # Make sure something is printed, even if log is disabled.
+
+ def StartTLSHandler(self, conn, starttls):
+ ''' Handle server reply if TLS is allowed to process. Behaves accordingly.
+ Used internally.'''
+ if starttls.getNamespace() <> NS_TLS:
+ self.on_tls_failure('Unknown namespace: %s' % starttls.getNamespace())
+ return
+ self.starttls = starttls.getName()
+ if self.starttls == 'failure':
+ self.on_tls_failure('TLS <failure> received: %s' % self.starttls)
+ return
+ self.DEBUG('Got starttls proceed response. Switching to TLS/SSL...','ok')
+ try:
+ self._startSSL()
+ except Exception, e:
+ log.error("StartTLSHandler:", exc_info=True)
+ self.on_tls_failure('in StartTLSHandler')
+ #traceback.print_exc()
+ return
+ self._owner.Dispatcher.PlugOut()
+ self.on_tls_success()
Modified: branches/bosh_support/src/common/xmpp/transports_nb.py
===================================================================
--- branches/bosh_support/src/common/xmpp/transports_nb.py 2008-06-30 22:23:07 UTC (rev 9869)
+++ branches/bosh_support/src/common/xmpp/transports_nb.py 2008-06-30 23:02:12 UTC (rev 9870)
@@ -14,8 +14,8 @@
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
-import socket,select,base64,dispatcher_nb
-import struct
+import socket,base64
+
from simplexml import ustr
from client import PlugIn
from idlequeue import IdleObject
@@ -27,7 +27,6 @@
import time
import traceback
-import threading
import logging
log = logging.getLogger('gajim.c.x.transports_nb')
@@ -52,22 +51,7 @@
grouped = re.match(regex, uri).groups()
proto, host, path = grouped[1], grouped[3], grouped[4]
return proto, host, path
-
-
-
-# I don't need to load gajim.py just because of few TLS variables, so I changed
-# %s/common\.gajim\.DATA_DIR/\'\.\.\/data\'/c
-# %s/common\.gajim\.MY_CACERTS/\'\%s\/\.gajim\/cacerts\.pem\' \% os\.environ\[\'HOME\'\]/c
-
-# To change it back do:
-# %s/\'\.\.\/data\'/common\.gajim\.DATA_DIR/c
-# %s/\'%s\/\.gajim\/cacerts\.pem\'\ %\ os\.environ\[\'HOME\'\]/common\.gajim\.MY_CACERTS/c
-# TODO: make the paths configurable - as constructor parameters or sth
-
-
-# import common.gajim
-
# timeout to connect to the server socket, it doesn't include auth
CONNECT_TIMEOUT_SECONDS = 30
@@ -130,8 +114,8 @@
self.set_timeout, self.remove_timeout]
def plugin(self, owner):
- owner.Connection=self
print 'plugin called'
+ owner.Connection=self
self.idlequeue = owner.idlequeue
def plugout(self):
@@ -372,6 +356,7 @@
else:
self.on_receive = None
return
+ log.debug('setting onreceive on %s' % recv_handler)
self.on_receive = recv_handler
@@ -406,485 +391,34 @@
# in case of some other exception
# FIXME: is this needed??
if errnum != 0:
- self.DEBUG(self.DBG, errstr, 'error')
log.error("CConnection to %s lost: %s %s" % (self.server, errnum, errstr))
- if not errors_only and self.state in [CONNECTING, CONNECTED]:
- self.pollend(retry=True)
+ self.disconnect()
return
received = ''
# we have received some bytes, stop the timeout!
self.renew_send_timeout()
+ # pass received data to owner
+ #self.
if self.on_receive:
self._raise_event(DATA_RECEIVED, received)
self._on_receive(received)
else:
- # This should never happen, so we need the debug
+ # This should never happen, so we need the debug. (If there is no handler
+ # on receive spacified, data are passed to Dispatcher.ProcessNonBlocking)
log.error('SOCKET Unhandled data received: %s' % received)
self.disconnect()
def _on_receive(self, data):
# Overriding this method allows modifying received data before it is passed
- # to callback.
+ # to owner's callback.
+ log.debug('About to call on_receive which is %s' % self.on_receive)
self.on_receive(data)
-class NonBlockingHttpBOSH(NonBlockingTcp):
- '''
- Socket wrapper that makes HTTP message out of send data and peels-off
- HTTP headers from incoming messages
- '''
- def __init__(self, bosh_uri, bosh_port, on_disconnect):
- self.bosh_protocol, self.bosh_host, self.bosh_path = self.urisplit(bosh_uri)
- if self.bosh_protocol is None:
- self.bosh_protocol = 'http'
- if self.bosh_path == '':
- bosh_path = '/'
- self.bosh_port = bosh_port
-
- def send(self, raw_data, now=False):
- NonBlockingTcp.send(
- self,
- self.build_http_message(raw_data),
- now)
- def _on_receive(self,data):
- '''Preceeds pass of received data to Client class. Gets rid of HTTP headers
- and checks them.'''
- statusline, headers, httpbody = self.parse_http_message(data)
- if statusline[1] != '200':
- log.error('HTTP Error: %s %s' % (statusline[1], statusline[2]))
- self.disconnect()
- self.on_receive(httpbody)
-
-
- def build_http_message(self, httpbody):
- '''
- Builds bosh http message with given body.
- Values for headers and status line fields are taken from class variables.
- )
- '''
- headers = ['POST %s HTTP/1.1' % self.bosh_path,
- 'Host: %s:%s' % (self.bosh_host, self.bosh_port),
- 'Content-Type: text/xml; charset=utf-8',
- 'Content-Length: %s' % len(str(httpbody)),
- '\r\n']
- headers = '\r\n'.join(headers)
- return('%s%s\r\n' % (headers, httpbody))
-
- def parse_http_message(self, message):
- '''
- splits http message to tuple (
- statusline - list of e.g. ['HTTP/1.1', '200', 'OK'],
- headers - dictionary of headers e.g. {'Content-Length': '604',
- 'Content-Type': 'text/xml; charset=utf-8'},
- httpbody - string with http body
- )
- '''
- message = message.replace('\r','')
- (header, httpbody) = message.split('\n\n')
- header = header.split('\n')
- statusline = header[0].split(' ')
- header = header[1:]
- headers = {}
- for dummy in header:
- row = dummy.split(' ',1)
- headers[row[0][:-1]] = row[1]
- return (statusline, headers, httpbody)
-
-
-USE_PYOPENSSL = False
-
-try:
- #raise ImportError("Manually disabled PyOpenSSL")
- import OpenSSL.SSL
- import OpenSSL.crypto
- USE_PYOPENSSL = True
- log.info("PyOpenSSL loaded")
-except ImportError:
- log.debug("Import of PyOpenSSL failed:", exc_info=True)
-
- # FIXME: Remove these prints before release, replace with a warning dialog.
- print >> sys.stderr, "=" * 79
- print >> sys.stderr, "PyOpenSSL not found, falling back to Python builtin SSL objects (insecure)."
- print >> sys.stderr, "=" * 79
-
-
-def torf(cond, tv, fv):
- if cond: return tv
- return fv
-
-def gattr(obj, attr, default=None):
- try:
- return getattr(obj, attr)
- except:
- return default
-
-class SSLWrapper:
- class Error(IOError):
- def __init__(self, sock=None, exc=None, errno=None, strerror=None, peer=None):
- self.parent = IOError
-
- errno = errno or gattr(exc, 'errno')
- strerror = strerror or gattr(exc, 'strerror') or gattr(exc, 'args')
- if not isinstance(strerror, basestring): strerror = repr(strerror)
-
- self.sock = sock
- self.exc = exc
- self.peer = peer
- self.exc_name = None
- self.exc_args = None
- self.exc_str = None
- self.exc_repr = None
-
- if self.exc is not None:
- self.exc_name = str(self.exc.__class__)
- self.exc_args = gattr(self.exc, 'args')
- self.exc_str = str(self.exc)
- self.exc_repr = repr(self.exc)
- if not errno:
- try:
- if isinstance(exc, OpenSSL.SSL.SysCallError):
- if self.exc_args[0] > 0:
- errno = self.exc_args[0]
- strerror = self.exc_args[1]
- except: pass
-
- self.parent.__init__(self, errno, strerror)
-
- if self.peer is None and sock is not None:
- try:
- ppeer = self.sock.getpeername()
- if len(ppeer) == 2 and isinstance(ppeer[0], basestring) \
- and isinstance(ppeer[1], int):
- self.peer = ppeer
- except: pass
-
- def __str__(self):
- s = str(self.__class__)
- if self.peer: s += " for %s:%d" % self.peer
- if self.errno is not None: s += ": [Errno: %d]" % self.errno
- if self.strerror: s += " (%s)" % self.strerror
- if self.exc_name:
- s += ", Caused by %s" % self.exc_name
- if self.exc_str:
- if self.strerror: s += "(%s)" % self.exc_str
- else: s += "(%s)" % str(self.exc_args)
- return s
-
- def __init__(self, sslobj, sock=None):
- self.sslobj = sslobj
- self.sock = sock
- log.debug("%s.__init__ called with %s", self.__class__, sslobj)
-
- def recv(self, data, flags=None):
- """ Receive wrapper for SSL object
-
- 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 by returning None. """
-
- raise NotImplementedException()
-
- def send(self, data, flags=None, now = False):
- raise NotImplementedException()
-
-class PyOpenSSLWrapper(SSLWrapper):
- '''Wrapper class for PyOpenSSL's recv() and send() methods'''
-
- def __init__(self, *args):
- self.parent = SSLWrapper
- self.parent.__init__(self, *args)
-
- def is_numtoolarge(self, 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(self, bufsize, flags=None):
- retval = None
- try:
- if flags is None: retval = self.sslobj.recv(bufsize)
- else: retval = self.sslobj.recv(bufsize, flags)
- except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError), e:
- pass
- # log.debug("Recv: " + repr(e))
- except OpenSSL.SSL.SysCallError, e:
- log.debug("Recv: Got OpenSSL.SSL.SysCallError: " + repr(e), exc_info=True)
- #traceback.print_exc()
- raise SSLWrapper.Error(self.sock or self.sslobj, e)
- except OpenSSL.SSL.Error, e:
- if self.is_numtoolarge(e):
- # warn, but ignore this exception
- log.warning("Recv: OpenSSL: asn1enc: first num too large (ignored)")
- else:
- log.debug("Recv: Caught OpenSSL.SSL.Error:", exc_info=True)
- #traceback.print_exc()
- #print "Current Stack:"
- #traceback.print_stack()
- raise SSLWrapper.Error(self.sock or self.sslobj, e)
- return retval
-
- def send(self, data, flags=None, now = False):
- try:
- if flags is None: return self.sslobj.send(data)
- else: return self.sslobj.send(data, flags)
- except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError), e:
- #log.debug("Send: " + repr(e))
- time.sleep(0.1) # prevent 100% CPU usage
- except OpenSSL.SSL.SysCallError, e:
- log.error("Send: Got OpenSSL.SSL.SysCallError: " + repr(e), exc_info=True)
- #traceback.print_exc()
- raise SSLWrapper.Error(self.sock or self.sslobj, e)
- except OpenSSL.SSL.Error, e:
- if self.is_numtoolarge(e):
- # warn, but ignore this exception
- log.warning("Send: OpenSSL: asn1enc: first num too large (ignored)")
- else:
- log.error("Send: Caught OpenSSL.SSL.Error:", exc_info=True)
- #traceback.print_exc()
- #print "Current Stack:"
- #traceback.print_stack()
- raise SSLWrapper.Error(self.sock or self.sslobj, e)
- return 0
-
-class StdlibSSLWrapper(SSLWrapper):
- '''Wrapper class for Python's socket.ssl read() and write() methods'''
-
- def __init__(self, *args):
- self.parent = SSLWrapper
- self.parent.__init__(self, *args)
-
- def recv(self, bufsize, flags=None):
- # we simply ignore flags since ssl object doesn't support it
- try:
- return self.sslobj.read(bufsize)
- except socket.sslerror, e:
- #log.debug("Recv: Caught socket.sslerror:", exc_info=True)
- #traceback.print_exc()
- if e.args[0] not in (socket.SSL_ERROR_WANT_READ, socket.SSL_ERROR_WANT_WRITE):
- raise SSLWrapper.Error(self.sock or self.sslobj, e)
- return None
-
- def send(self, data, flags=None, now = False):
- # we simply ignore flags since ssl object doesn't support it
- try:
- return self.sslobj.write(data)
- except socket.sslerror, e:
- #log.debug("Send: Caught socket.sslerror:", exc_info=True)
- #traceback.print_exc()
- if e.args[0] not in (socket.SSL_ERROR_WANT_READ, socket.SSL_ERROR_WANT_WRITE):
- raise SSLWrapper.Error(self.sock or self.sslobj, e)
- return 0
-
-
-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, on_tls_success, on_tls_failure, now=0):
- ''' If the 'now' argument is true then starts using encryption immidiatedly.
- If 'now' in false then starts encryption as soon as TLS feature is
- declared by the server (if it were already declared - it is ok).
- '''
- if owner.__dict__.has_key('NonBlockingTLS'):
- return # Already enabled.
- PlugIn.PlugIn(self, owner)
- DBG_LINE='NonBlockingTLS'
- self.on_tls_success = on_tls_success
- self.on_tls_faliure = on_tls_failure
- if now:
- try:
- res = self._startSSL()
- except Exception, e:
- log.error("PlugIn: while trying _startSSL():", exc_info=True)
- #traceback.print_exc()
- self._owner.socket.pollend()
- return
- on_tls_success()
- return res
- if self._owner.Dispatcher.Stream.features:
- try:
- self.FeaturesHandler(self._owner.Dispatcher, self._owner.Dispatcher.Stream.features)
- except NodeProcessed:
- pass
- else:
- self._owner.RegisterHandlerOnce('features',self.FeaturesHandler, xmlns=NS_STREAMS)
- self.starttls = None
-
- def plugout(self,now=0):
- ''' Unregisters TLS handler's from owner's dispatcher. Take note that encription
- can not be stopped once started. You can only break the connection and start over.'''
- # if dispatcher is not plugged we cannot (un)register handlers
- if self._owner.__dict__.has_key('Dispatcher'):
- self._owner.UnregisterHandler('features', self.FeaturesHandler,xmlns=NS_STREAMS)
- self._owner.Dispatcher.PlugOut()
- self._owner = None
-
- def FeaturesHandler(self, conn, feats):
- ''' Used to analyse server <features/> tag for TLS support.
- If TLS is supported starts the encryption negotiation. Used internally '''
- if not feats.getTag('starttls', namespace=NS_TLS):
- self.DEBUG("TLS unsupported by remote server.", 'warn')
- self.on_tls_failure("TLS unsupported by remote server.")
- return
- self.DEBUG("TLS supported by remote server. Requesting TLS start.", 'ok')
- self._owner.RegisterHandlerOnce('proceed', self.StartTLSHandler, xmlns=NS_TLS)
- self._owner.RegisterHandlerOnce('failure', self.StartTLSHandler, xmlns=NS_TLS)
- self._owner.send('<starttls xmlns="%s"/>' % NS_TLS)
- raise NodeProcessed
-
- def _dumpX509(self, cert, stream=sys.stderr):
- print >> stream, "Digest (SHA-1):", cert.digest("sha1")
- print >> stream, "Digest (MD5):", cert.digest("md5")
- print >> stream, "Serial #:", cert.get_serial_number()
- print >> stream, "Version:", cert.get_version()
- print >> stream, "Expired:", torf(cert.has_expired(), "Yes", "No")
- print >> stream, "Subject:"
- self._dumpX509Name(cert.get_subject(), stream)
- print >> stream, "Issuer:"
- self._dumpX509Name(cert.get_issuer(), stream)
- self._dumpPKey(cert.get_pubkey(), stream)
-
- def _dumpX509Name(self, name, stream=sys.stderr):
- print >> stream, "X509Name:", str(name)
-
- def _dumpPKey(self, pkey, stream=sys.stderr):
- typedict = {OpenSSL.crypto.TYPE_RSA: "RSA", OpenSSL.crypto.TYPE_DSA: "DSA"}
- print >> stream, "PKey bits:", pkey.bits()
- print >> stream, "PKey type: %s (%d)" % (typedict.get(pkey.type(), "Unknown"), pkey.type())
-
- def _startSSL(self):
- ''' Immidiatedly switch socket to TLS mode. Used internally.'''
- log.debug("_startSSL called")
- if USE_PYOPENSSL: return self._startSSL_pyOpenSSL()
- return self._startSSL_stdlib()
-
- def _startSSL_pyOpenSSL(self):
- #log.debug("_startSSL_pyOpenSSL called, thread id: %s", str(thread.get_ident()))
- log.debug("_startSSL_pyOpenSSL called")
- tcpsock = self._owner.Connection
- # FIXME: should method be configurable?
- tcpsock._sslContext = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_METHOD)
- #tcpsock._sslContext = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
- tcpsock.ssl_errnum = 0
- tcpsock._sslContext.set_verify(OpenSSL.SSL.VERIFY_PEER, self._ssl_verify_callback)
- cacerts = os.path.join('../data', 'other', 'cacerts.pem')
- try:
- tcpsock._sslContext.load_verify_locations(cacerts)
- except:
- log.warning('Unable to load SSL certificats from file %s' % \
- os.path.abspath(cacerts))
- # load users certs
- if os.path.isfile('%s/.gajim/cacerts.pem' % os.environ['HOME']):
- store = tcpsock._sslContext.get_cert_store()
- f = open('%s/.gajim/cacerts.pem' % os.environ['HOME'])
- lines = f.readlines()
- i = 0
- begin = -1
- for line in lines:
- if 'BEGIN CERTIFICATE' in line:
- begin = i
- elif 'END CERTIFICATE' in line and begin > -1:
- cert = ''.join(lines[begin:i+2])
- try:
- X509cert = OpenSSL.crypto.load_certificate(
- OpenSSL.crypto.FILETYPE_PEM, cert)
- store.add_cert(X509cert)
- except OpenSSL.crypto.Error, exception_obj:
- log.warning('Unable to load a certificate from file %s: %s' %\
- ('%s/.gajim/cacerts.pem' % os.environ['HOME'], exception_obj.args[0][0][2]))
- except:
- log.warning(
- 'Unknown error while loading certificate from file %s' % \
- '%s/.gajim/cacerts.pem' % os.environ['HOME'])
- begin = -1
- i += 1
- 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
-
- log.debug("Initiating handshake...")
- # FIXME: Figure out why _connect_success is called before the
- # SSL handshake is completed in STARTTLS mode. See #2838.
- tcpsock._sslObj.setblocking(True)
- try:
- self.starttls='in progress'
- tcpsock._sslObj.do_handshake()
- except:
- log.error('Error while TLS handshake: ', exc_info=True)
- self.on_tls_failure('Error while TLS Handshake')
- return
- tcpsock._sslObj.setblocking(False)
- log.debug("Synchronous handshake completed")
- #log.debug("Async handshake started...")
-
- # fake it, for now
- self.starttls='success'
-
- def _startSSL_stdlib(self):
- log.debug("_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()
- wrapper = StdlibSSLWrapper(tcpsock._sslObj, tcpsock._sock)
- tcpsock._recv = wrapper.recv
- tcpsock._send = wrapper.send
- self.starttls='success'
-
- def _ssl_verify_callback(self, sslconn, cert, errnum, depth, ok):
- # Exceptions can't propagate up through this callback, so print them here.
- try:
- self._owner.Connection.ssl_fingerprint_sha1 = cert.digest('sha1')
- if errnum == 0:
- return True
- self._owner.Connection.ssl_errnum = errnum
- self._owner.Connection.ssl_cert_pem = OpenSSL.crypto.dump_certificate(
- OpenSSL.crypto.FILETYPE_PEM, cert)
- return True
- except:
- log.error("Exception caught in _ssl_info_callback:", exc_info=True)
- traceback.print_exc() # Make sure something is printed, even if log is disabled.
-
- def StartTLSHandler(self, conn, starttls):
- ''' Handle server reply if TLS is allowed to process. Behaves accordingly.
- Used internally.'''
- if starttls.getNamespace() <> NS_TLS:
- self.on_tls_failure('Unknown namespace: %s' % starttls.getNamespace())
- return
- self.starttls = starttls.getName()
- if self.starttls == 'failure':
- self.on_tls_failure('TLS <failure> received: %s' % self.starttls)
- return
- self.DEBUG('Got starttls proceed response. Switching to TLS/SSL...','ok')
- try:
- self._startSSL()
- except Exception, e:
- log.error("StartTLSHandler:", exc_info=True)
- self.on_tls_failure('in StartTLSHandler')
- #traceback.print_exc()
- return
- self._owner.Dispatcher.PlugOut()
- self.on_tls_success()
- #dispatcher_nb.Dispatcher().PlugIn(self._owner)
-
class NBProxySocket(NonBlockingTcp):
'''
Interface for proxy socket wrappers - when tunnneling XMPP over proxies,
@@ -957,8 +491,10 @@
return
if len(reply) != 2:
pass
- self.onreceive(self._on_proxy_auth)
-
+ self.after_proxy_connect()
+ #self.onreceive(self._on_proxy_auth)
+
+ # FIXME: find out what it this method for
def _on_proxy_auth(self, reply):
if self.reply.find('\n\n') == -1:
if reply is None:
@@ -1109,3 +645,70 @@
def DEBUG(self, text, severity):
''' Overwrites DEBUG tag to allow debug output be presented as "CONNECTproxy".'''
return self._owner.DEBUG(DBG_CONNECT_PROXY, text, severity)
+
+
+
+class NonBlockingHttpBOSH(NonBlockingTcp):
+ '''
+ Socket wrapper that makes HTTP message out of send data and peels-off
+ HTTP headers from incoming messages
+ '''
+
+ def __init__(self, bosh_uri, bosh_port, on_disconnect):
+ self.bosh_protocol, self.bosh_host, self.bosh_path = self.urisplit(bosh_uri)
+ if self.bosh_protocol is None:
+ self.bosh_protocol = 'http'
+ if self.bosh_path == '':
+ bosh_path = '/'
+ self.bosh_port = bosh_port
+
+ def send(self, raw_data, now=False):
+
+ NonBlockingTcp.send(
+ self,
+ self.build_http_message(raw_data),
+ now)
+
+ def _on_receive(self,data):
+ '''Preceeds passing received data to Client class. Gets rid of HTTP headers
+ and checks them.'''
+ statusline, headers, httpbody = self.parse_http_message(data)
+ if statusline[1] != '200':
+ log.error('HTTP Error: %s %s' % (statusline[1], statusline[2]))
+ self.disconnect()
+ self.on_receive(httpbody)
+
+
+ def build_http_message(self, httpbody):
+ '''
+ Builds bosh http message with given body.
+ Values for headers and status line fields are taken from class variables.
+ )
+ '''
+ headers = ['POST %s HTTP/1.1' % self.bosh_path,
+ 'Host: %s:%s' % (self.bosh_host, self.bosh_port),
+ 'Content-Type: text/xml; charset=utf-8',
+ 'Content-Length: %s' % len(str(httpbody)),
+ '\r\n']
+ headers = '\r\n'.join(headers)
+ return('%s%s\r\n' % (headers, httpbody))
+
+ def parse_http_message(self, message):
+ '''
+ splits http message to tuple (
+ statusline - list of e.g. ['HTTP/1.1', '200', 'OK'],
+ headers - dictionary of headers e.g. {'Content-Length': '604',
+ 'Content-Type': 'text/xml; charset=utf-8'},
+ httpbody - string with http body
+ )
+ '''
+ message = message.replace('\r','')
+ (header, httpbody) = message.split('\n\n')
+ header = header.split('\n')
+ statusline = header[0].split(' ')
+ header = header[1:]
+ headers = {}
+ for dummy in header:
+ row = dummy.split(' ',1)
+ headers[row[0][:-1]] = row[1]
+ return (statusline, headers, httpbody)