2006-02-03 12:17:34 +00:00
|
|
|
## transports_nb.py
|
|
|
|
## based on transports.py
|
|
|
|
##
|
|
|
|
## Copyright (C) 2003-2004 Alexey "Snake" Nezhdanov
|
|
|
|
## modified by Dimitur Kirov <dkirov@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,select,base64,dispatcher_nb
|
|
|
|
from simplexml import ustr
|
|
|
|
from client import PlugIn
|
|
|
|
from idlequeue import IdleObject
|
|
|
|
from protocol import *
|
|
|
|
from transports import *
|
|
|
|
|
|
|
|
import sys
|
|
|
|
import os
|
|
|
|
import errno
|
2006-11-28 01:02:31 +00:00
|
|
|
import time
|
2006-02-03 12:17:34 +00:00
|
|
|
|
2006-11-13 14:24:41 +00:00
|
|
|
import traceback
|
|
|
|
import thread
|
|
|
|
|
|
|
|
import logging
|
2006-12-28 11:15:44 +00:00
|
|
|
log = logging.getLogger('gajim.c.x.transports_nb')
|
2006-11-13 14:24:41 +00:00
|
|
|
|
|
|
|
USE_PYOPENSSL = False
|
|
|
|
|
|
|
|
try:
|
|
|
|
#raise ImportError("Manually disabled PyOpenSSL")
|
|
|
|
import OpenSSL.SSL
|
|
|
|
import OpenSSL.crypto
|
|
|
|
USE_PYOPENSSL = True
|
2006-12-20 20:40:08 +00:00
|
|
|
log.info("PyOpenSSL loaded")
|
2006-11-13 14:24:41 +00:00
|
|
|
except ImportError:
|
2006-12-29 01:46:46 +00:00
|
|
|
log.debug("Import of PyOpenSSL failed:", exc_info=True)
|
|
|
|
|
2006-11-13 14:24:41 +00:00
|
|
|
# FIXME: Remove these prints before release, replace with a warning dialog.
|
2006-12-20 20:40:08 +00:00
|
|
|
print >> sys.stderr, "=" * 79
|
|
|
|
print >> sys.stderr, "PyOpenSSL not found, falling back to Python builtin SSL objects (insecure)."
|
|
|
|
print >> sys.stderr, "=" * 79
|
2006-02-03 12:17:34 +00:00
|
|
|
|
|
|
|
# timeout to connect to the server socket, it doesn't include auth
|
|
|
|
CONNECT_TIMEOUT_SECONDS = 30
|
|
|
|
|
|
|
|
# how long to wait for a disconnect to complete
|
|
|
|
DISCONNECT_TIMEOUT_SECONDS = 10
|
|
|
|
|
|
|
|
# size of the buffer which reads data from server
|
|
|
|
# if lower, more stanzas will be fragmented and processed twice
|
2006-11-13 14:24:41 +00:00
|
|
|
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
|
|
|
|
|
2006-11-28 01:02:31 +00:00
|
|
|
def gattr(obj, attr, default=None):
|
|
|
|
try:
|
|
|
|
return getattr(obj, attr)
|
|
|
|
except:
|
|
|
|
return default
|
2006-11-13 22:17:16 +00:00
|
|
|
|
2006-11-28 01:02:31 +00:00
|
|
|
class SSLWrapper:
|
|
|
|
class Error(IOError):
|
2006-12-20 20:40:08 +00:00
|
|
|
def __init__(self, sock=None, exc=None, errno=None, strerror=None, peer=None):
|
|
|
|
self.parent = IOError
|
2006-11-28 01:02:31 +00:00
|
|
|
|
2006-12-13 20:15:22 +00:00
|
|
|
errno = errno or gattr(exc, 'errno')
|
2006-12-01 22:23:45 +00:00
|
|
|
strerror = strerror or gattr(exc, 'strerror') or gattr(exc, 'args')
|
|
|
|
if not isinstance(strerror, basestring): strerror = repr(strerror)
|
2006-11-28 01:02:31 +00:00
|
|
|
|
2006-12-20 20:40:08 +00:00
|
|
|
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)
|
2006-12-13 20:15:22 +00:00
|
|
|
if not errno:
|
|
|
|
try:
|
|
|
|
if isinstance(exc, OpenSSL.SSL.SysCallError):
|
2006-12-20 20:40:08 +00:00
|
|
|
if self.exc_args[0] > 0:
|
|
|
|
errno = self.exc_args[0]
|
|
|
|
strerror = self.exc_args[1]
|
|
|
|
except: pass
|
2006-12-13 20:15:22 +00:00
|
|
|
|
2006-12-20 20:40:08 +00:00
|
|
|
self.parent.__init__(self, errno, strerror)
|
2006-11-28 01:02:31 +00:00
|
|
|
|
2006-12-20 20:40:08 +00:00
|
|
|
if self.peer is None and sock is not None:
|
2006-11-28 01:02:31 +00:00
|
|
|
try:
|
2006-12-20 20:40:08 +00:00
|
|
|
ppeer = self.sock.getpeername()
|
2006-11-28 01:02:31 +00:00
|
|
|
if len(ppeer) == 2 and isinstance(ppeer[0], basestring) \
|
|
|
|
and isinstance(ppeer[1], int):
|
2006-12-20 20:40:08 +00:00
|
|
|
self.peer = ppeer
|
2006-11-28 01:02:31 +00:00
|
|
|
except: pass
|
|
|
|
|
2006-12-20 20:40:08 +00:00
|
|
|
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)
|
2006-11-28 01:02:31 +00:00
|
|
|
return s
|
|
|
|
|
2006-12-20 20:40:08 +00:00
|
|
|
def __init__(self, sslobj, sock=None):
|
|
|
|
self.sslobj = sslobj
|
|
|
|
self.sock = sock
|
|
|
|
log.debug("%s.__init__ called with %s", self.__class__, sslobj)
|
2006-11-13 14:24:41 +00:00
|
|
|
|
2006-12-20 20:40:08 +00:00
|
|
|
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. """
|
2006-11-13 14:24:41 +00:00
|
|
|
|
|
|
|
raise NotImplementedException()
|
|
|
|
|
2006-12-20 20:40:08 +00:00
|
|
|
def send(self, data, flags=None):
|
2006-11-13 14:24:41 +00:00
|
|
|
raise NotImplementedException()
|
|
|
|
|
|
|
|
class PyOpenSSLWrapper(SSLWrapper):
|
|
|
|
'''Wrapper class for PyOpenSSL's recv() and send() methods'''
|
|
|
|
|
2006-12-20 20:40:08 +00:00
|
|
|
def __init__(self, *args):
|
|
|
|
self.parent = SSLWrapper
|
|
|
|
self.parent.__init__(self, *args)
|
2006-11-13 14:24:41 +00:00
|
|
|
|
2006-12-20 20:40:08 +00:00
|
|
|
def is_numtoolarge(self, e):
|
2006-11-13 14:24:41 +00:00
|
|
|
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
|
|
|
|
|
2006-12-20 20:40:08 +00:00
|
|
|
def recv(self, bufsize, flags=None):
|
2006-11-13 14:24:41 +00:00
|
|
|
retval = None
|
|
|
|
try:
|
2006-12-20 20:40:08 +00:00
|
|
|
if flags is None: retval = self.sslobj.recv(bufsize)
|
|
|
|
else: retval = self.sslobj.recv(bufsize, flags)
|
2006-11-13 14:24:41 +00:00
|
|
|
except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError), e:
|
2006-12-20 20:40:08 +00:00
|
|
|
pass
|
|
|
|
# log.debug("Recv: " + repr(e))
|
2006-11-14 06:39:08 +00:00
|
|
|
except OpenSSL.SSL.SysCallError, e:
|
2007-01-01 02:11:15 +00:00
|
|
|
log.debug("Recv: Got OpenSSL.SSL.SysCallError: " + repr(e), exc_info=True)
|
2006-12-20 20:40:08 +00:00
|
|
|
#traceback.print_exc()
|
|
|
|
raise SSLWrapper.Error(self.sock or self.sslobj, e)
|
2006-11-13 14:24:41 +00:00
|
|
|
except OpenSSL.SSL.Error, e:
|
2006-12-20 20:40:08 +00:00
|
|
|
if self.is_numtoolarge(e):
|
2006-11-28 01:02:31 +00:00
|
|
|
# warn, but ignore this exception
|
|
|
|
log.warning("Recv: OpenSSL: asn1enc: first num too large (ignored)")
|
2006-11-13 14:24:41 +00:00
|
|
|
else:
|
2007-01-01 02:11:15 +00:00
|
|
|
log.debug("Recv: Caught OpenSSL.SSL.Error:", exc_info=True)
|
2006-12-20 20:40:08 +00:00
|
|
|
#traceback.print_exc()
|
|
|
|
#print "Current Stack:"
|
|
|
|
#traceback.print_stack()
|
|
|
|
raise SSLWrapper.Error(self.sock or self.sslobj, e)
|
2006-11-13 14:24:41 +00:00
|
|
|
return retval
|
|
|
|
|
2006-12-20 20:40:08 +00:00
|
|
|
def send(self, data, flags=None):
|
2006-11-13 14:24:41 +00:00
|
|
|
try:
|
2006-12-20 20:40:08 +00:00
|
|
|
if flags is None: return self.sslobj.send(data)
|
|
|
|
else: return self.sslobj.send(data, flags)
|
2006-11-13 14:24:41 +00:00
|
|
|
except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError), e:
|
2006-12-20 20:40:08 +00:00
|
|
|
#log.debug("Send: " + repr(e))
|
2006-11-28 01:02:31 +00:00
|
|
|
time.sleep(0.1) # prevent 100% CPU usage
|
|
|
|
except OpenSSL.SSL.SysCallError, e:
|
2006-12-20 20:40:08 +00:00
|
|
|
log.error("Send: Got OpenSSL.SSL.SysCallError: " + repr(e), exc_info=True)
|
|
|
|
#traceback.print_exc()
|
|
|
|
raise SSLWrapper.Error(self.sock or self.sslobj, e)
|
2006-11-28 01:02:31 +00:00
|
|
|
except OpenSSL.SSL.Error, e:
|
2006-12-20 20:40:08 +00:00
|
|
|
if self.is_numtoolarge(e):
|
2006-11-13 14:24:41 +00:00
|
|
|
# warn, but ignore this exception
|
2006-11-28 01:02:31 +00:00
|
|
|
log.warning("Send: OpenSSL: asn1enc: first num too large (ignored)")
|
2006-11-13 14:24:41 +00:00
|
|
|
else:
|
2006-12-20 20:40:08 +00:00
|
|
|
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)
|
2006-11-13 14:24:41 +00:00
|
|
|
return 0
|
|
|
|
|
|
|
|
class StdlibSSLWrapper(SSLWrapper):
|
|
|
|
'''Wrapper class for Python's socket.ssl read() and write() methods'''
|
|
|
|
|
2006-12-20 20:40:08 +00:00
|
|
|
def __init__(self, *args):
|
|
|
|
self.parent = SSLWrapper
|
|
|
|
self.parent.__init__(self, *args)
|
2006-11-13 14:24:41 +00:00
|
|
|
|
2006-12-20 20:40:08 +00:00
|
|
|
def recv(self, bufsize, flags=None):
|
2006-11-13 14:24:41 +00:00
|
|
|
# we simply ignore flags since ssl object doesn't support it
|
|
|
|
try:
|
2006-12-20 20:40:08 +00:00
|
|
|
return self.sslobj.read(bufsize)
|
2006-11-13 14:24:41 +00:00
|
|
|
except socket.sslerror, e:
|
2006-12-21 18:08:17 +00:00
|
|
|
#log.debug("Recv: Caught socket.sslerror:", exc_info=True)
|
2006-12-20 20:40:08 +00:00
|
|
|
#traceback.print_exc()
|
2006-11-13 14:24:41 +00:00
|
|
|
if e.args[0] not in (socket.SSL_ERROR_WANT_READ, socket.SSL_ERROR_WANT_WRITE):
|
2006-12-20 20:40:08 +00:00
|
|
|
raise SSLWrapper.Error(self.sock or self.sslobj, e)
|
|
|
|
return None
|
2006-11-13 14:44:17 +00:00
|
|
|
|
2006-12-20 20:40:08 +00:00
|
|
|
def send(self, data, flags=None):
|
2006-11-13 14:24:41 +00:00
|
|
|
# we simply ignore flags since ssl object doesn't support it
|
|
|
|
try:
|
2006-12-20 20:40:08 +00:00
|
|
|
return self.sslobj.write(data)
|
2006-11-13 14:24:41 +00:00
|
|
|
except socket.sslerror, e:
|
2006-12-21 18:08:17 +00:00
|
|
|
#log.debug("Send: Caught socket.sslerror:", exc_info=True)
|
2006-12-20 20:40:08 +00:00
|
|
|
#traceback.print_exc()
|
2006-11-13 14:24:41 +00:00
|
|
|
if e.args[0] not in (socket.SSL_ERROR_WANT_READ, socket.SSL_ERROR_WANT_WRITE):
|
2006-12-20 20:40:08 +00:00
|
|
|
raise SSLWrapper.Error(self.sock or self.sslobj, e)
|
2006-11-13 14:24:41 +00:00
|
|
|
return 0
|
2006-02-03 12:17:34 +00:00
|
|
|
|
|
|
|
class NonBlockingTcp(PlugIn, IdleObject):
|
|
|
|
''' This class can be used instead of transports.Tcp in threadless implementations '''
|
|
|
|
def __init__(self, on_connect = None, on_connect_failure = None, server=None, use_srv = True):
|
|
|
|
''' Cache connection point 'server'. 'server' is the tuple of (host, port)
|
|
|
|
absolutely the same as standard tcp socket uses.
|
|
|
|
on_connect - called when we connect to the socket
|
|
|
|
on_connect_failure - called if there was error connecting to socket
|
|
|
|
'''
|
|
|
|
IdleObject.__init__(self)
|
|
|
|
PlugIn.__init__(self)
|
|
|
|
self.DBG_LINE='socket'
|
|
|
|
self._exported_methods=[self.send, self.disconnect, self.onreceive, self.set_send_timeout,
|
|
|
|
self.start_disconnect, self.set_timeout, self.remove_timeout]
|
|
|
|
self._server = server
|
|
|
|
self.on_connect = on_connect
|
|
|
|
self.on_connect_failure = on_connect_failure
|
|
|
|
self.on_receive = None
|
|
|
|
self.on_disconnect = None
|
2006-12-20 20:40:08 +00:00
|
|
|
self.printed_error = False
|
2006-02-03 12:17:34 +00:00
|
|
|
|
|
|
|
# 0 - not connected
|
|
|
|
# 1 - connected
|
|
|
|
# -1 - about to disconnect (when we wait for final events to complete)
|
|
|
|
# -2 - disconnected
|
|
|
|
self.state = 0
|
|
|
|
|
|
|
|
# queue with messages to be send
|
|
|
|
self.sendqueue = []
|
|
|
|
|
|
|
|
# bytes remained from the last send message
|
|
|
|
self.sendbuff = ''
|
|
|
|
|
|
|
|
# time to wait for SOME stanza to come and then send keepalive
|
|
|
|
self.sendtimeout = 0
|
|
|
|
|
|
|
|
# in case we want to something different than sending keepalives
|
|
|
|
self.on_timeout = None
|
|
|
|
|
|
|
|
# writable, readable - keep state of the last pluged flags
|
|
|
|
# This prevents replug of same object with the same flags
|
|
|
|
self.writable = True
|
|
|
|
self.readable = False
|
|
|
|
|
|
|
|
def plugin(self, owner):
|
|
|
|
''' Fire up connection. Return non-empty string on success.
|
|
|
|
Also registers self.disconnected method in the owner's dispatcher.
|
|
|
|
Called internally. '''
|
|
|
|
self.idlequeue = owner.idlequeue
|
2006-12-20 20:40:08 +00:00
|
|
|
self.printed_error = False
|
2006-02-03 12:17:34 +00:00
|
|
|
if not self._server:
|
|
|
|
self._server=(self._owner.Server,5222)
|
|
|
|
if self.connect(self._server) is False:
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
def read_timeout(self):
|
|
|
|
if self.state == 0:
|
|
|
|
self.idlequeue.unplug_idle(self.fd)
|
|
|
|
if self.on_connect_failure:
|
|
|
|
self.on_connect_failure()
|
|
|
|
else:
|
|
|
|
if self.on_timeout:
|
|
|
|
self.on_timeout()
|
|
|
|
self.renew_send_timeout()
|
|
|
|
|
|
|
|
def connect(self,server=None, proxy = None, secure = None):
|
|
|
|
''' Try to establish connection. Returns non-empty string on success. '''
|
|
|
|
if not server:
|
|
|
|
server=self._server
|
|
|
|
else:
|
|
|
|
self._server = server
|
2006-12-20 20:40:08 +00:00
|
|
|
self.printed_error = False
|
2006-02-03 12:17:34 +00:00
|
|
|
self.state = 0
|
2006-12-27 22:45:23 +00:00
|
|
|
success = False
|
2006-02-03 12:17:34 +00:00
|
|
|
try:
|
2006-11-25 11:50:31 +00:00
|
|
|
for ai in socket.getaddrinfo(server[0],server[1],socket.AF_UNSPEC,socket.SOCK_STREAM):
|
|
|
|
try:
|
|
|
|
self._sock=socket.socket(*ai[:3])
|
|
|
|
self._sock.setblocking(False)
|
|
|
|
self._server=ai[4]
|
2006-12-27 22:45:23 +00:00
|
|
|
success = True
|
2006-11-25 11:50:31 +00:00
|
|
|
break
|
|
|
|
except:
|
|
|
|
if sys.exc_value[0] == errno.EINPROGRESS:
|
2006-12-27 22:45:23 +00:00
|
|
|
success = True
|
2006-11-25 11:50:31 +00:00
|
|
|
break
|
|
|
|
#for all errors, we try other addresses
|
|
|
|
continue
|
2006-12-27 22:45:23 +00:00
|
|
|
except socket.gaierror, e:
|
|
|
|
log.info("Lookup failure for %s: %s[%s]", self.getName(), e[1], repr(e[0]), exc_info=True)
|
2006-03-10 20:36:15 +00:00
|
|
|
except:
|
2006-12-20 20:40:08 +00:00
|
|
|
log.error("Exception trying to connect to %s:", self.getName(), exc_info=True)
|
2006-12-27 22:45:23 +00:00
|
|
|
|
|
|
|
if not success:
|
2006-02-03 12:17:34 +00:00
|
|
|
if self.on_connect_failure:
|
|
|
|
self.on_connect_failure()
|
|
|
|
return False
|
2006-12-27 22:45:23 +00:00
|
|
|
|
2006-02-03 12:17:34 +00:00
|
|
|
self.fd = self._sock.fileno()
|
|
|
|
self.idlequeue.plug_idle(self, True, False)
|
|
|
|
self.set_timeout(CONNECT_TIMEOUT_SECONDS)
|
|
|
|
self._do_connect()
|
|
|
|
return True
|
|
|
|
|
|
|
|
def _plug_idle(self):
|
|
|
|
readable = self.state != 0
|
|
|
|
if self.sendqueue or self.sendbuff:
|
|
|
|
writable = True
|
|
|
|
else:
|
|
|
|
writable = False
|
|
|
|
if self.writable != writable or self.readable != readable:
|
|
|
|
self.idlequeue.plug_idle(self, writable, readable)
|
|
|
|
|
|
|
|
def pollout(self):
|
|
|
|
if self.state == 0:
|
|
|
|
return self._do_connect()
|
|
|
|
return self._do_send()
|
|
|
|
|
|
|
|
def plugout(self):
|
|
|
|
''' Disconnect from the remote server and unregister self.disconnected method from
|
|
|
|
the owner's dispatcher. '''
|
|
|
|
self.disconnect()
|
|
|
|
self._owner.Connection = None
|
2006-05-03 16:12:10 +00:00
|
|
|
self._owner = None
|
2006-02-03 12:17:34 +00:00
|
|
|
|
|
|
|
def pollin(self):
|
|
|
|
self._do_receive()
|
|
|
|
|
2006-12-08 21:19:01 +00:00
|
|
|
def pollend(self, retry=False):
|
2006-12-20 20:40:08 +00:00
|
|
|
if not self.printed_error:
|
|
|
|
self.printed_error = True
|
|
|
|
try: self._do_receive(errors_only=True)
|
|
|
|
except: log.error("pollend: Got exception from _do_receive:", exc_info=True)
|
2006-05-03 16:12:10 +00:00
|
|
|
conn_failure_cb = self.on_connect_failure
|
2006-02-03 12:17:34 +00:00
|
|
|
self.disconnect()
|
2006-05-03 16:12:10 +00:00
|
|
|
if conn_failure_cb:
|
2006-11-15 08:24:35 +00:00
|
|
|
conn_failure_cb(retry)
|
2006-02-03 12:17:34 +00:00
|
|
|
|
|
|
|
def disconnect(self):
|
|
|
|
if self.state == -2: # already disconnected
|
|
|
|
return
|
|
|
|
self.state = -2
|
|
|
|
self.sendqueue = None
|
|
|
|
self.remove_timeout()
|
|
|
|
self._owner.disconnected()
|
|
|
|
self.idlequeue.unplug_idle(self.fd)
|
2006-12-27 20:44:21 +00:00
|
|
|
sock = getattr(self, '_sock', None)
|
|
|
|
if sock:
|
|
|
|
try:
|
|
|
|
sock.shutdown(socket.SHUT_RDWR)
|
|
|
|
except socket.error, e:
|
|
|
|
if e[0] != errno.ENOTCONN:
|
|
|
|
log.error("Error shutting down socket for %s:", self.getName(), exc_info=True)
|
|
|
|
try: sock.close()
|
|
|
|
except: log.error("Error closing socket for %s:", self.getName(), exc_info=True)
|
2006-02-06 00:11:45 +00:00
|
|
|
# socket descriptor cannot be (un)plugged anymore
|
|
|
|
self.fd = -1
|
2006-02-03 12:17:34 +00:00
|
|
|
if self.on_disconnect:
|
|
|
|
self.on_disconnect()
|
2006-05-03 16:12:10 +00:00
|
|
|
self.on_connect_failure = None
|
2006-02-03 12:17:34 +00:00
|
|
|
|
|
|
|
def end_disconnect(self):
|
|
|
|
''' force disconnect only if we are still trying to disconnect '''
|
|
|
|
if self.state == -1:
|
|
|
|
self.disconnect()
|
|
|
|
|
|
|
|
def start_disconnect(self, to_send, on_disconnect):
|
|
|
|
self.on_disconnect = on_disconnect
|
|
|
|
self.sendqueue = []
|
|
|
|
self.send(to_send)
|
|
|
|
self.send('</stream:stream>')
|
|
|
|
self.state = -1 # about to disconnect
|
|
|
|
self.idlequeue.set_alarm(self.end_disconnect, DISCONNECT_TIMEOUT_SECONDS)
|
|
|
|
|
|
|
|
def set_timeout(self, timeout):
|
|
|
|
if self.state >= 0 and self.fd > 0:
|
|
|
|
self.idlequeue.set_read_timeout(self.fd, timeout)
|
|
|
|
|
|
|
|
def remove_timeout(self):
|
|
|
|
if self.fd:
|
|
|
|
self.idlequeue.remove_timeout(self.fd)
|
|
|
|
|
|
|
|
def onreceive(self, recv_handler):
|
|
|
|
if not recv_handler:
|
|
|
|
if hasattr(self._owner, 'Dispatcher'):
|
|
|
|
self.on_receive = self._owner.Dispatcher.ProcessNonBlocking
|
|
|
|
else:
|
|
|
|
self.on_receive = None
|
|
|
|
return
|
|
|
|
_tmp = self.on_receive
|
|
|
|
# make sure this cb is not overriden by recursive calls
|
|
|
|
if not recv_handler(None) and _tmp == self.on_receive:
|
|
|
|
self.on_receive = recv_handler
|
|
|
|
|
2006-12-20 20:40:08 +00:00
|
|
|
def _do_receive(self, errors_only=False):
|
2006-02-03 12:17:34 +00:00
|
|
|
''' Reads all pending incoming data. Calls owner's disconnected() method if appropriate.'''
|
2006-11-13 14:24:41 +00:00
|
|
|
ERR_DISCONN = -2 # Misc error signifying that we got disconnected
|
|
|
|
ERR_OTHER = -1 # Other error
|
|
|
|
received = None
|
2006-02-03 12:17:34 +00:00
|
|
|
errnum = 0
|
2006-11-13 14:24:41 +00:00
|
|
|
errtxt = 'No Error Set'
|
2006-02-03 12:17:34 +00:00
|
|
|
try:
|
|
|
|
# get as many bites, as possible, but not more than RECV_BUFSIZE
|
|
|
|
received = self._recv(RECV_BUFSIZE)
|
2006-11-13 14:24:41 +00:00
|
|
|
except (socket.error, socket.herror, socket.gaierror), e:
|
2006-12-27 18:38:50 +00:00
|
|
|
log.debug("_do_receive: got %s:", e.__class__, exc_info=True)
|
2006-12-20 20:40:08 +00:00
|
|
|
#traceback.print_exc()
|
|
|
|
#print "Current Stack:"
|
|
|
|
#traceback.print_stack()
|
2006-11-13 14:24:41 +00:00
|
|
|
errnum = e[0]
|
|
|
|
errtxt = str(errnum) + ':' + e[1]
|
|
|
|
except socket.sslerror, e:
|
2006-12-20 20:40:08 +00:00
|
|
|
log.error("_do_receive: got unknown %s:", e.__class__, exc_info=True)
|
|
|
|
#traceback.print_exc()
|
|
|
|
#print "Current Stack:"
|
|
|
|
#traceback.print_stack()
|
2006-11-13 14:24:41 +00:00
|
|
|
errnum = ERR_OTHER
|
|
|
|
errtxt = repr("socket.sslerror: " + e.args)
|
2006-11-28 01:02:31 +00:00
|
|
|
except SSLWrapper.Error, e:
|
2006-12-20 20:40:08 +00:00
|
|
|
log.debug("Caught: %s", str(e))
|
2006-11-28 01:02:31 +00:00
|
|
|
errnum = gattr(e, 'errno', ERR_OTHER)
|
2006-12-15 10:57:25 +00:00
|
|
|
if not errnum: errnum = ERR_OTHER # unset, but we must put a status
|
2006-11-28 01:02:31 +00:00
|
|
|
errtxt = gattr(e, 'strerror') or repr(e.args)
|
2006-11-13 14:24:41 +00:00
|
|
|
|
2006-12-20 20:40:08 +00:00
|
|
|
if received == '':
|
2006-11-13 14:24:41 +00:00
|
|
|
errnum = ERR_DISCONN
|
|
|
|
errtxt = "Connection closed unexpectedly"
|
|
|
|
|
|
|
|
if errnum in (ERR_DISCONN, errno.ECONNRESET, errno.ENOTCONN, errno.ESHUTDOWN):
|
2006-12-13 20:15:22 +00:00
|
|
|
log.error("Connection to %s lost: %s [%d]", self.getName(), errtxt, errnum)
|
2006-12-20 20:40:08 +00:00
|
|
|
self.printed_error = True
|
|
|
|
if not errors_only:
|
|
|
|
self.pollend(retry=(errnum in (ERR_DISCONN, errno.ECONNRESET)))
|
2006-12-08 21:19:01 +00:00
|
|
|
# don't process result, because it will raise an error
|
2006-02-11 21:49:17 +00:00
|
|
|
return
|
2006-11-13 14:24:41 +00:00
|
|
|
|
|
|
|
if received is None:
|
|
|
|
if errnum != 0:
|
|
|
|
self.DEBUG(errtxt, 'error')
|
2006-12-13 20:15:22 +00:00
|
|
|
log.error("Connection to %s lost: %s [%d]", self.getName(), errtxt, errnum)
|
2006-12-20 20:40:08 +00:00
|
|
|
self.printed_error = True
|
|
|
|
if not errors_only and self.state >= 0:
|
2006-12-08 21:19:01 +00:00
|
|
|
self.pollend(retry=True)
|
2006-11-13 14:24:41 +00:00
|
|
|
return
|
|
|
|
received = ''
|
|
|
|
|
2006-12-20 20:40:08 +00:00
|
|
|
if errors_only or self.state < 0:
|
2006-02-03 12:17:34 +00:00
|
|
|
return
|
2006-11-13 14:24:41 +00:00
|
|
|
|
2006-02-03 12:17:34 +00:00
|
|
|
# we have received some bites, stop the timeout!
|
|
|
|
self.renew_send_timeout()
|
|
|
|
if self.on_receive:
|
|
|
|
if received.strip():
|
2006-03-16 17:29:30 +00:00
|
|
|
self.DEBUG(received, 'got')
|
2006-02-03 12:17:34 +00:00
|
|
|
if hasattr(self._owner, 'Dispatcher'):
|
|
|
|
self._owner.Dispatcher.Event('', DATA_RECEIVED, received)
|
|
|
|
self.on_receive(received)
|
|
|
|
else:
|
|
|
|
# This should never happed, so we need the debug
|
|
|
|
self.DEBUG('Unhandled data received: %s' % received,'got')
|
|
|
|
self.disconnect()
|
2006-04-15 09:43:15 +00:00
|
|
|
if self.on_connect_failure:
|
|
|
|
self.on_connect_failure()
|
2006-02-03 12:17:34 +00:00
|
|
|
return True
|
|
|
|
|
|
|
|
def _do_send(self):
|
|
|
|
if not self.sendbuff:
|
|
|
|
if not self.sendqueue:
|
|
|
|
return None # nothing to send
|
|
|
|
self.sendbuff = self.sendqueue.pop(0)
|
|
|
|
self.sent_data = self.sendbuff
|
|
|
|
try:
|
|
|
|
send_count = self._send(self.sendbuff)
|
|
|
|
if send_count:
|
|
|
|
self.sendbuff = self.sendbuff[send_count:]
|
|
|
|
if not self.sendbuff and not self.sendqueue:
|
|
|
|
if self.state < 0:
|
2006-03-16 17:29:30 +00:00
|
|
|
self.idlequeue.unplug_idle(self.fd)
|
2006-02-03 12:17:34 +00:00
|
|
|
self._on_send()
|
|
|
|
self.disconnect()
|
|
|
|
return
|
|
|
|
# we are not waiting for write
|
|
|
|
self._plug_idle()
|
|
|
|
self._on_send()
|
2006-03-15 19:39:27 +00:00
|
|
|
except socket.error, e:
|
|
|
|
if e[0] == socket.SSL_ERROR_WANT_WRITE:
|
|
|
|
return True
|
2006-12-20 20:40:08 +00:00
|
|
|
log.error("_do_send:", exc_info=True)
|
|
|
|
#traceback.print_exc()
|
2006-02-03 12:17:34 +00:00
|
|
|
if self.state < 0:
|
|
|
|
self.disconnect()
|
|
|
|
return
|
|
|
|
if self._on_send_failure:
|
|
|
|
self._on_send_failure()
|
|
|
|
return
|
|
|
|
return True
|
|
|
|
|
|
|
|
def _do_connect(self):
|
|
|
|
if self.state != 0:
|
|
|
|
return
|
|
|
|
self._sock.setblocking(False)
|
2006-12-20 20:40:08 +00:00
|
|
|
self._send = self._sock.send
|
|
|
|
self._recv = self._sock.recv
|
2006-02-03 22:54:05 +00:00
|
|
|
errnum = 0
|
2006-02-03 12:17:34 +00:00
|
|
|
try:
|
2006-02-03 22:54:05 +00:00
|
|
|
self._sock.connect(self._server)
|
2006-02-03 12:17:34 +00:00
|
|
|
except socket.error, e:
|
|
|
|
errnum = e[0]
|
2006-12-23 21:18:07 +00:00
|
|
|
# 10035 - winsock equivalent of EINPROGRESS
|
|
|
|
if errnum not in (errno.EINPROGRESS, 10035):
|
2006-12-20 20:40:08 +00:00
|
|
|
log.error("_do_connect:", exc_info=True)
|
|
|
|
#traceback.print_exc()
|
2006-02-03 12:17:34 +00:00
|
|
|
# in progress, or would block
|
|
|
|
if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK):
|
|
|
|
return
|
|
|
|
# 10056 - already connected, only on win32
|
|
|
|
# code 'WS*' is not available on GNU, so we use its numeric value
|
2006-03-27 15:19:17 +00:00
|
|
|
elif errnum not in (0, 10056, errno.EISCONN):
|
2006-02-03 12:17:34 +00:00
|
|
|
self.remove_timeout()
|
|
|
|
if self.on_connect_failure:
|
|
|
|
self.on_connect_failure()
|
|
|
|
return
|
|
|
|
self.remove_timeout()
|
|
|
|
self._owner.Connection=self
|
|
|
|
self.state = 1
|
|
|
|
|
|
|
|
self._sock.setblocking(False)
|
|
|
|
self._plug_idle()
|
|
|
|
if self.on_connect:
|
|
|
|
self.on_connect()
|
|
|
|
self.on_connect = None
|
|
|
|
return True
|
|
|
|
|
|
|
|
def send(self, raw_data):
|
2006-03-15 19:39:27 +00:00
|
|
|
'''Append raw_data to the queue of messages to be send.
|
|
|
|
If supplied data is unicode string, encode it to utf-8.
|
|
|
|
'''
|
2006-02-03 12:17:34 +00:00
|
|
|
if self.state <= 0:
|
|
|
|
return
|
|
|
|
r = raw_data
|
|
|
|
if isinstance(r, unicode):
|
|
|
|
r = r.encode('utf-8')
|
|
|
|
elif not isinstance(r, str):
|
|
|
|
r = ustr(r).encode('utf-8')
|
|
|
|
self.sendqueue.append(r)
|
|
|
|
self._plug_idle()
|
|
|
|
|
|
|
|
def _on_send(self):
|
|
|
|
if self.sent_data and self.sent_data.strip():
|
|
|
|
self.DEBUG(self.sent_data,'sent')
|
|
|
|
if hasattr(self._owner, 'Dispatcher'):
|
|
|
|
self._owner.Dispatcher.Event('', DATA_SENT, self.sent_data)
|
|
|
|
self.sent_data = None
|
|
|
|
|
|
|
|
def _on_send_failure(self):
|
|
|
|
self.DEBUG("Socket error while sending data",'error')
|
|
|
|
self._owner.disconnected()
|
|
|
|
self.sent_data = None
|
|
|
|
|
|
|
|
def set_send_timeout(self, timeout, on_timeout):
|
|
|
|
self.sendtimeout = timeout
|
|
|
|
if self.sendtimeout > 0:
|
|
|
|
self.on_timeout = on_timeout
|
|
|
|
else:
|
|
|
|
self.on_timeout = None
|
|
|
|
|
|
|
|
def renew_send_timeout(self):
|
|
|
|
if self.on_timeout and self.sendtimeout > 0:
|
|
|
|
self.set_timeout(self.sendtimeout)
|
|
|
|
else:
|
|
|
|
self.remove_timeout()
|
|
|
|
|
|
|
|
def getHost(self):
|
|
|
|
''' Return the 'host' value that is connection is [will be] made to.'''
|
|
|
|
return self._server[0]
|
2006-12-13 20:15:22 +00:00
|
|
|
|
|
|
|
def getName(self):
|
|
|
|
''' Return the server's name, or 'getHost()' if not available.'''
|
|
|
|
retval = None
|
2006-12-15 10:57:25 +00:00
|
|
|
retval = gattr(self._owner, 'name')
|
2006-12-13 20:15:22 +00:00
|
|
|
if retval: return retval
|
|
|
|
return self.getHost()
|
|
|
|
|
2006-02-03 12:17:34 +00:00
|
|
|
def getPort(self):
|
|
|
|
''' Return the 'port' value that is connection is [will be] made to.'''
|
|
|
|
return self._server[1]
|
2006-03-15 19:39:27 +00:00
|
|
|
|
2006-02-03 12:17:34 +00:00
|
|
|
class NonBlockingTLS(PlugIn):
|
|
|
|
''' TLS connection used to encrypts already estabilished tcp connection.'''
|
2006-11-13 14:24:41 +00:00
|
|
|
|
|
|
|
# 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}
|
|
|
|
|
2006-02-03 12:17:34 +00:00
|
|
|
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
|
|
|
|
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_start = on_tls_start
|
2006-07-26 12:49:11 +00:00
|
|
|
if now:
|
|
|
|
try:
|
|
|
|
res = self._startSSL()
|
|
|
|
except Exception, e:
|
2006-12-20 20:40:08 +00:00
|
|
|
log.error("PlugIn: while trying _startSSL():", exc_info=True)
|
|
|
|
#traceback.print_exc()
|
2006-07-26 12:49:11 +00:00
|
|
|
self._owner.socket.pollend()
|
|
|
|
return
|
2006-02-03 12:17:34 +00:00
|
|
|
self.tls_start()
|
|
|
|
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.'''
|
2006-03-10 20:36:15 +00:00
|
|
|
# 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)
|
2006-05-03 16:12:10 +00:00
|
|
|
self._owner.Dispatcher.PlugOut()
|
|
|
|
self._owner = None
|
2006-02-03 12:17:34 +00:00
|
|
|
|
|
|
|
def tls_start(self):
|
|
|
|
if self.on_tls_start:
|
|
|
|
self.on_tls_start()
|
|
|
|
self.on_tls_start = 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.tls_start()
|
|
|
|
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)
|
|
|
|
self.tls_start()
|
|
|
|
raise NodeProcessed
|
|
|
|
|
2006-12-20 20:40:08 +00:00
|
|
|
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):
|
2006-11-13 14:24:41 +00:00
|
|
|
typedict = {OpenSSL.crypto.TYPE_RSA: "RSA", OpenSSL.crypto.TYPE_DSA: "DSA"}
|
2006-12-20 20:40:08 +00:00
|
|
|
print >> stream, "PKey bits:", pkey.bits()
|
|
|
|
print >> stream, "PKey type: %s (%d)" % (typedict.get(pkey.type(), "Unknown"), pkey.type())
|
2006-11-13 14:24:41 +00:00
|
|
|
|
2006-02-03 12:17:34 +00:00
|
|
|
def _startSSL(self):
|
|
|
|
''' Immidiatedly switch socket to TLS mode. Used internally.'''
|
2006-12-20 20:40:08 +00:00
|
|
|
log.debug("_startSSL called")
|
2006-11-13 14:24:41 +00:00
|
|
|
if USE_PYOPENSSL: return self._startSSL_pyOpenSSL()
|
|
|
|
return self._startSSL_stdlib()
|
|
|
|
|
|
|
|
def _startSSL_pyOpenSSL(self):
|
2006-12-20 20:40:08 +00:00
|
|
|
#log.debug("_startSSL_pyOpenSSL called, thread id: %s", str(thread.get_ident()))
|
|
|
|
log.debug("_startSSL_pyOpenSSL called")
|
2006-11-13 14:24:41 +00:00
|
|
|
tcpsock = self._owner.Connection
|
|
|
|
# FIXME: should method be configurable?
|
2006-12-21 18:08:17 +00:00
|
|
|
tcpsock._sslContext = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_METHOD)
|
|
|
|
#tcpsock._sslContext = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
|
2006-11-13 14:24:41 +00:00
|
|
|
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
|
|
|
|
|
2006-12-20 20:40:08 +00:00
|
|
|
log.debug("Initiating handshake...")
|
2006-12-22 23:30:23 +00:00
|
|
|
tcpsock._sslObj.setblocking(False)
|
2006-11-13 14:24:41 +00:00
|
|
|
try:
|
|
|
|
self.starttls='in progress'
|
|
|
|
tcpsock._sslObj.do_handshake()
|
|
|
|
except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError), e:
|
2006-12-20 20:40:08 +00:00
|
|
|
pass
|
2006-11-13 14:24:41 +00:00
|
|
|
#tcpsock._sslObj.setblocking(False)
|
|
|
|
#print "Done handshake"
|
2006-12-20 20:40:08 +00:00
|
|
|
log.debug("Async handshake started...")
|
2006-11-13 14:24:41 +00:00
|
|
|
|
2006-11-18 15:09:40 +00:00
|
|
|
# fake it, for now
|
|
|
|
self.starttls='success'
|
|
|
|
|
2006-11-13 14:24:41 +00:00
|
|
|
def _on_ssl_handshake_done(self):
|
2006-12-20 20:40:08 +00:00
|
|
|
log.debug("Handshake done!")
|
2006-11-18 15:09:40 +00:00
|
|
|
#self.starttls='success'
|
2006-11-13 14:24:41 +00:00
|
|
|
|
|
|
|
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)
|
2006-12-22 23:30:23 +00:00
|
|
|
tcpsock.serverDigestSHA1 = cert.digest('sha1')
|
|
|
|
tcpsock.serverDigestMD5 = cert.digest('md5')
|
2006-11-13 14:24:41 +00:00
|
|
|
|
2006-12-23 21:18:07 +00:00
|
|
|
if log.getEffectiveLevel() <= logging.DEBUG:
|
|
|
|
peercert = tcpsock._sslObj.get_peer_certificate()
|
|
|
|
ciphers = tcpsock._sslObj.get_cipher_list()
|
2006-11-13 14:24:41 +00:00
|
|
|
|
2006-12-23 21:18:07 +00:00
|
|
|
print >> sys.stderr, "Ciphers:", ciphers
|
|
|
|
print >> sys.stderr, "Peer cert:", peercert
|
|
|
|
self._dumpX509(peercert)
|
2006-11-13 14:24:41 +00:00
|
|
|
|
2006-12-23 21:18:07 +00:00
|
|
|
print >> sys.stderr, OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, peercert)
|
2006-11-13 14:24:41 +00:00
|
|
|
|
|
|
|
def _startSSL_stdlib(self):
|
2006-12-20 20:40:08 +00:00
|
|
|
log.debug("_startSSL_stdlib called")
|
2006-02-03 12:17:34 +00:00
|
|
|
tcpsock=self._owner.Connection
|
|
|
|
tcpsock._sock.setblocking(True)
|
2006-04-15 09:43:15 +00:00
|
|
|
tcpsock._sslObj = socket.ssl(tcpsock._sock, None, None)
|
2006-02-03 12:17:34 +00:00
|
|
|
tcpsock._sock.setblocking(False)
|
|
|
|
tcpsock._sslIssuer = tcpsock._sslObj.issuer()
|
|
|
|
tcpsock._sslServer = tcpsock._sslObj.server()
|
2006-11-28 01:02:31 +00:00
|
|
|
wrapper = StdlibSSLWrapper(tcpsock._sslObj, tcpsock._sock)
|
2006-11-13 14:24:41 +00:00
|
|
|
tcpsock._recv = wrapper.recv
|
|
|
|
tcpsock._send = wrapper.send
|
2006-02-03 12:17:34 +00:00
|
|
|
self.starttls='success'
|
|
|
|
|
2006-12-20 20:40:08 +00:00
|
|
|
def _ssl_info_callback(self, sslconn, type, st):
|
2006-11-13 14:24:41 +00:00
|
|
|
# Exceptions can't propagate up through this callback, so print them here.
|
2006-12-20 20:40:08 +00:00
|
|
|
try:
|
|
|
|
self._ssl_info_callback_guarded(sslconn, type, st)
|
|
|
|
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.
|
2006-11-13 14:24:41 +00:00
|
|
|
|
2006-12-20 20:40:08 +00:00
|
|
|
def _ssl_info_callback_guarded(self, sslconn, type, st):
|
|
|
|
b = self.ssl_h_bits
|
2006-11-13 14:24:41 +00:00
|
|
|
|
|
|
|
#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']:
|
2006-12-20 20:40:08 +00:00
|
|
|
self._on_ssl_handshake_done()
|
2006-11-13 14:24:41 +00:00
|
|
|
|
2006-02-03 12:17:34 +00:00
|
|
|
def StartTLSHandler(self, conn, starttls):
|
|
|
|
''' Handle server reply if TLS is allowed to process. Behaves accordingly.
|
|
|
|
Used internally.'''
|
|
|
|
if starttls.getNamespace() <> NS_TLS:
|
|
|
|
return
|
|
|
|
self.starttls = starttls.getName()
|
|
|
|
if self.starttls == 'failure':
|
|
|
|
self.DEBUG('Got starttls response: ' + self.starttls,'error')
|
|
|
|
return
|
|
|
|
self.DEBUG('Got starttls proceed response. Switching to TLS/SSL...','ok')
|
2006-07-26 12:49:11 +00:00
|
|
|
try:
|
|
|
|
self._startSSL()
|
|
|
|
except Exception, e:
|
2006-12-20 20:40:08 +00:00
|
|
|
log.error("StartTLSHandler:", exc_info=True)
|
|
|
|
#traceback.print_exc()
|
2006-07-26 12:49:11 +00:00
|
|
|
self._owner.socket.pollend()
|
|
|
|
return
|
2006-02-03 12:17:34 +00:00
|
|
|
self._owner.Dispatcher.PlugOut()
|
|
|
|
dispatcher_nb.Dispatcher().PlugIn(self._owner)
|
|
|
|
|
|
|
|
|
|
|
|
class NBHTTPPROXYsocket(NonBlockingTcp):
|
|
|
|
''' This class can be used instead of transports.HTTPPROXYsocket
|
|
|
|
HTTP (CONNECT) proxy connection class. Uses TCPsocket as the base class
|
|
|
|
redefines only connect method. Allows to use HTTP proxies like squid with
|
|
|
|
(optionally) simple authentication (using login and password).
|
|
|
|
|
|
|
|
'''
|
|
|
|
def __init__(self, on_connect =None, on_connect_failure = None,proxy = None,server = None,use_srv=True):
|
|
|
|
''' Caches proxy and target addresses.
|
|
|
|
'proxy' argument is a dictionary with mandatory keys 'host' and 'port' (proxy address)
|
|
|
|
and optional keys 'user' and 'password' to use for authentication.
|
|
|
|
'server' argument is a tuple of host and port - just like TCPsocket uses. '''
|
|
|
|
self.on_connect_proxy = on_connect
|
|
|
|
self.on_connect_failure = on_connect_failure
|
|
|
|
NonBlockingTcp.__init__(self, self._on_tcp_connect, on_connect_failure, server, use_srv)
|
|
|
|
self.DBG_LINE=DBG_CONNECT_PROXY
|
|
|
|
self.server = server
|
|
|
|
self.proxy=proxy
|
|
|
|
|
|
|
|
def plugin(self, owner):
|
|
|
|
''' Starts connection. Used interally. Returns non-empty string on success.'''
|
|
|
|
owner.debug_flags.append(DBG_CONNECT_PROXY)
|
|
|
|
NonBlockingTcp.plugin(self,owner)
|
|
|
|
|
|
|
|
def connect(self,dupe=None):
|
|
|
|
''' Starts connection. Connects to proxy, supplies login and password to it
|
|
|
|
(if were specified while creating instance). Instructs proxy to make
|
|
|
|
connection to the target server. Returns non-empty sting on success. '''
|
|
|
|
NonBlockingTcp.connect(self, (self.proxy['host'], self.proxy['port']))
|
|
|
|
|
|
|
|
def _on_tcp_connect(self):
|
|
|
|
self.DEBUG('Proxy server contacted, performing authentification','start')
|
|
|
|
connector = ['CONNECT %s:%s HTTP/1.0'%self.server,
|
|
|
|
'Proxy-Connection: Keep-Alive',
|
|
|
|
'Pragma: no-cache',
|
|
|
|
'Host: %s:%s'%self.server,
|
|
|
|
'User-Agent: HTTPPROXYsocket/v0.1']
|
|
|
|
if self.proxy.has_key('user') and self.proxy.has_key('password'):
|
|
|
|
credentials = '%s:%s' % ( self.proxy['user'], self.proxy['password'])
|
|
|
|
credentials = base64.encodestring(credentials).strip()
|
|
|
|
connector.append('Proxy-Authorization: Basic '+credentials)
|
|
|
|
connector.append('\r\n')
|
|
|
|
self.onreceive(self._on_headers_sent)
|
|
|
|
self.send('\r\n'.join(connector))
|
|
|
|
|
|
|
|
def _on_headers_sent(self, reply):
|
|
|
|
if reply is None:
|
|
|
|
return
|
|
|
|
self.reply = reply.replace('\r', '')
|
|
|
|
try:
|
|
|
|
proto, code, desc = reply.split('\n')[0].split(' ', 2)
|
|
|
|
except:
|
2006-12-20 20:40:08 +00:00
|
|
|
log.error("_on_headers_sent:", exc_info=True)
|
|
|
|
#traceback.print_exc()
|
2006-02-03 12:17:34 +00:00
|
|
|
raise error('Invalid proxy reply')
|
|
|
|
if code <> '200':
|
|
|
|
self.DEBUG('Invalid proxy reply: %s %s %s' % (proto, code, desc),'error')
|
|
|
|
self._owner.disconnected()
|
|
|
|
return
|
|
|
|
self.onreceive(self._on_proxy_auth)
|
|
|
|
|
|
|
|
def _on_proxy_auth(self, reply):
|
|
|
|
if self.reply.find('\n\n') == -1:
|
|
|
|
if reply is None:
|
|
|
|
return
|
|
|
|
if reply.find('\n\n') == -1:
|
|
|
|
self.reply += reply.replace('\r', '')
|
|
|
|
return
|
|
|
|
self.DEBUG('Authentification successfull. Jabber server contacted.','ok')
|
|
|
|
if self.on_connect_proxy:
|
|
|
|
self.on_connect_proxy()
|
|
|
|
|
|
|
|
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)
|