diff --git a/src/common/connection.py b/src/common/connection.py index fc6a71a56..932a8c12a 100644 --- a/src/common/connection.py +++ b/src/common/connection.py @@ -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 , 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) diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py index 25a771e05..687b7d9d1 100644 --- a/src/common/connection_handlers.py +++ b/src/common/connection_handlers.py @@ -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 diff --git a/src/common/xmpp/bosh.py b/src/common/xmpp/bosh.py index 062084d1c..d89cd6a50 100644 --- a/src/common/xmpp/bosh.py +++ b/src/common/xmpp/bosh.py @@ -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) diff --git a/src/common/xmpp/client_nb.py b/src/common/xmpp/client_nb.py index 8e15f3672..7e9e347bf 100644 --- a/src/common/xmpp/client_nb.py +++ b/src/common/xmpp/client_nb.py @@ -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 + self.RegisterHandlerOnce('proceed', self._tls_negotiation_handler, + xmlns=NS_TLS) + self.RegisterHandlerOnce('failure', self._tls_negotiation_handler, + xmlns=NS_TLS) + self.send('' % 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 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() diff --git a/src/common/xmpp/tls_nb.py b/src/common/xmpp/tls_nb.py index bd2091817..2f957f0bb 100644 --- a/src/common/xmpp/tls_nb.py +++ b/src/common/xmpp/tls_nb.py @@ -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 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('' % 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 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() diff --git a/src/common/xmpp/transports_nb.py b/src/common/xmpp/transports_nb.py index 29b95124f..d34ee3f7e 100644 --- a/src/common/xmpp/transports_nb.py +++ b/src/common/xmpp/transports_nb.py @@ -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 = '' def start_disconnect(self): - self.send('') NonBlockingTransport.start_disconnect(self) + self.send('', 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): diff --git a/src/gajim.py b/src/gajim.py index 748810a3b..be9eaab1a 100755 --- a/src/gajim.py +++ b/src/gajim.py @@ -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)