- 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:
parent
56e0ad7a96
commit
cbfa9d97df
|
@ -397,7 +397,7 @@ class Connection(ConnectionHandlers):
|
|||
|
||||
def connect(self, data = None):
|
||||
''' 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,
|
||||
custom_host (if use_custom_host), custom_port (if use_custom_host)'''
|
||||
if self.connection:
|
||||
|
@ -410,9 +410,11 @@ class Connection(ConnectionHandlers):
|
|||
p = data['proxy']
|
||||
use_srv = True
|
||||
use_custom = data['use_custom_host']
|
||||
print 'use_custom = %s' % use_custom
|
||||
if use_custom:
|
||||
custom_h = data['custom_host']
|
||||
custom_p = data['custom_port']
|
||||
print 'custom_port = %s' % custom_p
|
||||
else:
|
||||
hostname = gajim.config.get_per('accounts', self.name, 'hostname')
|
||||
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_custom = gajim.config.get_per('accounts', self.name,
|
||||
'use_custom_host')
|
||||
print 'use_custom = %s' % use_custom
|
||||
custom_h = gajim.config.get_per('accounts', self.name, 'custom_host')
|
||||
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
|
||||
self.connected = 1
|
||||
|
@ -502,68 +506,6 @@ class Connection(ConnectionHandlers):
|
|||
i['ssl_port'] = ssl_p
|
||||
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):
|
||||
if len(self._hosts):
|
||||
|
@ -573,10 +515,14 @@ class Connection(ConnectionHandlers):
|
|||
'connection_types').split()
|
||||
else:
|
||||
self._connection_types = ['tls', 'ssl', 'plain']
|
||||
#THEHACK
|
||||
#self._connection_types = ['ssl', 'plain']
|
||||
|
||||
# FIXME: remove after tls and ssl will be degubbed
|
||||
self._connection_types = ['plain']
|
||||
|
||||
if self._proxy and self._proxy['type']=='bosh':
|
||||
# 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)
|
||||
self._current_host = host
|
||||
|
@ -597,6 +543,55 @@ class Connection(ConnectionHandlers):
|
|||
# try reconnect if connection has failed before auth to server
|
||||
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):
|
||||
if not con_type:
|
||||
# we are not retrying, and not conecting
|
||||
|
@ -607,14 +602,21 @@ class Connection(ConnectionHandlers):
|
|||
(_('Could not connect to "%s"') % self._hostname,
|
||||
_('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):
|
||||
if not self.connected: # We went offline during connecting process
|
||||
# FIXME - not possible, maybe it was when we used threads
|
||||
return
|
||||
_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:
|
||||
log.info('Connecting to next type beacuse desired is %s and returned is %s'
|
||||
% (self._current_type, _con_type))
|
||||
|
@ -676,7 +678,7 @@ class Connection(ConnectionHandlers):
|
|||
|
||||
def plain_connection_accepted(self):
|
||||
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.__on_auth)
|
||||
|
||||
|
|
|
@ -207,7 +207,7 @@ class ConnectionBytestream:
|
|||
iq.setID(file_props['request-id'])
|
||||
query = iq.setTag('query')
|
||||
query.setNamespace(common.xmpp.NS_BYTESTREAM)
|
||||
query.setAttr('mode', 'tcp')
|
||||
query.setAttr('mode', 'plain')
|
||||
query.setAttr('sid', file_props['sid'])
|
||||
for ft_host in ft_add_hosts:
|
||||
# The streamhost, if set
|
||||
|
|
|
@ -19,9 +19,10 @@ In TCP-derived transports it is file descriptor of socket'''
|
|||
|
||||
|
||||
class NonBlockingBOSH(NonBlockingTransport):
|
||||
def __init__(self, raise_event, on_disconnect, idlequeue, xmpp_server, domain,
|
||||
bosh_dict, proxy_creds):
|
||||
NonBlockingTransport.__init__(self, raise_event, on_disconnect, idlequeue)
|
||||
def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls, certs,
|
||||
xmpp_server, domain, bosh_dict, proxy_creds):
|
||||
NonBlockingTransport.__init__(self, raise_event, on_disconnect, idlequeue,
|
||||
estabilish_tls, certs)
|
||||
|
||||
self.bosh_sid = None
|
||||
if locale.getdefaultlocale()[0]:
|
||||
|
@ -37,12 +38,19 @@ class NonBlockingBOSH(NonBlockingTransport):
|
|||
self.route_host, self.route_port = xmpp_server
|
||||
|
||||
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_uri = bosh_dict['bosh_uri']
|
||||
self.bosh_port = bosh_dict['bosh_port']
|
||||
self.bosh_content = bosh_dict['bosh_content']
|
||||
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.proxy_creds = proxy_creds
|
||||
self.wait_cb_time = None
|
||||
|
@ -55,7 +63,6 @@ class NonBlockingBOSH(NonBlockingTransport):
|
|||
self.ack_checker = None
|
||||
self.after_init = False
|
||||
|
||||
# if proxy_host .. do sth about HTTP proxy etc.
|
||||
|
||||
def 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.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
|
||||
# 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)
|
||||
self.http_socks[0].connect(
|
||||
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)
|
||||
|
||||
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):
|
||||
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)
|
||||
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):
|
||||
'''
|
||||
|
@ -95,17 +109,25 @@ class NonBlockingBOSH(NonBlockingTransport):
|
|||
There should be always one pending request on BOSH CM.
|
||||
'''
|
||||
log.info('on_http_req possible, state:\n%s' % self.get_current_state())
|
||||
if self.state == DISCONNECTING:
|
||||
if self.get_state() == DISCONNECTING:
|
||||
self.disconnect()
|
||||
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):
|
||||
for s in self.http_socks:
|
||||
if s.state==state: return s
|
||||
if s.get_state()==state: return s
|
||||
return None
|
||||
|
||||
|
||||
def get_free_socket(self):
|
||||
if self.http_pipelining:
|
||||
assert( len(self.http_socks) == 1 )
|
||||
|
@ -114,8 +136,8 @@ class NonBlockingBOSH(NonBlockingTransport):
|
|||
last_recv_time, tmpsock = 0, None
|
||||
for s in self.http_socks:
|
||||
# we're interested only into CONNECTED socket with no req pending
|
||||
if s.state==CONNECTED and s.pending_requests==0:
|
||||
# if there's more of them, we want the one with less recent data receive
|
||||
if s.get_state()==CONNECTED and s.pending_requests==0:
|
||||
# if there's more of them, we want the one with the least recent data receive
|
||||
# (lowest last_recv_time)
|
||||
if (last_recv_time==0) or (s.last_recv_time < last_recv_time):
|
||||
last_recv_time = s.last_recv_time
|
||||
|
@ -129,13 +151,14 @@ class NonBlockingBOSH(NonBlockingTransport):
|
|||
def send_BOSH(self, payload):
|
||||
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
|
||||
# no data to send, we do nothing and disccard the payload
|
||||
# when called after HTTP response (Payload=None) and when there are already
|
||||
# some pending requests and no data to send, or when the socket is
|
||||
# disconnected, we do nothing
|
||||
if payload is None and \
|
||||
total_pending_reqs > 0 and \
|
||||
self.stanza_buffer == [] and \
|
||||
self.prio_bosh_stanzas == [] or \
|
||||
self.state==DISCONNECTED:
|
||||
self.get_state()==DISCONNECTED:
|
||||
return
|
||||
|
||||
# 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
|
||||
# sent after HTTP response from CM, exception is when we're disconnecting - then we
|
||||
# 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' %
|
||||
self.get_current_state())
|
||||
return
|
||||
|
@ -232,17 +255,16 @@ class NonBlockingBOSH(NonBlockingTransport):
|
|||
self.remove_bosh_wait_timeout()
|
||||
|
||||
if self.after_init:
|
||||
self.after_init = False
|
||||
if stanza_attrs.has_key('sid'):
|
||||
# session ID should be only in init response
|
||||
self.bosh_sid = stanza_attrs['sid']
|
||||
|
||||
if stanza_attrs.has_key('requests'):
|
||||
#self.bosh_requests = int(stanza_attrs['requests'])
|
||||
self.bosh_requests = int(stanza_attrs['wait'])
|
||||
self.bosh_requests = int(stanza_attrs['requests'])
|
||||
|
||||
if stanza_attrs.has_key('wait'):
|
||||
self.bosh_wait = int(stanza_attrs['wait'])
|
||||
self.after_init = False
|
||||
|
||||
ack = None
|
||||
if stanza_attrs.has_key('ack'):
|
||||
|
@ -267,13 +289,12 @@ class NonBlockingBOSH(NonBlockingTransport):
|
|||
def append_stanza(self, stanza):
|
||||
if stanza:
|
||||
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)
|
||||
else:
|
||||
self.stanza_buffer.append(stanza)
|
||||
|
||||
|
||||
|
||||
def send(self, stanza, now=False):
|
||||
# body tags should be send only via send_BOSH()
|
||||
assert(not isinstance(stanza, BOSHBody))
|
||||
|
@ -284,7 +305,7 @@ class NonBlockingBOSH(NonBlockingTransport):
|
|||
def get_current_state(self):
|
||||
t = '------ SOCKET_ID\tSOCKET_STATE\tPENDING_REQS\n'
|
||||
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, self.prio_bosh_stanzas, self.stanza_buffer,
|
||||
self.ack_checker.get_not_acked_rids())
|
||||
|
@ -294,7 +315,7 @@ class NonBlockingBOSH(NonBlockingTransport):
|
|||
def connect_and_flush(self, socket):
|
||||
socket.connect(
|
||||
conn_5tuple = self.conn_5tuple,
|
||||
on_connect = lambda :self.send_BOSH(None),
|
||||
on_connect = self.on_http_request_possible,
|
||||
on_connect_failure = self.disconnect)
|
||||
|
||||
|
||||
|
@ -313,6 +334,7 @@ class NonBlockingBOSH(NonBlockingTransport):
|
|||
'sid': self.bosh_sid,
|
||||
'xml:lang': self.bosh_xml_lang,
|
||||
'xmpp:restart': 'true',
|
||||
'secure': self.bosh_secure,
|
||||
'xmlns:xmpp': 'urn:xmpp:xbosh'})
|
||||
else:
|
||||
t = BOSHBody(
|
||||
|
@ -347,6 +369,8 @@ class NonBlockingBOSH(NonBlockingTransport):
|
|||
raise_event=self.raise_event,
|
||||
on_disconnect=self.disconnect,
|
||||
idlequeue = self.idlequeue,
|
||||
estabilish_tls = self.estabilish_tls,
|
||||
certs = self.certs,
|
||||
on_http_request_possible = self.on_http_request_possible,
|
||||
http_dict = http_dict,
|
||||
on_persistent_fallback = self.on_persistent_fallback)
|
||||
|
@ -368,7 +392,7 @@ class NonBlockingBOSH(NonBlockingTransport):
|
|||
|
||||
def disconnect(self, do_callback=True):
|
||||
self.remove_bosh_wait_timeout()
|
||||
if self.state == DISCONNECTED: return
|
||||
if self.get_state() == DISCONNECTED: return
|
||||
self.fd = -1
|
||||
for s in self.http_socks:
|
||||
s.disconnect(do_callback=False)
|
||||
|
|
|
@ -26,6 +26,8 @@ import socket
|
|||
import transports_nb, tls_nb, dispatcher_nb, auth_nb, roster_nb, protocol, bosh
|
||||
from client import *
|
||||
|
||||
from protocol import NS_TLS
|
||||
|
||||
import logging
|
||||
log = logging.getLogger('gajim.c.x.client_nb')
|
||||
|
||||
|
@ -71,7 +73,6 @@ class NBCommonClient:
|
|||
|
||||
self.connected=''
|
||||
log.debug('Client disconnected..')
|
||||
print 'ffffffffffffffffff'
|
||||
for i in reversed(self.disconnect_handlers):
|
||||
log.debug('Calling disconnect handler %s' % i)
|
||||
i()
|
||||
|
@ -86,9 +87,9 @@ class NBCommonClient:
|
|||
if self.__dict__.has_key('NonBlockingTLS'):
|
||||
self.NonBlockingTLS.PlugOut()
|
||||
if self.__dict__.has_key('NBHTTPProxySocket'):
|
||||
self.NBHTTPPROXYsocket.PlugOut()
|
||||
self.NBHTTPProxySocket.PlugOut()
|
||||
if self.__dict__.has_key('NBSOCKS5ProxySocket'):
|
||||
self.NBSOCKS5PROXYsocket.PlugOut()
|
||||
self.NBSOCKS5ProxySocket.PlugOut()
|
||||
if self.__dict__.has_key('NonBlockingTCP'):
|
||||
self.NonBlockingTCP.PlugOut()
|
||||
if self.__dict__.has_key('NonBlockingHTTP'):
|
||||
|
@ -98,7 +99,7 @@ class NBCommonClient:
|
|||
|
||||
|
||||
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).
|
||||
: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
|
||||
for keys 'host' and 'port' - connection details for proxy server and
|
||||
optionally keys 'user' and 'pass' as proxy credentials
|
||||
:param secure:
|
||||
:param secure_tuple:
|
||||
'''
|
||||
self.on_connect = on_connect
|
||||
self.on_connect_failure=on_connect_failure
|
||||
self.on_proxy_failure = on_proxy_failure
|
||||
self._secure = secure
|
||||
self.secure, self.cacerts, self.mycerts = secure_tuple
|
||||
self.Connection = None
|
||||
self.Port = port
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
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.socket.connect(
|
||||
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)
|
||||
|
||||
|
||||
|
@ -159,6 +158,8 @@ class NBCommonClient:
|
|||
return None
|
||||
|
||||
def _xmpp_connect(self, socket_type):
|
||||
if socket_type == 'plain' and self.Connection.ssl_lib:
|
||||
socket_type = 'ssl'
|
||||
self.connected = socket_type
|
||||
self._xmpp_connect_machine()
|
||||
|
||||
|
@ -169,7 +170,6 @@ class NBCommonClient:
|
|||
and features tag handling. Calls _on_stream_start when stream is
|
||||
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] ))
|
||||
|
||||
def on_next_receive(mode):
|
||||
|
@ -181,6 +181,7 @@ class NBCommonClient:
|
|||
|
||||
if not mode:
|
||||
# starting state
|
||||
if self.__dict__.has_key('Dispatcher'): self.Dispatcher.PlugOut()
|
||||
d=dispatcher_nb.Dispatcher().PlugIn(self)
|
||||
on_next_receive('RECEIVE_DOCUMENT_ATTRIBUTES')
|
||||
|
||||
|
@ -222,11 +223,38 @@ class NBCommonClient:
|
|||
elif mode == 'STREAM_STARTED':
|
||||
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):
|
||||
'''Called when stream is opened. To be overriden in derived classes.'''
|
||||
|
||||
def _on_connect_failure(self, retry=None, err_message=None):
|
||||
self.connected = None
|
||||
self.connected = ''
|
||||
if err_message:
|
||||
log.debug('While connecting: %s' % err_message)
|
||||
if self.socket:
|
||||
|
@ -234,6 +262,10 @@ class NBCommonClient:
|
|||
self.on_connect_failure(retry)
|
||||
|
||||
def _on_connect(self):
|
||||
if self.secure == 'tls':
|
||||
self._on_connect_failure('uaaaaaa')
|
||||
return
|
||||
print 'self.secure = %s' % self.secure
|
||||
self.onreceive(None)
|
||||
self.on_connect(self, self.connected)
|
||||
|
||||
|
@ -243,7 +275,7 @@ class NBCommonClient:
|
|||
self.Dispatcher.Event('', event_type, data)
|
||||
|
||||
|
||||
# moved from client.CommonClient:
|
||||
# moved from client.CommonClient (blocking client from xmpppy):
|
||||
def RegisterDisconnectHandler(self,handler):
|
||||
""" Register handler that will be called on disconnect."""
|
||||
self.disconnect_handlers.append(handler)
|
||||
|
@ -259,7 +291,7 @@ class NBCommonClient:
|
|||
raise IOError('Disconnected from server.')
|
||||
|
||||
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
|
||||
|
||||
def get_peerhost(self):
|
||||
|
@ -267,8 +299,7 @@ class NBCommonClient:
|
|||
to the server , (e.g. me).
|
||||
We will create listening socket on the same ip '''
|
||||
# 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):
|
||||
|
@ -372,16 +403,21 @@ class NonBlockingClient(NBCommonClient):
|
|||
self.protocol_type = 'XMPP'
|
||||
|
||||
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,
|
||||
on_proxy_failure, proxy, secure)
|
||||
on_proxy_failure, proxy, secure_tuple)
|
||||
|
||||
if hostname:
|
||||
xmpp_hostname = hostname
|
||||
else:
|
||||
xmpp_hostname = self.Server
|
||||
|
||||
estabilish_tls = self.secure == 'ssl'
|
||||
certs = (self.cacerts, self.mycerts)
|
||||
|
||||
self._on_tcp_failure = self._on_connect_failure
|
||||
|
||||
if proxy:
|
||||
# with proxies, client connects to proxy instead of directly to
|
||||
# XMPP server ((hostname, port))
|
||||
|
@ -390,13 +426,14 @@ class NonBlockingClient(NBCommonClient):
|
|||
tcp_host, tcp_port, proxy_user, proxy_pass = \
|
||||
transports_nb.get_proxy_data_from_dict(proxy)
|
||||
|
||||
self._on_tcp_failure = self.on_proxy_failure
|
||||
|
||||
if proxy['type'] == 'bosh':
|
||||
self.socket = bosh.NonBlockingBOSH(
|
||||
on_disconnect = self.on_disconnect,
|
||||
raise_event = self.raise_event,
|
||||
idlequeue = self.idlequeue,
|
||||
estabilish_tls = estabilish_tls,
|
||||
certs = certs,
|
||||
proxy_creds = (proxy_user, proxy_pass),
|
||||
xmpp_server = (xmpp_hostname, self.Port),
|
||||
domain = self.Server,
|
||||
|
@ -405,6 +442,7 @@ class NonBlockingClient(NBCommonClient):
|
|||
self.wait_for_restart_response = proxy['bosh_wait_for_restart_response']
|
||||
|
||||
else:
|
||||
self._on_tcp_failure = self.on_proxy_failure
|
||||
if proxy['type'] == 'socks5':
|
||||
proxy_class = transports_nb.NBSOCKS5ProxySocket
|
||||
elif proxy['type'] == 'http':
|
||||
|
@ -413,16 +451,19 @@ class NonBlockingClient(NBCommonClient):
|
|||
on_disconnect = self.on_disconnect,
|
||||
raise_event = self.raise_event,
|
||||
idlequeue = self.idlequeue,
|
||||
estabilish_tls = estabilish_tls,
|
||||
certs = certs,
|
||||
proxy_creds = (proxy_user, proxy_pass),
|
||||
xmpp_server = (xmpp_hostname, self.Port))
|
||||
else:
|
||||
self._on_tcp_failure = self._on_connect_failure
|
||||
tcp_host=xmpp_hostname
|
||||
tcp_port=self.Port
|
||||
self.socket = transports_nb.NonBlockingTCP(
|
||||
on_disconnect = self.on_disconnect,
|
||||
raise_event = self.raise_event,
|
||||
idlequeue = self.idlequeue)
|
||||
idlequeue = self.idlequeue,
|
||||
estabilish_tls = estabilish_tls,
|
||||
certs = certs)
|
||||
|
||||
self.socket.PlugIn(self)
|
||||
|
||||
|
@ -434,31 +475,31 @@ class NonBlockingClient(NBCommonClient):
|
|||
|
||||
|
||||
|
||||
|
||||
def _on_stream_start(self):
|
||||
'''
|
||||
Called after XMPP stream is opened.
|
||||
In pure XMPP client, TLS negotiation may follow after esabilishing a stream.
|
||||
'''
|
||||
self.onreceive(None)
|
||||
if self.connected == 'tcp':
|
||||
if not self.connected or not self._secure:
|
||||
# if we are disconnected or TLS/SSL is not desired, return
|
||||
if self.connected == 'plain':
|
||||
if self.secure == 'plain':
|
||||
# if we want plain connection, we're done now
|
||||
self._on_connect()
|
||||
return
|
||||
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()
|
||||
return
|
||||
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()
|
||||
return
|
||||
# otherwise start TLS
|
||||
tls_nb.NonBlockingTLS().PlugIn(
|
||||
self,
|
||||
on_tls_success=lambda: self._xmpp_connect(socket_type='tls'),
|
||||
on_tls_failure=self._on_connect_failure)
|
||||
elif self.connected == 'tls':
|
||||
# otherwise start TLS
|
||||
log.info("TLS supported by remote server. Requesting TLS start.")
|
||||
self._tls_negotiation_handler()
|
||||
elif self.connected in ['ssl', 'tls']:
|
||||
self._on_connect()
|
||||
|
||||
|
||||
|
|
|
@ -27,24 +27,12 @@ import traceback
|
|||
|
||||
import logging
|
||||
log = logging.getLogger('gajim.c.x.tls_nb')
|
||||
|
||||
# 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
|
||||
log.setLevel(logging.DEBUG)
|
||||
|
||||
USE_PYOPENSSL = False
|
||||
|
||||
|
||||
#TODO: add callback set from PlugIn for errors during runtime
|
||||
# - sth like on_disconnect in socket wrappers
|
||||
|
||||
PYOPENSSL = 'PYOPENSSL'
|
||||
PYSTDLIB = 'PYSTDLIB'
|
||||
|
||||
try:
|
||||
#raise ImportError("Manually disabled PyOpenSSL")
|
||||
|
@ -235,6 +223,11 @@ class StdlibSSLWrapper(SSLWrapper):
|
|||
class NonBlockingTLS(PlugIn):
|
||||
''' 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)
|
||||
ssl_h_bits = { "SSL_ST_CONNECT": 0x1000, "SSL_ST_ACCEPT": 0x2000,
|
||||
"SSL_CB_LOOP": 0x01, "SSL_CB_EXIT": 0x02,
|
||||
|
@ -242,56 +235,23 @@ class NonBlockingTLS(PlugIn):
|
|||
"SSL_CB_ALERT": 0x4000,
|
||||
"SSL_CB_HANDSHAKE_START": 0x10, "SSL_CB_HANDSHAKE_DONE": 0x20}
|
||||
|
||||
def PlugIn(self, owner, on_tls_success, on_tls_failure, now=0):
|
||||
''' If the 'now' argument is true then starts using encryption immidiatedly.
|
||||
If 'now' in false then starts encryption as soon as TLS feature is
|
||||
declared by the server (if it were already declared - it is ok).
|
||||
def PlugIn(self, owner):
|
||||
'''
|
||||
if owner.__dict__.has_key('NonBlockingTLS'):
|
||||
return # Already enabled.
|
||||
start using encryption immediately
|
||||
'''
|
||||
log.info('Starting TLS estabilishing')
|
||||
PlugIn.PlugIn(self, owner)
|
||||
self.on_tls_success = on_tls_success
|
||||
self.on_tls_faliure = on_tls_failure
|
||||
if now:
|
||||
try:
|
||||
res = self._startSSL()
|
||||
except Exception, e:
|
||||
log.error("PlugIn: while trying _startSSL():", exc_info=True)
|
||||
#traceback.print_exc()
|
||||
self._owner.socket.pollend()
|
||||
return
|
||||
on_tls_success()
|
||||
return res
|
||||
if self._owner.Dispatcher.Stream.features:
|
||||
try:
|
||||
self.FeaturesHandler(self._owner.Dispatcher, self._owner.Dispatcher.Stream.features)
|
||||
except NodeProcessed:
|
||||
pass
|
||||
else:
|
||||
self._owner.RegisterHandlerOnce('features',self.FeaturesHandler, xmlns=NS_STREAMS)
|
||||
self.starttls = None
|
||||
|
||||
def plugout(self,now=0):
|
||||
''' Unregisters TLS handler's from owner's dispatcher. Take note that encription
|
||||
can not be stopped once started. You can only break the connection and start over.'''
|
||||
# if dispatcher is not plugged we cannot (un)register handlers
|
||||
if self._owner.__dict__.has_key('Dispatcher'):
|
||||
self._owner.UnregisterHandler('features', self.FeaturesHandler,xmlns=NS_STREAMS)
|
||||
self._owner.Dispatcher.PlugOut()
|
||||
self._owner = None
|
||||
print 'inplugin'
|
||||
try:
|
||||
self._owner._plug_idle(writable=False, readable=False)
|
||||
res = self._startSSL()
|
||||
except Exception, e:
|
||||
log.error("PlugIn: while trying _startSSL():", exc_info=True)
|
||||
#traceback.print_exc()
|
||||
return False
|
||||
return res
|
||||
|
||||
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):
|
||||
print >> stream, "Digest (SHA-1):", cert.digest("sha1")
|
||||
|
@ -317,27 +277,26 @@ class NonBlockingTLS(PlugIn):
|
|||
''' Immidiatedly switch socket to TLS mode. Used internally.'''
|
||||
log.debug("_startSSL called")
|
||||
if USE_PYOPENSSL: return self._startSSL_pyOpenSSL()
|
||||
return self._startSSL_stdlib()
|
||||
else: return self._startSSL_stdlib()
|
||||
|
||||
def _startSSL_pyOpenSSL(self):
|
||||
#log.debug("_startSSL_pyOpenSSL called, thread id: %s", str(thread.get_ident()))
|
||||
log.debug("_startSSL_pyOpenSSL called")
|
||||
tcpsock = self._owner.Connection
|
||||
tcpsock = self._owner
|
||||
# FIXME: should method be configurable?
|
||||
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.TLSv1_METHOD)
|
||||
tcpsock._sslContext = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
|
||||
tcpsock.ssl_errnum = 0
|
||||
tcpsock._sslContext.set_verify(OpenSSL.SSL.VERIFY_PEER, self._ssl_verify_callback)
|
||||
cacerts = os.path.join('../data', 'other', 'cacerts.pem')
|
||||
try:
|
||||
tcpsock._sslContext.load_verify_locations(cacerts)
|
||||
tcpsock._sslContext.load_verify_locations(self.cacerts)
|
||||
except:
|
||||
log.warning('Unable to load SSL certificats from file %s' % \
|
||||
os.path.abspath(cacerts))
|
||||
log.warning('Unable to load SSL certificates from file %s' % \
|
||||
os.path.abspath(self.cacerts))
|
||||
# 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()
|
||||
f = open('%s/.gajim/cacerts.pem' % os.environ['HOME'])
|
||||
f = open(self.mycerts)
|
||||
lines = f.readlines()
|
||||
i = 0
|
||||
begin = -1
|
||||
|
@ -352,11 +311,10 @@ class NonBlockingTLS(PlugIn):
|
|||
store.add_cert(X509cert)
|
||||
except OpenSSL.crypto.Error, exception_obj:
|
||||
log.warning('Unable to load a certificate from file %s: %s' %\
|
||||
('%s/.gajim/cacerts.pem' % os.environ['HOME'], exception_obj.args[0][0][2]))
|
||||
(self.mycerts, exception_obj.args[0][0][2]))
|
||||
except:
|
||||
log.warning(
|
||||
'Unknown error while loading certificate from file %s' % \
|
||||
'%s/.gajim/cacerts.pem' % os.environ['HOME'])
|
||||
log.warning('Unknown error while loading certificate from file %s' %\
|
||||
self.mycerts)
|
||||
begin = -1
|
||||
i += 1
|
||||
tcpsock._sslObj = OpenSSL.SSL.Connection(tcpsock._sslContext, tcpsock._sock)
|
||||
|
@ -367,67 +325,52 @@ class NonBlockingTLS(PlugIn):
|
|||
tcpsock._send = wrapper.send
|
||||
|
||||
log.debug("Initiating handshake...")
|
||||
# FIXME: Figure out why _connect_success is called before the
|
||||
# SSL handshake is completed in STARTTLS mode. See #2838.
|
||||
tcpsock._sslObj.setblocking(True)
|
||||
try:
|
||||
self.starttls='in progress'
|
||||
tcpsock._sslObj.do_handshake()
|
||||
except:
|
||||
log.error('Error while TLS handshake: ', exc_info=True)
|
||||
self.on_tls_failure('Error while TLS Handshake')
|
||||
return
|
||||
return False
|
||||
tcpsock._sslObj.setblocking(False)
|
||||
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):
|
||||
log.debug("_startSSL_stdlib called")
|
||||
tcpsock=self._owner.Connection
|
||||
tcpsock._sock.setblocking(True)
|
||||
tcpsock._sslObj = socket.ssl(tcpsock._sock, None, None)
|
||||
tcpsock._sock.setblocking(False)
|
||||
tcpsock._sslIssuer = tcpsock._sslObj.issuer()
|
||||
tcpsock._sslServer = tcpsock._sslObj.server()
|
||||
wrapper = StdlibSSLWrapper(tcpsock._sslObj, tcpsock._sock)
|
||||
tcpsock._recv = wrapper.recv
|
||||
tcpsock._send = wrapper.send
|
||||
self.starttls='success'
|
||||
tcpsock=self._owner
|
||||
try:
|
||||
tcpsock._sock.setblocking(True)
|
||||
tcpsock._sslObj = socket.ssl(tcpsock._sock, None, None)
|
||||
tcpsock._sock.setblocking(False)
|
||||
tcpsock._sslIssuer = tcpsock._sslObj.issuer()
|
||||
tcpsock._sslServer = tcpsock._sslObj.server()
|
||||
wrapper = StdlibSSLWrapper(tcpsock._sslObj, tcpsock._sock)
|
||||
tcpsock._recv = wrapper.recv
|
||||
tcpsock._send = wrapper.send
|
||||
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):
|
||||
# Exceptions can't propagate up through this callback, so print them here.
|
||||
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:
|
||||
return True
|
||||
self._owner.Connection.ssl_errnum = errnum
|
||||
self._owner.Connection.ssl_cert_pem = OpenSSL.crypto.dump_certificate(
|
||||
self._owner.ssl_errnum = errnum
|
||||
self._owner.ssl_cert_pem = OpenSSL.crypto.dump_certificate(
|
||||
OpenSSL.crypto.FILETYPE_PEM, cert)
|
||||
return True
|
||||
except:
|
||||
log.error("Exception caught in _ssl_info_callback:", exc_info=True)
|
||||
traceback.print_exc() # Make sure something is printed, even if log is disabled.
|
||||
|
||||
def StartTLSHandler(self, conn, starttls):
|
||||
''' Handle server reply if TLS is allowed to process. Behaves accordingly.
|
||||
Used internally.'''
|
||||
if starttls.getNamespace() <> NS_TLS:
|
||||
self.on_tls_failure('Unknown namespace: %s' % starttls.getNamespace())
|
||||
return
|
||||
self.starttls = starttls.getName()
|
||||
if self.starttls == 'failure':
|
||||
self.on_tls_failure('TLS <failure> received: %s' % self.starttls)
|
||||
return
|
||||
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()
|
||||
|
|
|
@ -21,6 +21,7 @@ from simplexml import ustr
|
|||
from client import PlugIn
|
||||
from idlequeue import IdleObject
|
||||
from protocol import *
|
||||
from tls_nb import NonBlockingTLS
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
@ -76,15 +77,17 @@ DATA_RECEIVED='DATA RECEIVED'
|
|||
DATA_SENT='DATA SENT'
|
||||
|
||||
|
||||
DISCONNECTED ='DISCONNECTED'
|
||||
DISCONNECTING ='DISCONNECTING'
|
||||
CONNECTING ='CONNECTING'
|
||||
CONNECTED ='CONNECTED'
|
||||
|
||||
# transports have different constructor and same connect
|
||||
DISCONNECTED = 'DISCONNECTED'
|
||||
DISCONNECTING = 'DISCONNECTING'
|
||||
CONNECTING = 'CONNECTING'
|
||||
PROXY_CONNECTING = 'PROXY_CONNECTING'
|
||||
CONNECTED = 'CONNECTED'
|
||||
STATES = [DISCONNECTED, DISCONNECTING, CONNECTING, PROXY_CONNECTING, CONNECTED]
|
||||
# transports have different arguments in constructor and same in connect()
|
||||
# method
|
||||
|
||||
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)
|
||||
self.raise_event = raise_event
|
||||
self.on_disconnect = on_disconnect
|
||||
|
@ -94,7 +97,11 @@ class NonBlockingTransport(PlugIn):
|
|||
self.on_receive = None
|
||||
self.server = 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.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):
|
||||
'''
|
||||
connect method should have the same declaration in all derived transports
|
||||
|
||||
'''
|
||||
assert(self.state == DISCONNECTED)
|
||||
self.on_connect = on_connect
|
||||
self.on_connect_failure = on_connect_failure
|
||||
(self.server, self.port) = conn_5tuple[4][:2]
|
||||
|
@ -124,14 +129,16 @@ class NonBlockingTransport(PlugIn):
|
|||
|
||||
|
||||
def set_state(self, newstate):
|
||||
assert(newstate in [DISCONNECTED, CONNECTING, CONNECTED, DISCONNECTING])
|
||||
assert(newstate in STATES)
|
||||
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 '''
|
||||
# data is reference to socket wrapper instance. We don't need it in client
|
||||
# because
|
||||
self.peerhost = data._sock.getsockname()
|
||||
self.set_state(CONNECTED)
|
||||
self.on_connect()
|
||||
|
||||
|
@ -144,9 +151,9 @@ class NonBlockingTransport(PlugIn):
|
|||
self.on_connect_failure(err_message=err_message)
|
||||
|
||||
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.' %
|
||||
(raw_data, self.state))
|
||||
(raw_data, self.get_state()))
|
||||
|
||||
|
||||
def disconnect(self, do_callback=True):
|
||||
|
@ -166,7 +173,7 @@ class NonBlockingTransport(PlugIn):
|
|||
return
|
||||
self.on_receive = recv_handler
|
||||
|
||||
def tcp_connection_started(self):
|
||||
def tcp_connecting_started(self):
|
||||
self.set_state(CONNECTING)
|
||||
# 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
|
||||
'''
|
||||
def __init__(self, raise_event, on_disconnect, idlequeue):
|
||||
def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls, certs):
|
||||
'''
|
||||
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
|
||||
self.sendqueue = []
|
||||
|
||||
# bytes remained from the last send message
|
||||
self.sendbuff = ''
|
||||
|
||||
self.terminator = '</stream:stream>'
|
||||
|
||||
def start_disconnect(self):
|
||||
self.send('</stream:stream>')
|
||||
NonBlockingTransport.start_disconnect(self)
|
||||
self.send('</stream:stream>', now=True)
|
||||
self.disconnect()
|
||||
|
||||
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):
|
||||
# connecting in progress
|
||||
log.info('After NB connect() of %s. "%s" raised => CONNECTING' % (id(self),errstr))
|
||||
self.tcp_connection_started()
|
||||
self.tcp_connecting_started()
|
||||
return
|
||||
elif errnum in (0, 10056, errno.EISCONN):
|
||||
# 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.server, self.port, errnum, errstr))
|
||||
|
||||
def _on_connect(self, data):
|
||||
def _on_connect(self):
|
||||
''' with TCP socket, we have to remove send-timeout '''
|
||||
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):
|
||||
'''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()
|
||||
|
||||
def pollout(self):
|
||||
'''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))
|
||||
self._on_connect(self)
|
||||
self._on_connect()
|
||||
return
|
||||
self._do_send()
|
||||
|
||||
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.server, self.port))
|
||||
else :
|
||||
self.disconnect()
|
||||
|
||||
def disconnect(self, do_callback=True):
|
||||
if self.state == DISCONNECTED:
|
||||
if self.get_state() == DISCONNECTED:
|
||||
return
|
||||
self.set_state(DISCONNECTED)
|
||||
self.idlequeue.unplug_idle(self.fd)
|
||||
|
@ -330,8 +350,8 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
|
|||
'''
|
||||
Implemntation of IdleObject function called on timeouts from IdleQueue.
|
||||
'''
|
||||
log.warn('read_timeout called, state == %s' % self.state)
|
||||
if self.state==CONNECTING:
|
||||
log.warn('read_timeout called, state == %s' % self.get_state())
|
||||
if self.get_state()==CONNECTING:
|
||||
# if read_timeout is called during connecting, connect() didn't end yet
|
||||
# thus we have to call the tcp failure callback
|
||||
self._on_connect_failure('Error during connect to %s:%s' %
|
||||
|
@ -342,16 +362,16 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
|
|||
|
||||
|
||||
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)
|
||||
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):
|
||||
if self.fd:
|
||||
NonBlockingTransport.remove_timeout(self)
|
||||
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):
|
||||
'''Append raw_data to the queue of messages to be send.
|
||||
|
@ -368,6 +388,7 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
|
|||
self._do_send()
|
||||
else:
|
||||
self.sendqueue.append(r)
|
||||
|
||||
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,
|
||||
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)
|
||||
|
||||
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
|
||||
# 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
|
||||
log.error("Connection to %s lost: %s %s" % ( self.server, errnum, errstr))
|
||||
if self.on_remote_disconnect:
|
||||
self.on_remote_disconnect()
|
||||
else:
|
||||
self.disconnect()
|
||||
log.error("Connection to %s lost: %s %s" % ( self.server, errnum, errstr), exc_info=True)
|
||||
if hasattr(self, 'on_remote_disconnect'): self.on_remote_disconnect()
|
||||
else: self.disconnect()
|
||||
return
|
||||
|
||||
if received is None:
|
||||
# in case of some other exception
|
||||
# FIXME: is this needed??
|
||||
# in case of SSL error - because there are two types of TLS wrappers, the TLS
|
||||
# pluging recv method returns None in case of error
|
||||
print 'SSL ERROR'
|
||||
if errnum != 0:
|
||||
log.error("CConnection to %s lost: %s %s" % (self.server, errnum, errstr))
|
||||
self.disconnect()
|
||||
|
@ -456,6 +474,7 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
|
|||
|
||||
# we have received some bytes, stop the timeout!
|
||||
self.renew_send_timeout()
|
||||
print '-->%s<--' % received
|
||||
# pass received data to owner
|
||||
if self.on_receive:
|
||||
self.raise_event(DATA_RECEIVED, received)
|
||||
|
@ -479,10 +498,11 @@ class NonBlockingHTTP(NonBlockingTCP):
|
|||
HTTP headers from incoming messages
|
||||
'''
|
||||
|
||||
def __init__(self, raise_event, on_disconnect, idlequeue, on_http_request_possible,
|
||||
on_persistent_fallback, http_dict):
|
||||
def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls, certs,
|
||||
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'])
|
||||
if self.http_protocol is None:
|
||||
|
@ -522,10 +542,7 @@ class NonBlockingHTTP(NonBlockingTCP):
|
|||
self.disconnect(do_callback=False)
|
||||
self.connect(
|
||||
conn_5tuple = self.conn_5tuple,
|
||||
# after connect, the socket will be plugged as writable - pollout will be
|
||||
# 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 = self.on_http_request_possible,
|
||||
on_connect_failure = self.disconnect)
|
||||
|
||||
else:
|
||||
|
@ -549,7 +566,7 @@ class NonBlockingHTTP(NonBlockingTCP):
|
|||
|
||||
if self.expected_length > len(self.recvbuff):
|
||||
# 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' %
|
||||
(self.expected_length, len(self.recvbuff)))
|
||||
return
|
||||
|
@ -587,6 +604,9 @@ class NonBlockingHTTP(NonBlockingTCP):
|
|||
credentials = '%s:%s' % (self.proxy_user, self.proxy_pass)
|
||||
credentials = base64.encodestring(credentials).strip()
|
||||
headers.append('Proxy-Authorization: Basic %s' % credentials)
|
||||
else:
|
||||
headers.append('Connection: Keep-Alive')
|
||||
|
||||
headers.append('\r\n')
|
||||
headers = '\r\n'.join(headers)
|
||||
return('%s%s\r\n' % (headers, httpbody))
|
||||
|
@ -611,6 +631,8 @@ class NonBlockingHTTP(NonBlockingTCP):
|
|||
headers[row[0][:-1]] = row[1]
|
||||
return (statusline, headers, httpbody)
|
||||
|
||||
|
||||
|
||||
class NonBlockingHTTPBOSH(NonBlockingHTTP):
|
||||
|
||||
|
||||
|
@ -646,29 +668,26 @@ class NBProxySocket(NonBlockingTCP):
|
|||
Interface for proxy socket wrappers - when tunnneling XMPP over proxies,
|
||||
some connecting process usually has to be done before opening stream.
|
||||
'''
|
||||
def __init__(self, raise_event, on_disconnect, idlequeue, xmpp_server,
|
||||
proxy_creds=(None,None)):
|
||||
def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls, certs,
|
||||
xmpp_server, proxy_creds=(None,None)):
|
||||
|
||||
self.proxy_user, self.proxy_pass = proxy_creds
|
||||
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 connect(self, conn_5tuple, on_connect, on_connect_failure):
|
||||
def _on_connect(self):
|
||||
'''
|
||||
connect method is extended by proxy credentials and xmpp server hostname
|
||||
and port because those are needed for
|
||||
The idea is to insert Proxy-specific mechanism after TCP connect and
|
||||
before XMPP stream opening (which is done from client).
|
||||
We're redefining _on_connect method to insert proxy-specific mechanism before
|
||||
invoking the ssl connection and then client callback. All the proxy connecting
|
||||
is done before XML stream is opened.
|
||||
'''
|
||||
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):
|
||||
'''to be implemented in each proxy socket wrapper'''
|
||||
pass
|
||||
|
||||
|
||||
|
@ -713,7 +732,7 @@ class NBHTTPProxySocket(NBProxySocket):
|
|||
return
|
||||
if len(reply) != 2:
|
||||
pass
|
||||
self.after_proxy_connect()
|
||||
NonBlockingT._on_connect(self)
|
||||
#self.onreceive(self._on_proxy_auth)
|
||||
|
||||
def _on_proxy_auth(self, reply):
|
||||
|
|
|
@ -545,7 +545,7 @@ class Interface:
|
|||
ctrl.print_conversation('Error %s: %s' % (array[2], array[1]))
|
||||
|
||||
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
|
||||
self.roster.draw_account(account)
|
||||
|
||||
|
|
Loading…
Reference in New Issue