- TLS classes refactored - NonBlockingTLS is now plugged to NonBlockingTCP and

derived (was plugged to NonBlockingClient which made it unusable for BOSH)
- Fixed HTTP CONNECT proxy socket
- Implemented workaround for the bug with insecure-connection warning dialog
  (unfortunately, this is not over - I just forbid the transport to send
  BOSH empty bodies until auth module is plugged, which is wrong and will
  break if user will wait more than "inactivity" (usualy thirty) seconds
  before clicking the dialog.
  This workaround works with ejb and opf, and also breaks connection with
  both of them if delay is too long.
- Implemented basic TLS over BOSH. It works only with OPF and poorly.
This commit is contained in:
tomk 2008-08-05 23:52:35 +00:00
parent 56e0ad7a96
commit cbfa9d97df
7 changed files with 345 additions and 316 deletions

View File

@ -397,7 +397,7 @@ class Connection(ConnectionHandlers):
def connect(self, data = None): def connect(self, data = None):
''' Start a connection to the Jabber server. ''' Start a connection to the Jabber server.
Returns connection, and connection type ('tls', 'ssl', 'tcp', '') Returns connection, and connection type ('tls', 'ssl', 'plain', '')
data MUST contain hostname, usessl, proxy, use_custom_host, data MUST contain hostname, usessl, proxy, use_custom_host,
custom_host (if use_custom_host), custom_port (if use_custom_host)''' custom_host (if use_custom_host), custom_port (if use_custom_host)'''
if self.connection: if self.connection:
@ -410,9 +410,11 @@ class Connection(ConnectionHandlers):
p = data['proxy'] p = data['proxy']
use_srv = True use_srv = True
use_custom = data['use_custom_host'] use_custom = data['use_custom_host']
print 'use_custom = %s' % use_custom
if use_custom: if use_custom:
custom_h = data['custom_host'] custom_h = data['custom_host']
custom_p = data['custom_port'] custom_p = data['custom_port']
print 'custom_port = %s' % custom_p
else: else:
hostname = gajim.config.get_per('accounts', self.name, 'hostname') hostname = gajim.config.get_per('accounts', self.name, 'hostname')
usessl = gajim.config.get_per('accounts', self.name, 'usessl') usessl = gajim.config.get_per('accounts', self.name, 'usessl')
@ -422,8 +424,10 @@ class Connection(ConnectionHandlers):
use_srv = gajim.config.get_per('accounts', self.name, 'use_srv') use_srv = gajim.config.get_per('accounts', self.name, 'use_srv')
use_custom = gajim.config.get_per('accounts', self.name, use_custom = gajim.config.get_per('accounts', self.name,
'use_custom_host') 'use_custom_host')
print 'use_custom = %s' % use_custom
custom_h = gajim.config.get_per('accounts', self.name, 'custom_host') custom_h = gajim.config.get_per('accounts', self.name, 'custom_host')
custom_p = gajim.config.get_per('accounts', self.name, 'custom_port') custom_p = gajim.config.get_per('accounts', self.name, 'custom_port')
print 'custom_port = %s' % custom_p
# create connection if it doesn't already exist # create connection if it doesn't already exist
self.connected = 1 self.connected = 1
@ -502,68 +506,6 @@ class Connection(ConnectionHandlers):
i['ssl_port'] = ssl_p i['ssl_port'] = ssl_p
self.connect_to_next_host() self.connect_to_next_host()
def on_proxy_failure(self, reason):
log.error('Connection to proxy failed: %s' % reason)
self.time_to_reconnect = None
self.on_connect_failure = None
self.disconnect(on_purpose = True)
self.dispatch('STATUS', 'offline')
self.dispatch('CONNECTION_LOST',
(_('Connection to proxy failed'), reason))
def connect_to_next_type(self, retry=False):
if len(self._connection_types):
self._current_type = self._connection_types.pop(0)
if self.last_connection:
self.last_connection.socket.disconnect()
self.last_connection = None
self.connection = None
if self._current_type == 'ssl':
# SSL (force TLS on different port than plain)
port = self._current_host['ssl_port']
secure = 'force'
else:
port = self._current_host['port']
if self._current_type == 'plain':
# plain connection
secure = None
else:
# TLS (on the same port as plain)
secure = 'negotiate'
con = common.xmpp.NonBlockingClient(
domain=self._hostname,
caller=self,
idlequeue=gajim.idlequeue)
self.last_connection = con
# increase default timeout for server responses
common.xmpp.dispatcher_nb.DEFAULT_TIMEOUT_SECONDS = self.try_connecting_for_foo_secs
# FIXME: this is a hack; need a better way
if self.on_connect_success == self._on_new_account:
con.RegisterDisconnectHandler(self._on_new_account)
# FIXME: BOSH properties should be loaded from config
#if self._proxy and self._proxy['type'] == 'bosh':
# self._proxy['bosh_hold'] = '2'
# self._proxy['bosh_wait'] = '10'
# self._proxy['bosh_content'] = 'text/xml; charset=utf-8'
# self._proxy['wait_for_restart_response'] = False
log.info('Connecting to %s: [%s:%d]', self.name,
self._current_host['host'], port)
con.connect(
hostname=self._current_host['host'],
port=port,
on_connect=self.on_connect_success,
on_proxy_failure=self.on_proxy_failure,
on_connect_failure=self.connect_to_next_type,
proxy=self._proxy,
secure = secure)
else:
self.connect_to_next_host(retry)
def connect_to_next_host(self, retry = False): def connect_to_next_host(self, retry = False):
if len(self._hosts): if len(self._hosts):
@ -573,10 +515,14 @@ class Connection(ConnectionHandlers):
'connection_types').split() 'connection_types').split()
else: else:
self._connection_types = ['tls', 'ssl', 'plain'] self._connection_types = ['tls', 'ssl', 'plain']
#THEHACK
#self._connection_types = ['ssl', 'plain']
# FIXME: remove after tls and ssl will be degubbed if self._proxy and self._proxy['type']=='bosh':
self._connection_types = ['plain'] # with BOSH, we can't do TLS negotiation with <starttls>, we do only "plain"
# connection and TLS with handshake right after TCP connecting ("ssl")
try: self._connection_types.remove('tls')
except ValueError: pass
host = self.select_next_host(self._hosts) host = self.select_next_host(self._hosts)
self._current_host = host self._current_host = host
@ -597,6 +543,55 @@ class Connection(ConnectionHandlers):
# try reconnect if connection has failed before auth to server # try reconnect if connection has failed before auth to server
self._disconnectedReconnCB() self._disconnectedReconnCB()
def connect_to_next_type(self, retry=False):
if len(self._connection_types):
self._current_type = self._connection_types.pop(0)
if self.last_connection:
self.last_connection.socket.disconnect()
self.last_connection = None
self.connection = None
if self._current_type == 'ssl':
# SSL (force TLS on different port than plain)
port = self._current_host['ssl_port']
elif self._current_type == 'tls':
# TLS - negotiate tls after XMPP stream is estabilished
port = self._current_host['port']
elif self._current_type == 'plain':
# plain connection on defined port
port = self._current_host['port']
cacerts = os.path.join(common.gajim.DATA_DIR, 'other', 'cacerts.pem')
mycerts = common.gajim.MY_CACERTS
secure_tuple = (self._current_type, cacerts, mycerts)
con = common.xmpp.NonBlockingClient(
domain=self._hostname,
caller=self,
idlequeue=gajim.idlequeue)
self.last_connection = con
# increase default timeout for server responses
common.xmpp.dispatcher_nb.DEFAULT_TIMEOUT_SECONDS = self.try_connecting_for_foo_secs
# FIXME: this is a hack; need a better way
if self.on_connect_success == self._on_new_account:
con.RegisterDisconnectHandler(self._on_new_account)
log.info('Connecting to %s: [%s:%d]', self.name,
self._current_host['host'], port)
print secure_tuple
con.connect(
hostname=self._current_host['host'],
port=port,
on_connect=self.on_connect_success,
on_proxy_failure=self.on_proxy_failure,
on_connect_failure=self.connect_to_next_type,
proxy=self._proxy,
secure_tuple = secure_tuple)
else:
self.connect_to_next_host(retry)
def _connect_failure(self, con_type = None): def _connect_failure(self, con_type = None):
if not con_type: if not con_type:
# we are not retrying, and not conecting # we are not retrying, and not conecting
@ -607,14 +602,21 @@ class Connection(ConnectionHandlers):
(_('Could not connect to "%s"') % self._hostname, (_('Could not connect to "%s"') % self._hostname,
_('Check your connection or try again later.'))) _('Check your connection or try again later.')))
def on_proxy_failure(self, reason):
log.error('Connection to proxy failed: %s' % reason)
self.time_to_reconnect = None
self.on_connect_failure = None
self.disconnect(on_purpose = True)
self.dispatch('STATUS', 'offline')
self.dispatch('CONNECTION_LOST',
(_('Connection to proxy failed'), reason))
def _connect_success(self, con, con_type): def _connect_success(self, con, con_type):
if not self.connected: # We went offline during connecting process if not self.connected: # We went offline during connecting process
# FIXME - not possible, maybe it was when we used threads # FIXME - not possible, maybe it was when we used threads
return return
_con_type = con_type _con_type = con_type
# xmpp returns 'tcp', but we set 'plain' in connection_types in config
if _con_type == 'tcp':
_con_type = 'plain'
if _con_type != self._current_type: if _con_type != self._current_type:
log.info('Connecting to next type beacuse desired is %s and returned is %s' log.info('Connecting to next type beacuse desired is %s and returned is %s'
% (self._current_type, _con_type)) % (self._current_type, _con_type))
@ -676,7 +678,7 @@ class Connection(ConnectionHandlers):
def plain_connection_accepted(self): def plain_connection_accepted(self):
name = gajim.config.get_per('accounts', self.name, 'name') name = gajim.config.get_per('accounts', self.name, 'name')
self._register_handlers(self.connection, 'tcp') self._register_handlers(self.connection, 'plain')
self.connection.auth(name, self.password, self.server_resource, 1, self.connection.auth(name, self.password, self.server_resource, 1,
self.__on_auth) self.__on_auth)

View File

@ -207,7 +207,7 @@ class ConnectionBytestream:
iq.setID(file_props['request-id']) iq.setID(file_props['request-id'])
query = iq.setTag('query') query = iq.setTag('query')
query.setNamespace(common.xmpp.NS_BYTESTREAM) query.setNamespace(common.xmpp.NS_BYTESTREAM)
query.setAttr('mode', 'tcp') query.setAttr('mode', 'plain')
query.setAttr('sid', file_props['sid']) query.setAttr('sid', file_props['sid'])
for ft_host in ft_add_hosts: for ft_host in ft_add_hosts:
# The streamhost, if set # The streamhost, if set

View File

@ -19,9 +19,10 @@ In TCP-derived transports it is file descriptor of socket'''
class NonBlockingBOSH(NonBlockingTransport): class NonBlockingBOSH(NonBlockingTransport):
def __init__(self, raise_event, on_disconnect, idlequeue, xmpp_server, domain, def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls, certs,
bosh_dict, proxy_creds): xmpp_server, domain, bosh_dict, proxy_creds):
NonBlockingTransport.__init__(self, raise_event, on_disconnect, idlequeue) NonBlockingTransport.__init__(self, raise_event, on_disconnect, idlequeue,
estabilish_tls, certs)
self.bosh_sid = None self.bosh_sid = None
if locale.getdefaultlocale()[0]: if locale.getdefaultlocale()[0]:
@ -37,12 +38,19 @@ class NonBlockingBOSH(NonBlockingTransport):
self.route_host, self.route_port = xmpp_server self.route_host, self.route_port = xmpp_server
self.bosh_wait = bosh_dict['bosh_wait'] self.bosh_wait = bosh_dict['bosh_wait']
self.bosh_hold = bosh_dict['bosh_hold'] if not self.http_pipelining:
self.bosh_hold = 1
else:
self.bosh_hold = bosh_dict['bosh_hold']
self.bosh_requests = self.bosh_hold self.bosh_requests = self.bosh_hold
self.bosh_uri = bosh_dict['bosh_uri'] self.bosh_uri = bosh_dict['bosh_uri']
self.bosh_port = bosh_dict['bosh_port'] self.bosh_port = bosh_dict['bosh_port']
self.bosh_content = bosh_dict['bosh_content'] self.bosh_content = bosh_dict['bosh_content']
self.over_proxy = bosh_dict['bosh_useproxy'] self.over_proxy = bosh_dict['bosh_useproxy']
if estabilish_tls:
self.bosh_secure = 'true'
else:
self.bosh_secure = 'false'
self.use_proxy_auth = bosh_dict['useauth'] self.use_proxy_auth = bosh_dict['useauth']
self.proxy_creds = proxy_creds self.proxy_creds = proxy_creds
self.wait_cb_time = None self.wait_cb_time = None
@ -55,7 +63,6 @@ class NonBlockingBOSH(NonBlockingTransport):
self.ack_checker = None self.ack_checker = None
self.after_init = False self.after_init = False
# if proxy_host .. do sth about HTTP proxy etc.
def connect(self, conn_5tuple, on_connect, on_connect_failure): def connect(self, conn_5tuple, on_connect, on_connect_failure):
NonBlockingTransport.connect(self, conn_5tuple, on_connect, on_connect_failure) NonBlockingTransport.connect(self, conn_5tuple, on_connect, on_connect_failure)
@ -72,7 +79,7 @@ class NonBlockingBOSH(NonBlockingTransport):
self.after_init = True self.after_init = True
self.http_socks.append(self.get_new_http_socket()) self.http_socks.append(self.get_new_http_socket())
self.tcp_connection_started() self.tcp_connecting_started()
# following connect() is not necessary because sockets can be connected on # following connect() is not necessary because sockets can be connected on
# send but we need to know if host is reachable in order to invoke callback # send but we need to know if host is reachable in order to invoke callback
@ -80,14 +87,21 @@ class NonBlockingBOSH(NonBlockingTransport):
# for errors occurring after connection is etabilished) # for errors occurring after connection is etabilished)
self.http_socks[0].connect( self.http_socks[0].connect(
conn_5tuple = conn_5tuple, conn_5tuple = conn_5tuple,
on_connect = lambda: self._on_connect(self.http_socks[0]), on_connect = lambda: self._on_connect(),
on_connect_failure = self._on_connect_failure) on_connect_failure = self._on_connect_failure)
def _on_connect(self):
self.peerhost = self.http_socks[0].peerhost
self.ssl_lib = self.http_socks[0].ssl_lib
NonBlockingTransport._on_connect(self)
def set_timeout(self, timeout): def set_timeout(self, timeout):
if self.state in [CONNECTING, CONNECTED] and self.fd != -1: if self.get_state() in [CONNECTING, CONNECTED] and self.fd != -1:
NonBlockingTransport.set_timeout(self, timeout) NonBlockingTransport.set_timeout(self, timeout)
else: else:
log.warn('set_timeout: TIMEOUT NOT SET: state is %s, fd is %s' % (self.state, self.fd)) log.warn('set_timeout: TIMEOUT NOT SET: state is %s, fd is %s' % (self.get_state(), self.fd))
def on_http_request_possible(self): def on_http_request_possible(self):
''' '''
@ -95,17 +109,25 @@ class NonBlockingBOSH(NonBlockingTransport):
There should be always one pending request on BOSH CM. There should be always one pending request on BOSH CM.
''' '''
log.info('on_http_req possible, state:\n%s' % self.get_current_state()) log.info('on_http_req possible, state:\n%s' % self.get_current_state())
if self.state == DISCONNECTING: if self.get_state() == DISCONNECTING:
self.disconnect() self.disconnect()
return return
self.send_BOSH(None)
print 'SSSSSSSSSSEEEEEEEEEND'
if hasattr(self._owner, 'NonBlockingNonSASL') or hasattr(self._owner, 'SASL'):
#FIXME: Hack for making the non-secure warning dialog work
self.send_BOSH(None)
else:
self.http_socks[0]._plug_idle(writable=False, readable=True)
return
def get_socket_in(self, state): def get_socket_in(self, state):
for s in self.http_socks: for s in self.http_socks:
if s.state==state: return s if s.get_state()==state: return s
return None return None
def get_free_socket(self): def get_free_socket(self):
if self.http_pipelining: if self.http_pipelining:
assert( len(self.http_socks) == 1 ) assert( len(self.http_socks) == 1 )
@ -114,8 +136,8 @@ class NonBlockingBOSH(NonBlockingTransport):
last_recv_time, tmpsock = 0, None last_recv_time, tmpsock = 0, None
for s in self.http_socks: for s in self.http_socks:
# we're interested only into CONNECTED socket with no req pending # we're interested only into CONNECTED socket with no req pending
if s.state==CONNECTED and s.pending_requests==0: if s.get_state()==CONNECTED and s.pending_requests==0:
# if there's more of them, we want the one with less recent data receive # if there's more of them, we want the one with the least recent data receive
# (lowest last_recv_time) # (lowest last_recv_time)
if (last_recv_time==0) or (s.last_recv_time < last_recv_time): if (last_recv_time==0) or (s.last_recv_time < last_recv_time):
last_recv_time = s.last_recv_time last_recv_time = s.last_recv_time
@ -129,13 +151,14 @@ class NonBlockingBOSH(NonBlockingTransport):
def send_BOSH(self, payload): def send_BOSH(self, payload):
total_pending_reqs = sum([s.pending_requests for s in self.http_socks]) total_pending_reqs = sum([s.pending_requests for s in self.http_socks])
# when called after HTTP response when there are some pending requests and # when called after HTTP response (Payload=None) and when there are already
# no data to send, we do nothing and disccard the payload # some pending requests and no data to send, or when the socket is
# disconnected, we do nothing
if payload is None and \ if payload is None and \
total_pending_reqs > 0 and \ total_pending_reqs > 0 and \
self.stanza_buffer == [] and \ self.stanza_buffer == [] and \
self.prio_bosh_stanzas == [] or \ self.prio_bosh_stanzas == [] or \
self.state==DISCONNECTED: self.get_state()==DISCONNECTED:
return return
# now the payload is put to buffer and will be sent at some point # now the payload is put to buffer and will be sent at some point
@ -144,7 +167,7 @@ class NonBlockingBOSH(NonBlockingTransport):
# if we're about to make more requests than allowed, we don't send - stanzas will be # if we're about to make more requests than allowed, we don't send - stanzas will be
# sent after HTTP response from CM, exception is when we're disconnecting - then we # sent after HTTP response from CM, exception is when we're disconnecting - then we
# send anyway # send anyway
if total_pending_reqs >= self.bosh_requests and self.state!=DISCONNECTING: if total_pending_reqs >= self.bosh_requests and self.get_state()!=DISCONNECTING:
log.warn('attemp to make more requests than allowed by Connection Manager:\n%s' % log.warn('attemp to make more requests than allowed by Connection Manager:\n%s' %
self.get_current_state()) self.get_current_state())
return return
@ -232,17 +255,16 @@ class NonBlockingBOSH(NonBlockingTransport):
self.remove_bosh_wait_timeout() self.remove_bosh_wait_timeout()
if self.after_init: if self.after_init:
self.after_init = False
if stanza_attrs.has_key('sid'): if stanza_attrs.has_key('sid'):
# session ID should be only in init response # session ID should be only in init response
self.bosh_sid = stanza_attrs['sid'] self.bosh_sid = stanza_attrs['sid']
if stanza_attrs.has_key('requests'): if stanza_attrs.has_key('requests'):
#self.bosh_requests = int(stanza_attrs['requests']) self.bosh_requests = int(stanza_attrs['requests'])
self.bosh_requests = int(stanza_attrs['wait'])
if stanza_attrs.has_key('wait'): if stanza_attrs.has_key('wait'):
self.bosh_wait = int(stanza_attrs['wait']) self.bosh_wait = int(stanza_attrs['wait'])
self.after_init = False
ack = None ack = None
if stanza_attrs.has_key('ack'): if stanza_attrs.has_key('ack'):
@ -267,13 +289,12 @@ class NonBlockingBOSH(NonBlockingTransport):
def append_stanza(self, stanza): def append_stanza(self, stanza):
if stanza: if stanza:
if isinstance(stanza, tuple): if isinstance(stanza, tuple):
# tuple of BOSH stanza and True/False for whether to add payload # stanza is tuple of BOSH stanza and bool value for whether to add payload
self.prio_bosh_stanzas.append(stanza) self.prio_bosh_stanzas.append(stanza)
else: else:
self.stanza_buffer.append(stanza) self.stanza_buffer.append(stanza)
def send(self, stanza, now=False): def send(self, stanza, now=False):
# body tags should be send only via send_BOSH() # body tags should be send only via send_BOSH()
assert(not isinstance(stanza, BOSHBody)) assert(not isinstance(stanza, BOSHBody))
@ -284,7 +305,7 @@ class NonBlockingBOSH(NonBlockingTransport):
def get_current_state(self): def get_current_state(self):
t = '------ SOCKET_ID\tSOCKET_STATE\tPENDING_REQS\n' t = '------ SOCKET_ID\tSOCKET_STATE\tPENDING_REQS\n'
for s in self.http_socks: for s in self.http_socks:
t = '%s------ %s\t%s\t%s\n' % (t,id(s), s.state, s.pending_requests) t = '%s------ %s\t%s\t%s\n' % (t,id(s), s.get_state(), s.pending_requests)
t = '%s------ prio stanzas: %s, queued XMPP stanzas: %s, not_acked stanzas: %s' \ t = '%s------ prio stanzas: %s, queued XMPP stanzas: %s, not_acked stanzas: %s' \
% (t, self.prio_bosh_stanzas, self.stanza_buffer, % (t, self.prio_bosh_stanzas, self.stanza_buffer,
self.ack_checker.get_not_acked_rids()) self.ack_checker.get_not_acked_rids())
@ -294,7 +315,7 @@ class NonBlockingBOSH(NonBlockingTransport):
def connect_and_flush(self, socket): def connect_and_flush(self, socket):
socket.connect( socket.connect(
conn_5tuple = self.conn_5tuple, conn_5tuple = self.conn_5tuple,
on_connect = lambda :self.send_BOSH(None), on_connect = self.on_http_request_possible,
on_connect_failure = self.disconnect) on_connect_failure = self.disconnect)
@ -313,6 +334,7 @@ class NonBlockingBOSH(NonBlockingTransport):
'sid': self.bosh_sid, 'sid': self.bosh_sid,
'xml:lang': self.bosh_xml_lang, 'xml:lang': self.bosh_xml_lang,
'xmpp:restart': 'true', 'xmpp:restart': 'true',
'secure': self.bosh_secure,
'xmlns:xmpp': 'urn:xmpp:xbosh'}) 'xmlns:xmpp': 'urn:xmpp:xbosh'})
else: else:
t = BOSHBody( t = BOSHBody(
@ -347,6 +369,8 @@ class NonBlockingBOSH(NonBlockingTransport):
raise_event=self.raise_event, raise_event=self.raise_event,
on_disconnect=self.disconnect, on_disconnect=self.disconnect,
idlequeue = self.idlequeue, idlequeue = self.idlequeue,
estabilish_tls = self.estabilish_tls,
certs = self.certs,
on_http_request_possible = self.on_http_request_possible, on_http_request_possible = self.on_http_request_possible,
http_dict = http_dict, http_dict = http_dict,
on_persistent_fallback = self.on_persistent_fallback) on_persistent_fallback = self.on_persistent_fallback)
@ -368,7 +392,7 @@ class NonBlockingBOSH(NonBlockingTransport):
def disconnect(self, do_callback=True): def disconnect(self, do_callback=True):
self.remove_bosh_wait_timeout() self.remove_bosh_wait_timeout()
if self.state == DISCONNECTED: return if self.get_state() == DISCONNECTED: return
self.fd = -1 self.fd = -1
for s in self.http_socks: for s in self.http_socks:
s.disconnect(do_callback=False) s.disconnect(do_callback=False)

View File

@ -26,6 +26,8 @@ import socket
import transports_nb, tls_nb, dispatcher_nb, auth_nb, roster_nb, protocol, bosh import transports_nb, tls_nb, dispatcher_nb, auth_nb, roster_nb, protocol, bosh
from client import * from client import *
from protocol import NS_TLS
import logging import logging
log = logging.getLogger('gajim.c.x.client_nb') log = logging.getLogger('gajim.c.x.client_nb')
@ -71,7 +73,6 @@ class NBCommonClient:
self.connected='' self.connected=''
log.debug('Client disconnected..') log.debug('Client disconnected..')
print 'ffffffffffffffffff'
for i in reversed(self.disconnect_handlers): for i in reversed(self.disconnect_handlers):
log.debug('Calling disconnect handler %s' % i) log.debug('Calling disconnect handler %s' % i)
i() i()
@ -86,9 +87,9 @@ class NBCommonClient:
if self.__dict__.has_key('NonBlockingTLS'): if self.__dict__.has_key('NonBlockingTLS'):
self.NonBlockingTLS.PlugOut() self.NonBlockingTLS.PlugOut()
if self.__dict__.has_key('NBHTTPProxySocket'): if self.__dict__.has_key('NBHTTPProxySocket'):
self.NBHTTPPROXYsocket.PlugOut() self.NBHTTPProxySocket.PlugOut()
if self.__dict__.has_key('NBSOCKS5ProxySocket'): if self.__dict__.has_key('NBSOCKS5ProxySocket'):
self.NBSOCKS5PROXYsocket.PlugOut() self.NBSOCKS5ProxySocket.PlugOut()
if self.__dict__.has_key('NonBlockingTCP'): if self.__dict__.has_key('NonBlockingTCP'):
self.NonBlockingTCP.PlugOut() self.NonBlockingTCP.PlugOut()
if self.__dict__.has_key('NonBlockingHTTP'): if self.__dict__.has_key('NonBlockingHTTP'):
@ -98,7 +99,7 @@ class NBCommonClient:
def connect(self, on_connect, on_connect_failure, hostname=None, port=5222, def connect(self, on_connect, on_connect_failure, hostname=None, port=5222,
on_proxy_failure=None, proxy=None, secure=None): on_proxy_failure=None, proxy=None, secure_tuple=None):
''' '''
Open XMPP connection (open XML streams in both directions). Open XMPP connection (open XML streams in both directions).
:param hostname: hostname of XMPP server from SRV request :param hostname: hostname of XMPP server from SRV request
@ -110,17 +111,15 @@ class NBCommonClient:
:param proxy: dictionary with proxy data. It should contain at least values :param proxy: dictionary with proxy data. It should contain at least values
for keys 'host' and 'port' - connection details for proxy server and for keys 'host' and 'port' - connection details for proxy server and
optionally keys 'user' and 'pass' as proxy credentials optionally keys 'user' and 'pass' as proxy credentials
:param secure: :param secure_tuple:
''' '''
self.on_connect = on_connect self.on_connect = on_connect
self.on_connect_failure=on_connect_failure self.on_connect_failure=on_connect_failure
self.on_proxy_failure = on_proxy_failure self.on_proxy_failure = on_proxy_failure
self._secure = secure self.secure, self.cacerts, self.mycerts = secure_tuple
self.Connection = None self.Connection = None
self.Port = port self.Port = port
def _resolve_hostname(self, hostname, port, on_success, on_failure): def _resolve_hostname(self, hostname, port, on_success, on_failure):
@ -147,7 +146,7 @@ class NBCommonClient:
self.current_ip = self.ip_addresses.pop(0) self.current_ip = self.ip_addresses.pop(0)
self.socket.connect( self.socket.connect(
conn_5tuple=self.current_ip, conn_5tuple=self.current_ip,
on_connect=lambda: self._xmpp_connect(socket_type='tcp'), on_connect=lambda: self._xmpp_connect(socket_type='plain'),
on_connect_failure=self._try_next_ip) on_connect_failure=self._try_next_ip)
@ -159,6 +158,8 @@ class NBCommonClient:
return None return None
def _xmpp_connect(self, socket_type): def _xmpp_connect(self, socket_type):
if socket_type == 'plain' and self.Connection.ssl_lib:
socket_type = 'ssl'
self.connected = socket_type self.connected = socket_type
self._xmpp_connect_machine() self._xmpp_connect_machine()
@ -169,7 +170,6 @@ class NBCommonClient:
and features tag handling. Calls _on_stream_start when stream is and features tag handling. Calls _on_stream_start when stream is
started, and _on_connect_failure on failure. started, and _on_connect_failure on failure.
''' '''
#FIXME: use RegisterHandlerOnce instead of onreceive
log.info('-------------xmpp_connect_machine() >> mode: %s, data: %s' % (mode,str(data)[:20] )) log.info('-------------xmpp_connect_machine() >> mode: %s, data: %s' % (mode,str(data)[:20] ))
def on_next_receive(mode): def on_next_receive(mode):
@ -181,6 +181,7 @@ class NBCommonClient:
if not mode: if not mode:
# starting state # starting state
if self.__dict__.has_key('Dispatcher'): self.Dispatcher.PlugOut()
d=dispatcher_nb.Dispatcher().PlugIn(self) d=dispatcher_nb.Dispatcher().PlugIn(self)
on_next_receive('RECEIVE_DOCUMENT_ATTRIBUTES') on_next_receive('RECEIVE_DOCUMENT_ATTRIBUTES')
@ -222,11 +223,38 @@ class NBCommonClient:
elif mode == 'STREAM_STARTED': elif mode == 'STREAM_STARTED':
self._on_stream_start() self._on_stream_start()
def _tls_negotiation_handler(self, con=None, tag=None):
log.info('-------------tls_negotiaton_handler() >> tag: %s' % tag)
if not con and not tag:
# starting state when we send the <starttls>
self.RegisterHandlerOnce('proceed', self._tls_negotiation_handler,
xmlns=NS_TLS)
self.RegisterHandlerOnce('failure', self._tls_negotiation_handler,
xmlns=NS_TLS)
self.send('<starttls xmlns="%s"/>' % NS_TLS)
else:
if tag.getNamespace() <> NS_TLS:
self._on_connect_failure('Unknown namespace: %s' % tag.getNamespace())
return
tagname = tag.getName()
if tagname == 'failure':
self._on_connect_failure('TLS <failure> received: %s' % tag)
return
log.info('Got starttls proceed response. Switching to TLS/SSL...')
# following call wouldn't work for BOSH transport but it doesn't matter
# because TLS negotiation with BOSH is forbidden
self.Connection.tls_init(
on_succ = lambda: self._xmpp_connect(socket_type='tls'),
on_fail = lambda: self._on_connect_failure('error while etabilishing TLS'))
def _on_stream_start(self): def _on_stream_start(self):
'''Called when stream is opened. To be overriden in derived classes.''' '''Called when stream is opened. To be overriden in derived classes.'''
def _on_connect_failure(self, retry=None, err_message=None): def _on_connect_failure(self, retry=None, err_message=None):
self.connected = None self.connected = ''
if err_message: if err_message:
log.debug('While connecting: %s' % err_message) log.debug('While connecting: %s' % err_message)
if self.socket: if self.socket:
@ -234,6 +262,10 @@ class NBCommonClient:
self.on_connect_failure(retry) self.on_connect_failure(retry)
def _on_connect(self): def _on_connect(self):
if self.secure == 'tls':
self._on_connect_failure('uaaaaaa')
return
print 'self.secure = %s' % self.secure
self.onreceive(None) self.onreceive(None)
self.on_connect(self, self.connected) self.on_connect(self, self.connected)
@ -243,7 +275,7 @@ class NBCommonClient:
self.Dispatcher.Event('', event_type, data) self.Dispatcher.Event('', event_type, data)
# moved from client.CommonClient: # moved from client.CommonClient (blocking client from xmpppy):
def RegisterDisconnectHandler(self,handler): def RegisterDisconnectHandler(self,handler):
""" Register handler that will be called on disconnect.""" """ Register handler that will be called on disconnect."""
self.disconnect_handlers.append(handler) self.disconnect_handlers.append(handler)
@ -259,7 +291,7 @@ class NBCommonClient:
raise IOError('Disconnected from server.') raise IOError('Disconnected from server.')
def get_connect_type(self): def get_connect_type(self):
""" Returns connection state. F.e.: None / 'tls' / 'tcp+non_sasl' . """ """ Returns connection state. F.e.: None / 'tls' / 'plain+non_sasl' . """
return self.connected return self.connected
def get_peerhost(self): def get_peerhost(self):
@ -267,8 +299,7 @@ class NBCommonClient:
to the server , (e.g. me). to the server , (e.g. me).
We will create listening socket on the same ip ''' We will create listening socket on the same ip '''
# FIXME: tuple (ip, port) is expected (and checked for) but port num is useless # FIXME: tuple (ip, port) is expected (and checked for) but port num is useless
if hasattr(self, 'socket'): return self.socket.peerhost
return self.socket.peerhost
def auth(self, user, password, resource = '', sasl = 1, on_auth = None): def auth(self, user, password, resource = '', sasl = 1, on_auth = None):
@ -372,16 +403,21 @@ class NonBlockingClient(NBCommonClient):
self.protocol_type = 'XMPP' self.protocol_type = 'XMPP'
def connect(self, on_connect, on_connect_failure, hostname=None, port=5222, def connect(self, on_connect, on_connect_failure, hostname=None, port=5222,
on_proxy_failure=None, proxy=None, secure=None): on_proxy_failure=None, proxy=None, secure_tuple=None):
NBCommonClient.connect(self, on_connect, on_connect_failure, hostname, port, NBCommonClient.connect(self, on_connect, on_connect_failure, hostname, port,
on_proxy_failure, proxy, secure) on_proxy_failure, proxy, secure_tuple)
if hostname: if hostname:
xmpp_hostname = hostname xmpp_hostname = hostname
else: else:
xmpp_hostname = self.Server xmpp_hostname = self.Server
estabilish_tls = self.secure == 'ssl'
certs = (self.cacerts, self.mycerts)
self._on_tcp_failure = self._on_connect_failure
if proxy: if proxy:
# with proxies, client connects to proxy instead of directly to # with proxies, client connects to proxy instead of directly to
# XMPP server ((hostname, port)) # XMPP server ((hostname, port))
@ -390,13 +426,14 @@ class NonBlockingClient(NBCommonClient):
tcp_host, tcp_port, proxy_user, proxy_pass = \ tcp_host, tcp_port, proxy_user, proxy_pass = \
transports_nb.get_proxy_data_from_dict(proxy) transports_nb.get_proxy_data_from_dict(proxy)
self._on_tcp_failure = self.on_proxy_failure
if proxy['type'] == 'bosh': if proxy['type'] == 'bosh':
self.socket = bosh.NonBlockingBOSH( self.socket = bosh.NonBlockingBOSH(
on_disconnect = self.on_disconnect, on_disconnect = self.on_disconnect,
raise_event = self.raise_event, raise_event = self.raise_event,
idlequeue = self.idlequeue, idlequeue = self.idlequeue,
estabilish_tls = estabilish_tls,
certs = certs,
proxy_creds = (proxy_user, proxy_pass), proxy_creds = (proxy_user, proxy_pass),
xmpp_server = (xmpp_hostname, self.Port), xmpp_server = (xmpp_hostname, self.Port),
domain = self.Server, domain = self.Server,
@ -405,6 +442,7 @@ class NonBlockingClient(NBCommonClient):
self.wait_for_restart_response = proxy['bosh_wait_for_restart_response'] self.wait_for_restart_response = proxy['bosh_wait_for_restart_response']
else: else:
self._on_tcp_failure = self.on_proxy_failure
if proxy['type'] == 'socks5': if proxy['type'] == 'socks5':
proxy_class = transports_nb.NBSOCKS5ProxySocket proxy_class = transports_nb.NBSOCKS5ProxySocket
elif proxy['type'] == 'http': elif proxy['type'] == 'http':
@ -413,16 +451,19 @@ class NonBlockingClient(NBCommonClient):
on_disconnect = self.on_disconnect, on_disconnect = self.on_disconnect,
raise_event = self.raise_event, raise_event = self.raise_event,
idlequeue = self.idlequeue, idlequeue = self.idlequeue,
estabilish_tls = estabilish_tls,
certs = certs,
proxy_creds = (proxy_user, proxy_pass), proxy_creds = (proxy_user, proxy_pass),
xmpp_server = (xmpp_hostname, self.Port)) xmpp_server = (xmpp_hostname, self.Port))
else: else:
self._on_tcp_failure = self._on_connect_failure
tcp_host=xmpp_hostname tcp_host=xmpp_hostname
tcp_port=self.Port tcp_port=self.Port
self.socket = transports_nb.NonBlockingTCP( self.socket = transports_nb.NonBlockingTCP(
on_disconnect = self.on_disconnect, on_disconnect = self.on_disconnect,
raise_event = self.raise_event, raise_event = self.raise_event,
idlequeue = self.idlequeue) idlequeue = self.idlequeue,
estabilish_tls = estabilish_tls,
certs = certs)
self.socket.PlugIn(self) self.socket.PlugIn(self)
@ -434,31 +475,31 @@ class NonBlockingClient(NBCommonClient):
def _on_stream_start(self): def _on_stream_start(self):
''' '''
Called after XMPP stream is opened. Called after XMPP stream is opened.
In pure XMPP client, TLS negotiation may follow after esabilishing a stream. In pure XMPP client, TLS negotiation may follow after esabilishing a stream.
''' '''
self.onreceive(None) self.onreceive(None)
if self.connected == 'tcp': if self.connected == 'plain':
if not self.connected or not self._secure: if self.secure == 'plain':
# if we are disconnected or TLS/SSL is not desired, return # if we want plain connection, we're done now
self._on_connect() self._on_connect()
return return
if not self.Dispatcher.Stream.features.getTag('starttls'): if not self.Dispatcher.Stream.features.getTag('starttls'):
# if server doesn't advertise TLS in init response # if server doesn't advertise TLS in init response, we can't do more
log.warn('While connecting with type = "tls": TLS unsupported by remote server')
self._on_connect() self._on_connect()
return return
if self.incoming_stream_version() != '1.0': if self.incoming_stream_version() != '1.0':
# if stream version is less than 1.0, we can't do more
log.warn('While connecting with type = "tls": stream version is less than 1.0')
self._on_connect() self._on_connect()
return return
# otherwise start TLS # otherwise start TLS
tls_nb.NonBlockingTLS().PlugIn( log.info("TLS supported by remote server. Requesting TLS start.")
self, self._tls_negotiation_handler()
on_tls_success=lambda: self._xmpp_connect(socket_type='tls'), elif self.connected in ['ssl', 'tls']:
on_tls_failure=self._on_connect_failure)
elif self.connected == 'tls':
self._on_connect() self._on_connect()

View File

@ -27,24 +27,12 @@ import traceback
import logging import logging
log = logging.getLogger('gajim.c.x.tls_nb') log = logging.getLogger('gajim.c.x.tls_nb')
log.setLevel(logging.DEBUG)
# 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 USE_PYOPENSSL = False
PYOPENSSL = 'PYOPENSSL'
#TODO: add callback set from PlugIn for errors during runtime PYSTDLIB = 'PYSTDLIB'
# - sth like on_disconnect in socket wrappers
try: try:
#raise ImportError("Manually disabled PyOpenSSL") #raise ImportError("Manually disabled PyOpenSSL")
@ -235,6 +223,11 @@ class StdlibSSLWrapper(SSLWrapper):
class NonBlockingTLS(PlugIn): class NonBlockingTLS(PlugIn):
''' TLS connection used to encrypts already estabilished tcp connection.''' ''' TLS connection used to encrypts already estabilished tcp connection.'''
def __init__(self, cacerts, mycerts):
PlugIn.__init__(self)
self.cacerts = cacerts
self.mycerts = mycerts
# from ssl.h (partial extract) # from ssl.h (partial extract)
ssl_h_bits = { "SSL_ST_CONNECT": 0x1000, "SSL_ST_ACCEPT": 0x2000, ssl_h_bits = { "SSL_ST_CONNECT": 0x1000, "SSL_ST_ACCEPT": 0x2000,
"SSL_CB_LOOP": 0x01, "SSL_CB_EXIT": 0x02, "SSL_CB_LOOP": 0x01, "SSL_CB_EXIT": 0x02,
@ -242,56 +235,23 @@ class NonBlockingTLS(PlugIn):
"SSL_CB_ALERT": 0x4000, "SSL_CB_ALERT": 0x4000,
"SSL_CB_HANDSHAKE_START": 0x10, "SSL_CB_HANDSHAKE_DONE": 0x20} "SSL_CB_HANDSHAKE_START": 0x10, "SSL_CB_HANDSHAKE_DONE": 0x20}
def PlugIn(self, owner, on_tls_success, on_tls_failure, now=0): def PlugIn(self, owner):
''' 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'): start using encryption immediately
return # Already enabled. '''
log.info('Starting TLS estabilishing')
PlugIn.PlugIn(self, owner) PlugIn.PlugIn(self, owner)
self.on_tls_success = on_tls_success print 'inplugin'
self.on_tls_faliure = on_tls_failure try:
if now: self._owner._plug_idle(writable=False, readable=False)
try: res = self._startSSL()
res = self._startSSL() except Exception, e:
except Exception, e: log.error("PlugIn: while trying _startSSL():", exc_info=True)
log.error("PlugIn: while trying _startSSL():", exc_info=True) #traceback.print_exc()
#traceback.print_exc() return False
self._owner.socket.pollend() return res
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):
log.warn("TLS unsupported by remote server.")
self.on_tls_failure("TLS unsupported by remote server.")
return
log.debug("TLS supported by remote server. Requesting TLS start.")
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): def _dumpX509(self, cert, stream=sys.stderr):
print >> stream, "Digest (SHA-1):", cert.digest("sha1") print >> stream, "Digest (SHA-1):", cert.digest("sha1")
@ -317,27 +277,26 @@ class NonBlockingTLS(PlugIn):
''' Immidiatedly switch socket to TLS mode. Used internally.''' ''' Immidiatedly switch socket to TLS mode. Used internally.'''
log.debug("_startSSL called") log.debug("_startSSL called")
if USE_PYOPENSSL: return self._startSSL_pyOpenSSL() if USE_PYOPENSSL: return self._startSSL_pyOpenSSL()
return self._startSSL_stdlib() else: return self._startSSL_stdlib()
def _startSSL_pyOpenSSL(self): def _startSSL_pyOpenSSL(self):
#log.debug("_startSSL_pyOpenSSL called, thread id: %s", str(thread.get_ident())) #log.debug("_startSSL_pyOpenSSL called, thread id: %s", str(thread.get_ident()))
log.debug("_startSSL_pyOpenSSL called") log.debug("_startSSL_pyOpenSSL called")
tcpsock = self._owner.Connection tcpsock = self._owner
# FIXME: should method be configurable? # FIXME: should method be configurable?
tcpsock._sslContext = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_METHOD) #tcpsock._sslContext = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_METHOD)
#tcpsock._sslContext = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD) tcpsock._sslContext = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
tcpsock.ssl_errnum = 0 tcpsock.ssl_errnum = 0
tcpsock._sslContext.set_verify(OpenSSL.SSL.VERIFY_PEER, self._ssl_verify_callback) tcpsock._sslContext.set_verify(OpenSSL.SSL.VERIFY_PEER, self._ssl_verify_callback)
cacerts = os.path.join('../data', 'other', 'cacerts.pem')
try: try:
tcpsock._sslContext.load_verify_locations(cacerts) tcpsock._sslContext.load_verify_locations(self.cacerts)
except: except:
log.warning('Unable to load SSL certificats from file %s' % \ log.warning('Unable to load SSL certificates from file %s' % \
os.path.abspath(cacerts)) os.path.abspath(self.cacerts))
# load users certs # load users certs
if os.path.isfile('%s/.gajim/cacerts.pem' % os.environ['HOME']): if os.path.isfile(self.mycerts):
store = tcpsock._sslContext.get_cert_store() store = tcpsock._sslContext.get_cert_store()
f = open('%s/.gajim/cacerts.pem' % os.environ['HOME']) f = open(self.mycerts)
lines = f.readlines() lines = f.readlines()
i = 0 i = 0
begin = -1 begin = -1
@ -352,11 +311,10 @@ class NonBlockingTLS(PlugIn):
store.add_cert(X509cert) store.add_cert(X509cert)
except OpenSSL.crypto.Error, exception_obj: except OpenSSL.crypto.Error, exception_obj:
log.warning('Unable to load a certificate from file %s: %s' %\ log.warning('Unable to load a certificate from file %s: %s' %\
('%s/.gajim/cacerts.pem' % os.environ['HOME'], exception_obj.args[0][0][2])) (self.mycerts, exception_obj.args[0][0][2]))
except: except:
log.warning( log.warning('Unknown error while loading certificate from file %s' %\
'Unknown error while loading certificate from file %s' % \ self.mycerts)
'%s/.gajim/cacerts.pem' % os.environ['HOME'])
begin = -1 begin = -1
i += 1 i += 1
tcpsock._sslObj = OpenSSL.SSL.Connection(tcpsock._sslContext, tcpsock._sock) tcpsock._sslObj = OpenSSL.SSL.Connection(tcpsock._sslContext, tcpsock._sock)
@ -367,67 +325,52 @@ class NonBlockingTLS(PlugIn):
tcpsock._send = wrapper.send tcpsock._send = wrapper.send
log.debug("Initiating handshake...") 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) tcpsock._sslObj.setblocking(True)
try: try:
self.starttls='in progress'
tcpsock._sslObj.do_handshake() tcpsock._sslObj.do_handshake()
except: except:
log.error('Error while TLS handshake: ', exc_info=True) log.error('Error while TLS handshake: ', exc_info=True)
self.on_tls_failure('Error while TLS Handshake') return False
return
tcpsock._sslObj.setblocking(False) tcpsock._sslObj.setblocking(False)
log.debug("Synchronous handshake completed") log.debug("Synchronous handshake completed")
#log.debug("Async handshake started...") self._owner.ssl_lib = PYOPENSSL
return self._endSSL()
# fake it, for now
self.starttls='success'
def _startSSL_stdlib(self): def _startSSL_stdlib(self):
log.debug("_startSSL_stdlib called") log.debug("_startSSL_stdlib called")
tcpsock=self._owner.Connection tcpsock=self._owner
tcpsock._sock.setblocking(True) try:
tcpsock._sslObj = socket.ssl(tcpsock._sock, None, None) tcpsock._sock.setblocking(True)
tcpsock._sock.setblocking(False) tcpsock._sslObj = socket.ssl(tcpsock._sock, None, None)
tcpsock._sslIssuer = tcpsock._sslObj.issuer() tcpsock._sock.setblocking(False)
tcpsock._sslServer = tcpsock._sslObj.server() tcpsock._sslIssuer = tcpsock._sslObj.issuer()
wrapper = StdlibSSLWrapper(tcpsock._sslObj, tcpsock._sock) tcpsock._sslServer = tcpsock._sslObj.server()
tcpsock._recv = wrapper.recv wrapper = StdlibSSLWrapper(tcpsock._sslObj, tcpsock._sock)
tcpsock._send = wrapper.send tcpsock._recv = wrapper.recv
self.starttls='success' tcpsock._send = wrapper.send
except:
log.error("Exception caught in _startSSL_stdlib:", exc_info=True)
return False
self._owner.ssl_lib = PYSTDLIB
return self._endSSL()
def _endSSL(self):
self._owner._plug_idle(writable=True, readable=False)
return True
def _ssl_verify_callback(self, sslconn, cert, errnum, depth, ok): def _ssl_verify_callback(self, sslconn, cert, errnum, depth, ok):
# Exceptions can't propagate up through this callback, so print them here. # Exceptions can't propagate up through this callback, so print them here.
try: try:
self._owner.Connection.ssl_fingerprint_sha1 = cert.digest('sha1') print 'in ssl verify callback'
self._owner.ssl_fingerprint_sha1 = cert.digest('sha1')
if errnum == 0: if errnum == 0:
return True return True
self._owner.Connection.ssl_errnum = errnum self._owner.ssl_errnum = errnum
self._owner.Connection.ssl_cert_pem = OpenSSL.crypto.dump_certificate( self._owner.ssl_cert_pem = OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_PEM, cert) OpenSSL.crypto.FILETYPE_PEM, cert)
return True return True
except: except:
log.error("Exception caught in _ssl_info_callback:", exc_info=True) log.error("Exception caught in _ssl_info_callback:", exc_info=True)
traceback.print_exc() # Make sure something is printed, even if log is disabled. 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
log.debug('Got starttls proceed response. Switching to TLS/SSL...')
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()

View File

@ -21,6 +21,7 @@ from simplexml import ustr
from client import PlugIn from client import PlugIn
from idlequeue import IdleObject from idlequeue import IdleObject
from protocol import * from protocol import *
from tls_nb import NonBlockingTLS
import sys import sys
import os import os
@ -76,15 +77,17 @@ DATA_RECEIVED='DATA RECEIVED'
DATA_SENT='DATA SENT' DATA_SENT='DATA SENT'
DISCONNECTED ='DISCONNECTED' DISCONNECTED = 'DISCONNECTED'
DISCONNECTING ='DISCONNECTING' DISCONNECTING = 'DISCONNECTING'
CONNECTING ='CONNECTING' CONNECTING = 'CONNECTING'
CONNECTED ='CONNECTED' PROXY_CONNECTING = 'PROXY_CONNECTING'
CONNECTED = 'CONNECTED'
# transports have different constructor and same connect STATES = [DISCONNECTED, DISCONNECTING, CONNECTING, PROXY_CONNECTING, CONNECTED]
# transports have different arguments in constructor and same in connect()
# method
class NonBlockingTransport(PlugIn): class NonBlockingTransport(PlugIn):
def __init__(self, raise_event, on_disconnect, idlequeue): def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls, certs):
PlugIn.__init__(self) PlugIn.__init__(self)
self.raise_event = raise_event self.raise_event = raise_event
self.on_disconnect = on_disconnect self.on_disconnect = on_disconnect
@ -94,7 +97,11 @@ class NonBlockingTransport(PlugIn):
self.on_receive = None self.on_receive = None
self.server = None self.server = None
self.port = None self.port = None
self.state = DISCONNECTED self.set_state(DISCONNECTED)
self.estabilish_tls = estabilish_tls
self.certs = certs
# type of used ssl lib (if any) will be assigned to this member var
self.ssl_lib = None
self._exported_methods=[self.disconnect, self.onreceive, self.set_send_timeout, self._exported_methods=[self.disconnect, self.onreceive, self.set_send_timeout,
self.set_timeout, self.remove_timeout, self.start_disconnect] self.set_timeout, self.remove_timeout, self.start_disconnect]
@ -114,9 +121,7 @@ class NonBlockingTransport(PlugIn):
def connect(self, conn_5tuple, on_connect, on_connect_failure): def connect(self, conn_5tuple, on_connect, on_connect_failure):
''' '''
connect method should have the same declaration in all derived transports connect method should have the same declaration in all derived transports
''' '''
assert(self.state == DISCONNECTED)
self.on_connect = on_connect self.on_connect = on_connect
self.on_connect_failure = on_connect_failure self.on_connect_failure = on_connect_failure
(self.server, self.port) = conn_5tuple[4][:2] (self.server, self.port) = conn_5tuple[4][:2]
@ -124,14 +129,16 @@ class NonBlockingTransport(PlugIn):
def set_state(self, newstate): def set_state(self, newstate):
assert(newstate in [DISCONNECTED, CONNECTING, CONNECTED, DISCONNECTING]) assert(newstate in STATES)
self.state = newstate self.state = newstate
def _on_connect(self, data): def get_state(self):
return self.state
def _on_connect(self):
''' preceeds call of on_connect callback ''' ''' preceeds call of on_connect callback '''
# data is reference to socket wrapper instance. We don't need it in client # data is reference to socket wrapper instance. We don't need it in client
# because # because
self.peerhost = data._sock.getsockname()
self.set_state(CONNECTED) self.set_state(CONNECTED)
self.on_connect() self.on_connect()
@ -144,9 +151,9 @@ class NonBlockingTransport(PlugIn):
self.on_connect_failure(err_message=err_message) self.on_connect_failure(err_message=err_message)
def send(self, raw_data, now=False): def send(self, raw_data, now=False):
if self.state not in [CONNECTED]: if self.get_state() == DISCONNECTED:
log.error('Unable to send %s \n because state is %s.' % log.error('Unable to send %s \n because state is %s.' %
(raw_data, self.state)) (raw_data, self.get_state()))
def disconnect(self, do_callback=True): def disconnect(self, do_callback=True):
@ -166,7 +173,7 @@ class NonBlockingTransport(PlugIn):
return return
self.on_receive = recv_handler self.on_receive = recv_handler
def tcp_connection_started(self): def tcp_connecting_started(self):
self.set_state(CONNECTING) self.set_state(CONNECTING)
# on_connect/on_conn_failure will be called from self.pollin/self.pollout # on_connect/on_conn_failure will be called from self.pollin/self.pollout
@ -206,23 +213,23 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
''' '''
Non-blocking TCP socket wrapper Non-blocking TCP socket wrapper
''' '''
def __init__(self, raise_event, on_disconnect, idlequeue): def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls, certs):
''' '''
Class constructor. Class constructor.
''' '''
NonBlockingTransport.__init__(self, raise_event, on_disconnect, idlequeue) NonBlockingTransport.__init__(self, raise_event, on_disconnect, idlequeue,
estabilish_tls, certs)
# queue with messages to be send # queue with messages to be send
self.sendqueue = [] self.sendqueue = []
# bytes remained from the last send message # bytes remained from the last send message
self.sendbuff = '' self.sendbuff = ''
self.terminator = '</stream:stream>'
def start_disconnect(self): def start_disconnect(self):
self.send('</stream:stream>')
NonBlockingTransport.start_disconnect(self) NonBlockingTransport.start_disconnect(self)
self.send('</stream:stream>', now=True)
self.disconnect()
def connect(self, conn_5tuple, on_connect, on_connect_failure): def connect(self, conn_5tuple, on_connect, on_connect_failure):
''' '''
@ -267,7 +274,7 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK): if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK):
# connecting in progress # connecting in progress
log.info('After NB connect() of %s. "%s" raised => CONNECTING' % (id(self),errstr)) log.info('After NB connect() of %s. "%s" raised => CONNECTING' % (id(self),errstr))
self.tcp_connection_started() self.tcp_connecting_started()
return return
elif errnum in (0, 10056, errno.EISCONN): elif errnum in (0, 10056, errno.EISCONN):
# already connected - this branch is probably useless, nonblocking connect() will # already connected - this branch is probably useless, nonblocking connect() will
@ -282,39 +289,52 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
self._on_connect_failure('Exception while connecting to %s:%s - %s %s' % self._on_connect_failure('Exception while connecting to %s:%s - %s %s' %
(self.server, self.port, errnum, errstr)) (self.server, self.port, errnum, errstr))
def _on_connect(self, data): def _on_connect(self):
''' with TCP socket, we have to remove send-timeout ''' ''' with TCP socket, we have to remove send-timeout '''
self.idlequeue.remove_timeout(self.fd) self.idlequeue.remove_timeout(self.fd)
self.peerhost = self._sock.getsockname()
print self.estabilish_tls
if self.estabilish_tls:
self.tls_init(
on_succ = lambda: NonBlockingTransport._on_connect(self),
on_fail = lambda: self._on_connect_failure('error while estabilishing TLS'))
else:
NonBlockingTransport._on_connect(self)
def tls_init(self, on_succ, on_fail):
cacerts, mycerts = self.certs
result = NonBlockingTLS(cacerts, mycerts).PlugIn(self)
if result: on_succ()
else: on_fail()
NonBlockingTransport._on_connect(self, data)
def pollin(self): def pollin(self):
'''called when receive on plugged socket is possible ''' '''called when receive on plugged socket is possible '''
log.info('pollin called, state == %s' % self.state) log.info('pollin called, state == %s' % self.get_state())
self._do_receive() self._do_receive()
def pollout(self): def pollout(self):
'''called when send to plugged socket is possible''' '''called when send to plugged socket is possible'''
log.info('pollout called, state == %s' % self.state) log.info('pollout called, state == %s' % self.get_state())
if self.state==CONNECTING: if self.get_state()==CONNECTING:
log.info('%s socket wrapper connected' % id(self)) log.info('%s socket wrapper connected' % id(self))
self._on_connect(self) self._on_connect()
return return
self._do_send() self._do_send()
def pollend(self): def pollend(self):
log.info('pollend called, state == %s' % self.state) log.info('pollend called, state == %s' % self.get_state())
if self.state==CONNECTING: if self.get_state()==CONNECTING:
self._on_connect_failure('Error during connect to %s:%s' % self._on_connect_failure('Error during connect to %s:%s' %
(self.server, self.port)) (self.server, self.port))
else : else :
self.disconnect() self.disconnect()
def disconnect(self, do_callback=True): def disconnect(self, do_callback=True):
if self.state == DISCONNECTED: if self.get_state() == DISCONNECTED:
return return
self.set_state(DISCONNECTED) self.set_state(DISCONNECTED)
self.idlequeue.unplug_idle(self.fd) self.idlequeue.unplug_idle(self.fd)
@ -330,8 +350,8 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
''' '''
Implemntation of IdleObject function called on timeouts from IdleQueue. Implemntation of IdleObject function called on timeouts from IdleQueue.
''' '''
log.warn('read_timeout called, state == %s' % self.state) log.warn('read_timeout called, state == %s' % self.get_state())
if self.state==CONNECTING: if self.get_state()==CONNECTING:
# if read_timeout is called during connecting, connect() didn't end yet # if read_timeout is called during connecting, connect() didn't end yet
# thus we have to call the tcp failure callback # thus we have to call the tcp failure callback
self._on_connect_failure('Error during connect to %s:%s' % self._on_connect_failure('Error during connect to %s:%s' %
@ -342,16 +362,16 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
def set_timeout(self, timeout): def set_timeout(self, timeout):
if self.state != DISCONNECTED and self.fd != -1: if self.get_state() != DISCONNECTED and self.fd != -1:
NonBlockingTransport.set_timeout(self, timeout) NonBlockingTransport.set_timeout(self, timeout)
else: else:
log.warn('set_timeout: TIMEOUT NOT SET: state is %s, fd is %s' % (self.state, self.fd)) log.warn('set_timeout: TIMEOUT NOT SET: state is %s, fd is %s' % (self.get_state(), self.fd))
def remove_timeout(self): def remove_timeout(self):
if self.fd: if self.fd:
NonBlockingTransport.remove_timeout(self) NonBlockingTransport.remove_timeout(self)
else: else:
log.warn('remove_timeout: no self.fd state is %s' % self.state) log.warn('remove_timeout: no self.fd state is %s' % self.get_state())
def send(self, raw_data, now=False): def send(self, raw_data, now=False):
'''Append raw_data to the queue of messages to be send. '''Append raw_data to the queue of messages to be send.
@ -368,6 +388,7 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
self._do_send() self._do_send()
else: else:
self.sendqueue.append(r) self.sendqueue.append(r)
self._plug_idle(writable=True, readable=True) self._plug_idle(writable=True, readable=True)
@ -380,8 +401,6 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
Plugged socket will always be watched for "error" event - in that case, Plugged socket will always be watched for "error" event - in that case,
pollend() is called. pollend() is called.
''' '''
# if we are connecting, we shouln't touch the socket until it's connected
assert(self.state!=CONNECTING)
self.idlequeue.plug_idle(self, writable, readable) self.idlequeue.plug_idle(self, writable, readable)
log.info('Plugging fd %d, W:%s, R:%s' % (self.fd, writable, readable)) log.info('Plugging fd %d, W:%s, R:%s' % (self.fd, writable, readable))
@ -438,16 +457,15 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
# ENOTCONN - Transport endpoint is not connected # ENOTCONN - Transport endpoint is not connected
# ESHUTDOWN - shutdown(2) has been called on a socket to close down the # ESHUTDOWN - shutdown(2) has been called on a socket to close down the
# sending end of the transmision, and then data was attempted to be sent # sending end of the transmision, and then data was attempted to be sent
log.error("Connection to %s lost: %s %s" % ( self.server, errnum, errstr)) log.error("Connection to %s lost: %s %s" % ( self.server, errnum, errstr), exc_info=True)
if self.on_remote_disconnect: if hasattr(self, 'on_remote_disconnect'): self.on_remote_disconnect()
self.on_remote_disconnect() else: self.disconnect()
else:
self.disconnect()
return return
if received is None: if received is None:
# in case of some other exception # in case of SSL error - because there are two types of TLS wrappers, the TLS
# FIXME: is this needed?? # pluging recv method returns None in case of error
print 'SSL ERROR'
if errnum != 0: if errnum != 0:
log.error("CConnection to %s lost: %s %s" % (self.server, errnum, errstr)) log.error("CConnection to %s lost: %s %s" % (self.server, errnum, errstr))
self.disconnect() self.disconnect()
@ -456,6 +474,7 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
# we have received some bytes, stop the timeout! # we have received some bytes, stop the timeout!
self.renew_send_timeout() self.renew_send_timeout()
print '-->%s<--' % received
# pass received data to owner # pass received data to owner
if self.on_receive: if self.on_receive:
self.raise_event(DATA_RECEIVED, received) self.raise_event(DATA_RECEIVED, received)
@ -479,10 +498,11 @@ class NonBlockingHTTP(NonBlockingTCP):
HTTP headers from incoming messages HTTP headers from incoming messages
''' '''
def __init__(self, raise_event, on_disconnect, idlequeue, on_http_request_possible, def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls, certs,
on_persistent_fallback, http_dict): on_http_request_possible, on_persistent_fallback, http_dict):
NonBlockingTCP.__init__(self, raise_event, on_disconnect, idlequeue) NonBlockingTCP.__init__(self, raise_event, on_disconnect, idlequeue,
estabilish_tls, certs)
self.http_protocol, self.http_host, self.http_path = urisplit(http_dict['http_uri']) self.http_protocol, self.http_host, self.http_path = urisplit(http_dict['http_uri'])
if self.http_protocol is None: if self.http_protocol is None:
@ -522,10 +542,7 @@ class NonBlockingHTTP(NonBlockingTCP):
self.disconnect(do_callback=False) self.disconnect(do_callback=False)
self.connect( self.connect(
conn_5tuple = self.conn_5tuple, conn_5tuple = self.conn_5tuple,
# after connect, the socket will be plugged as writable - pollout will be on_connect = self.on_http_request_possible,
# called, and since there are still data in sendbuff, _do_send will be
# called and sendbuff will be flushed
on_connect = lambda: self._plug_idle(writable=True, readable=True),
on_connect_failure = self.disconnect) on_connect_failure = self.disconnect)
else: else:
@ -549,7 +566,7 @@ class NonBlockingHTTP(NonBlockingTCP):
if self.expected_length > len(self.recvbuff): if self.expected_length > len(self.recvbuff):
# If we haven't received the whole HTTP mess yet, let's end the thread. # If we haven't received the whole HTTP mess yet, let's end the thread.
# It will be finnished from one of following polls (io_watch) on plugged socket. # It will be finnished from one of following recvs on plugged socket.
log.info('not enough bytes in HTTP response - %d expected, %d got' % log.info('not enough bytes in HTTP response - %d expected, %d got' %
(self.expected_length, len(self.recvbuff))) (self.expected_length, len(self.recvbuff)))
return return
@ -587,6 +604,9 @@ class NonBlockingHTTP(NonBlockingTCP):
credentials = '%s:%s' % (self.proxy_user, self.proxy_pass) credentials = '%s:%s' % (self.proxy_user, self.proxy_pass)
credentials = base64.encodestring(credentials).strip() credentials = base64.encodestring(credentials).strip()
headers.append('Proxy-Authorization: Basic %s' % credentials) headers.append('Proxy-Authorization: Basic %s' % credentials)
else:
headers.append('Connection: Keep-Alive')
headers.append('\r\n') headers.append('\r\n')
headers = '\r\n'.join(headers) headers = '\r\n'.join(headers)
return('%s%s\r\n' % (headers, httpbody)) return('%s%s\r\n' % (headers, httpbody))
@ -611,6 +631,8 @@ class NonBlockingHTTP(NonBlockingTCP):
headers[row[0][:-1]] = row[1] headers[row[0][:-1]] = row[1]
return (statusline, headers, httpbody) return (statusline, headers, httpbody)
class NonBlockingHTTPBOSH(NonBlockingHTTP): class NonBlockingHTTPBOSH(NonBlockingHTTP):
@ -646,29 +668,26 @@ class NBProxySocket(NonBlockingTCP):
Interface for proxy socket wrappers - when tunnneling XMPP over proxies, Interface for proxy socket wrappers - when tunnneling XMPP over proxies,
some connecting process usually has to be done before opening stream. some connecting process usually has to be done before opening stream.
''' '''
def __init__(self, raise_event, on_disconnect, idlequeue, xmpp_server, def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls, certs,
proxy_creds=(None,None)): xmpp_server, proxy_creds=(None,None)):
self.proxy_user, self.proxy_pass = proxy_creds self.proxy_user, self.proxy_pass = proxy_creds
self.xmpp_server = xmpp_server self.xmpp_server = xmpp_server
NonBlockingTCP.__init__(self, raise_event, on_disconnect, idlequeue) NonBlockingTCP.__init__(self, raise_event, on_disconnect, idlequeue,
estabilish_tls, certs)
def _on_connect(self):
def connect(self, conn_5tuple, on_connect, on_connect_failure):
''' '''
connect method is extended by proxy credentials and xmpp server hostname We're redefining _on_connect method to insert proxy-specific mechanism before
and port because those are needed for invoking the ssl connection and then client callback. All the proxy connecting
The idea is to insert Proxy-specific mechanism after TCP connect and is done before XML stream is opened.
before XMPP stream opening (which is done from client).
''' '''
self.set_state(PROXY_CONNECTING)
self._on_tcp_connect()
self.after_proxy_connect = on_connect
NonBlockingTCP.connect(self,
conn_5tuple=conn_5tuple,
on_connect =self._on_tcp_connect,
on_connect_failure =on_connect_failure)
def _on_tcp_connect(self): def _on_tcp_connect(self):
'''to be implemented in each proxy socket wrapper'''
pass pass
@ -713,7 +732,7 @@ class NBHTTPProxySocket(NBProxySocket):
return return
if len(reply) != 2: if len(reply) != 2:
pass pass
self.after_proxy_connect() NonBlockingT._on_connect(self)
#self.onreceive(self._on_proxy_auth) #self.onreceive(self._on_proxy_auth)
def _on_proxy_auth(self, reply): def _on_proxy_auth(self, reply):

View File

@ -545,7 +545,7 @@ class Interface:
ctrl.print_conversation('Error %s: %s' % (array[2], array[1])) ctrl.print_conversation('Error %s: %s' % (array[2], array[1]))
def handle_event_con_type(self, account, con_type): def handle_event_con_type(self, account, con_type):
# ('CON_TYPE', account, con_type) which can be 'ssl', 'tls', 'tcp' # ('CON_TYPE', account, con_type) which can be 'ssl', 'tls', 'plain'
gajim.con_types[account] = con_type gajim.con_types[account] = con_type
self.roster.draw_account(account) self.roster.draw_account(account)