- improved SSL connections with BOSH - SSL over HTTP proxy is possible now,

Gajim will do HTTP CONNECT on proxy to reach the BOSH Conn manager and try
  to estabilish TLS (same as what firefox do when approaching HTTPS server via
  proxy)
- moved proxy-connecting code to xmpp/proxy_connectors.py
- debugged SOCKS5 proxy code
- tested with Tigase server
This commit is contained in:
tomk 2008-08-14 21:48:43 +00:00
parent 4504861084
commit a76c173816
9 changed files with 405 additions and 405 deletions

View File

@ -424,10 +424,8 @@ 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
@ -519,9 +517,11 @@ class Connection(ConnectionHandlers):
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
scheme = common.xmpp.transports_nb.urisplit(self._proxy['bosh_uri'])[0]
if scheme=='https':
self._connection_types = ['ssl']
else:
self._connection_types = ['plain']
host = self.select_next_host(self._hosts)
self._current_host = host
@ -553,7 +553,7 @@ class Connection(ConnectionHandlers):
if self._current_type == 'ssl':
# SSL (force TLS on different port than plain)
# If we do TLS over BOSH, port of XMPP server should be the standard one
# and TLS should be negotiated because immediate TLS on 5223 is deprecated
# and TLS should be negotiated because TLS on 5223 is deprecated
if self._proxy and self._proxy['type']=='bosh':
port = self._current_host['port']
else:
@ -583,7 +583,6 @@ class Connection(ConnectionHandlers):
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,
@ -1303,6 +1302,7 @@ class Connection(ConnectionHandlers):
self.connect(config)
def _on_new_account(self, con = None, con_type = None):
print 'on_new_acc- con: %s, con_type: %s' % (con, con_type)
if not con_type:
self.dispatch('NEW_ACC_NOT_CONNECTED',
(_('Could not connect to "%s"') % self._hostname))

View File

@ -27,7 +27,7 @@ and use only methods for access all values you should not have any problems.
"""
import simplexml, protocol, auth_nb, transports_nb, roster_nb
import dispatcher_nb, features_nb, idlequeue, bosh, tls_nb
import dispatcher_nb, features_nb, idlequeue, bosh, tls_nb, proxy_connectors
from client_nb import *
from client import *
from protocol import *

View File

@ -253,8 +253,6 @@ class NonBlockingNonSASL(PlugIn):
def plugin(self, owner):
''' Determine the best auth method (digest/0k/plain) and use it for auth.
Returns used method name on success. Used internally. '''
if not self.resource:
return self.authComponent(owner)
log.info('Querying server about possible auth methods')
self.owner = owner
@ -303,33 +301,6 @@ class NonBlockingNonSASL(PlugIn):
log.error('Authentication failed!')
return self.on_auth(None)
def authComponent(self,owner):
''' Authenticate component. Send handshake stanza and wait for result. Returns "ok" on success. '''
self.handshake=0
owner.send(Node(NS_COMPONENT_ACCEPT+' handshake',
payload=[sha.new(owner.Dispatcher.Stream._document_attrs['id']+self.password).hexdigest()]))
owner.RegisterHandler('handshake', self.handshakeHandler, xmlns=NS_COMPONENT_ACCEPT)
self._owner.onreceive(self._on_auth_component)
def _on_auth_component(self, data):
''' called when we receive some response, after we send the handshake '''
if data:
self.Dispatcher.ProcessNonBlocking(data)
if not self.handshake:
log.info('waiting on handshake')
return
self._owner.onreceive(None)
owner._registered_name=self.user
if self.handshake+1:
return self.on_auth('ok')
self.on_auth(None)
def handshakeHandler(self,disp,stanza):
''' Handler for registering in dispatcher for accepting transport authentication. '''
if stanza.getName() == 'handshake':
self.handshake=1
else:
self.handshake=-1
class NonBlockingBind(PlugIn):
''' Bind some JID to the current connection to allow router know of our location.'''

View File

@ -1,11 +1,29 @@
## bosh.py
##
##
## Copyright (C) 2008 Tomas Karasek <tom.to.the.k@gmail.com>
##
## This file is part of Gajim.
##
## Gajim is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published
## by the Free Software Foundation; version 3 only.
##
## Gajim is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
import locale, random
import locale, random, sha
from transports_nb import NonBlockingTransport, NonBlockingHTTPBOSH,\
CONNECTED, CONNECTING, DISCONNECTED, DISCONNECTING,\
urisplit
urisplit, DISCONNECT_TIMEOUT_SECONDS
from protocol import BOSHBody
from simplexml import Node
import sha
import logging
log = logging.getLogger('gajim.c.x.bosh')
@ -62,6 +80,13 @@ class NonBlockingBOSH(NonBlockingTransport):
self.key_stack = None
self.ack_checker = None
self.after_init = False
self.proxy_dict = {}
if self.over_proxy and self.estabilish_tls:
self.proxy_dict['type'] = 'http'
# with SSL over proxy, we do HTTP CONNECT to proxy to open a channel to
# BOSH Connection Manager
self.proxy_dict['xmpp_server'] = (urisplit(self.bosh_uri)[1], self.bosh_port)
self.proxy_dict['credentials'] = self.proxy_creds
def connect(self, conn_5tuple, on_connect, on_connect_failure):
@ -81,13 +106,9 @@ class NonBlockingBOSH(NonBlockingTransport):
self.http_socks.append(self.get_new_http_socket())
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
# for connecting failure eventually (the callback is different than callback
# for errors occurring after connection is etabilished)
self.http_socks[0].connect(
conn_5tuple = conn_5tuple,
on_connect = lambda: self._on_connect(),
on_connect = self._on_connect,
on_connect_failure = self._on_connect_failure)
def _on_connect(self):
@ -98,27 +119,28 @@ class NonBlockingBOSH(NonBlockingTransport):
def set_timeout(self, timeout):
if self.get_state() in [CONNECTING, CONNECTED] 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.get_state(), self.fd))
def on_http_request_possible(self):
'''
Called after HTTP response is received - another request is possible.
There should be always one pending request on BOSH CM.
Called after HTTP response is received - when another request is possible.
There should be always one pending request to BOSH CM.
'''
log.info('on_http_req possible, state:\n%s' % self.get_current_state())
if self.get_state() == DISCONNECTING:
self.disconnect()
return
if self.get_state()==DISCONNECTED: return
#Hack for making the non-secure warning dialog work
if hasattr(self._owner, 'NonBlockingNonSASL') or hasattr(self._owner, 'SASL'):
self.send_BOSH(None)
if self._owner.got_features:
if (hasattr(self._owner, 'NonBlockingNonSASL') or hasattr(self._owner, 'SASL')):
self.send_BOSH(None)
else:
return
else:
self.http_socks[0]._plug_idle(writable=False, readable=True)
return
self.send_BOSH(None)
def get_socket_in(self, state):
@ -129,7 +151,6 @@ class NonBlockingBOSH(NonBlockingTransport):
def get_free_socket(self):
if self.http_pipelining:
assert( len(self.http_socks) == 1 )
return self.get_socket_in(CONNECTED)
else:
last_recv_time, tmpsock = 0, None
@ -184,11 +205,12 @@ class NonBlockingBOSH(NonBlockingTransport):
# CONNECTED with too many pending requests
s = self.get_socket_in(DISCONNECTED)
# if we have DISCONNECTED socket, lets connect it and ...
# if we have DISCONNECTED socket, lets connect it and plug for send
if s:
self.connect_and_flush(s)
else:
if len(self.http_socks) > 1: return
#if len(self.http_socks) > 1: return
print 'connecting sock'
ss = self.get_new_http_socket()
self.http_socks.append(ss)
self.connect_and_flush(ss)
@ -200,7 +222,7 @@ class NonBlockingBOSH(NonBlockingTransport):
if s:
s._plug_idle(writable=True, readable=True)
else:
log.error('=====!!!!!!!!====> Couldnt get free socket in plug_socket())')
log.error('=====!!!!!!!!====> Couldn\'t get free socket in plug_socket())')
def build_stanza(self, socket):
if self.prio_bosh_stanzas:
@ -222,21 +244,20 @@ class NonBlockingBOSH(NonBlockingTransport):
log.info('sending msg with rid=%s to sock %s' % (stanza.getAttr('rid'), id(socket)))
socket.send(stanza)
self.renew_bosh_wait_timeout()
#socket.send(stanza)
self.renew_bosh_wait_timeout(self.bosh_wait + 3)
return stanza
def on_bosh_wait_timeout(self):
log.error('Connection Manager didn\'t respond within % seconds --> forcing \
disconnect' % self.bosh_wait)
log.error('Connection Manager didn\'t respond within %s + 3 seconds --> forcing disconnect' % self.bosh_wait)
self.disconnect()
def renew_bosh_wait_timeout(self):
def renew_bosh_wait_timeout(self, timeout):
if self.wait_cb_time is not None:
self.remove_bosh_wait_timeout()
sched_time = self.idlequeue.set_alarm(self.on_bosh_wait_timeout, self.bosh_wait+10)
sched_time = self.idlequeue.set_alarm(self.on_bosh_wait_timeout, timeout)
self.wait_cb_time = sched_time
def remove_bosh_wait_timeout(self):
@ -244,10 +265,17 @@ class NonBlockingBOSH(NonBlockingTransport):
self.on_bosh_wait_timeout,
self.wait_cb_time)
def on_persistent_fallback(self):
def on_persistent_fallback(self, socket):
log.warn('Fallback to nonpersistent HTTP (no pipelining as well)')
self.http_persistent = False
self.http_pipelining = False
if socket.http_persistent:
socket.http_persistent = False
self.http_persistent = False
self.http_pipelining = False
socket.disconnect(do_callback=False)
self.connect_and_flush(socket)
else:
socket.disconnect()
def handle_body_attrs(self, stanza_attrs):
@ -277,7 +305,8 @@ class NonBlockingBOSH(NonBlockingTransport):
if stanza_attrs.has_key('condition'):
condition = stanza_attrs['condition']
log.error('Received terminating stanza: %s - %s' % (condition, bosh_errors[condition]))
self.set_state(DISCONNECTING)
self.disconnect()
return
if stanza_attrs['type'] == 'error':
# recoverable error
@ -295,8 +324,6 @@ class NonBlockingBOSH(NonBlockingTransport):
def send(self, stanza, now=False):
# body tags should be send only via send_BOSH()
assert(not isinstance(stanza, BOSHBody))
self.send_BOSH(stanza)
@ -350,6 +377,7 @@ class NonBlockingBOSH(NonBlockingTransport):
def start_disconnect(self):
NonBlockingTransport.start_disconnect(self)
self.renew_bosh_wait_timeout(DISCONNECT_TIMEOUT_SECONDS)
self.send_BOSH(
(BOSHBody(attrs={'sid': self.bosh_sid, 'type': 'terminate'}), True))
@ -359,7 +387,7 @@ class NonBlockingBOSH(NonBlockingTransport):
'http_port': self.bosh_port,
'http_version': self.http_version,
'http_persistent': self.http_persistent,
'over_proxy': self.over_proxy}
'add_proxy_headers': self.over_proxy and not self.estabilish_tls}
if self.use_proxy_auth:
http_dict['proxy_user'], http_dict['proxy_pass'] = self.proxy_creds
@ -372,6 +400,7 @@ class NonBlockingBOSH(NonBlockingTransport):
certs = self.certs,
on_http_request_possible = self.on_http_request_possible,
http_dict = http_dict,
proxy_dict = self.proxy_dict,
on_persistent_fallback = self.on_persistent_fallback)
s.onreceive(self.on_received_http)
s.set_stanza_build_cb(self.build_stanza)

View File

@ -56,12 +56,12 @@ class NBCommonClient:
self._owner = self
self._registered_name = None
self.connected = ''
self._component=0
self.socket = None
self.on_connect = None
self.on_proxy_failure = None
self.on_connect_failure = None
self.proxy = None
self.got_features = False
def on_disconnect(self):
@ -72,7 +72,6 @@ class NBCommonClient:
'''
self.connected=''
log.debug('Client disconnected..')
for i in reversed(self.disconnect_handlers):
log.debug('Calling disconnect handler %s' % i)
i()
@ -84,18 +83,13 @@ class NBCommonClient:
self.NonBlockingNonSASL.PlugOut()
if self.__dict__.has_key('SASL'):
self.SASL.PlugOut()
if self.__dict__.has_key('NonBlockingTLS'):
self.NonBlockingTLS.PlugOut()
if self.__dict__.has_key('NBHTTPProxySocket'):
self.NBHTTPProxySocket.PlugOut()
if self.__dict__.has_key('NBSOCKS5ProxySocket'):
self.NBSOCKS5ProxySocket.PlugOut()
if self.__dict__.has_key('NonBlockingTCP'):
self.NonBlockingTCP.PlugOut()
if self.__dict__.has_key('NonBlockingHTTP'):
self.NonBlockingHTTP.PlugOut()
if self.__dict__.has_key('NonBlockingBOSH'):
self.NonBlockingBOSH.PlugOut()
log.debug('Client disconnected..')
def connect(self, on_connect, on_connect_failure, hostname=None, port=5222,
@ -181,7 +175,9 @@ class NBCommonClient:
if not mode:
# starting state
if self.__dict__.has_key('Dispatcher'): self.Dispatcher.PlugOut()
if self.__dict__.has_key('Dispatcher'):
self.Dispatcher.PlugOut()
self.got_features = False
d=dispatcher_nb.Dispatcher().PlugIn(self)
on_next_receive('RECEIVE_DOCUMENT_ATTRIBUTES')
@ -197,7 +193,7 @@ class NBCommonClient:
mode='FAILURE',
data='Error on stream open')
if self.incoming_stream_version() == '1.0':
if not self.Dispatcher.Stream.features:
if not self.got_features:
on_next_receive('RECEIVE_STREAM_FEATURES')
else:
log.info('got STREAM FEATURES in first recv')
@ -212,7 +208,7 @@ class NBCommonClient:
# sometimes <features> are received together with document
# attributes and sometimes on next receive...
self.Dispatcher.ProcessNonBlocking(data)
if not self.Dispatcher.Stream.features:
if not self.got_features:
self._xmpp_connect_machine(
mode='FAILURE',
data='Missing <features> in 1.0 stream')
@ -263,10 +259,6 @@ 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)
@ -343,7 +335,6 @@ class NBCommonClient:
# wrong user/pass, stop auth
self.connected = None
self._on_sasl_auth(None)
self.SASL.PlugOut()
elif self.SASL.startsasl == 'success':
auth_nb.NonBlockingBind().PlugIn(self)
if self.protocol_type == 'BOSH':
@ -418,6 +409,9 @@ class NonBlockingClient(NBCommonClient):
certs = (self.cacerts, self.mycerts)
self._on_tcp_failure = self._on_connect_failure
proxy_dict = {}
tcp_host=xmpp_hostname
tcp_port=self.Port
if proxy:
# with proxies, client connects to proxy instead of directly to
@ -444,27 +438,18 @@ class NonBlockingClient(NBCommonClient):
else:
self._on_tcp_failure = self.on_proxy_failure
if proxy['type'] == 'socks5':
proxy_class = transports_nb.NBSOCKS5ProxySocket
elif proxy['type'] == 'http':
proxy_class = transports_nb.NBHTTPProxySocket
self.socket = proxy_class(
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:
tcp_host=xmpp_hostname
tcp_port=self.Port
proxy_dict['type'] = proxy['type']
proxy_dict['xmpp_server'] = (xmpp_hostname, self.Port)
proxy_dict['credentials'] = (proxy_user, proxy_pass)
if not proxy or proxy['type'] != 'bosh':
self.socket = transports_nb.NonBlockingTCP(
on_disconnect = self.on_disconnect,
raise_event = self.raise_event,
idlequeue = self.idlequeue,
estabilish_tls = estabilish_tls,
certs = certs)
certs = certs,
proxy_dict = proxy_dict)
self.socket.PlugIn(self)

View File

@ -305,6 +305,7 @@ class XMPPDispatcher(PlugIn):
name = stanza.getName()
if name=='features':
self._owner.got_features = True
session.Stream.features=stanza
xmlns=stanza.getNamespace()
@ -390,7 +391,7 @@ class XMPPDispatcher(PlugIn):
''' Put stanza on the wire and wait for recipient's response to it. '''
if timeout is None:
timeout = DEFAULT_TIMEOUT_SECONDS
self._witid = self._owner.send(stanza)
self._witid = self.send(stanza)
if func:
self.on_responses[self._witid] = (func, args)
if timeout:

View File

@ -0,0 +1,221 @@
## proxy_connectors.py
## based on transports_nb.py
##
## Copyright (C) 2003-2004 Alexey "Snake" Nezhdanov
## modified by Dimitur Kirov <dkirov@gmail.com>
## modified by Tomas Karasek <tom.to.the.k@gmail.com>
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2, or (at your option)
## any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
import struct, socket, base64
'''
Module containing classes for proxy connecting. So far its HTTP CONNECT
and SOCKS5 proxy.
'''
import logging
log = logging.getLogger('gajim.c.x.proxy_connectors')
class ProxyConnector:
'''
Interface for proxy-connecting object - when tunnneling XMPP over proxies,
some connecting process usually has to be done before opening stream.
Proxy connectors are used right after TCP connection is estabilished.
'''
def __init__(self, send_method, onreceive, old_on_receive, on_success,
on_failure, xmpp_server, proxy_creds=(None,None)):
self.send = send_method
self.onreceive = onreceive
self.old_on_receive = old_on_receive
self.on_success = on_success
self.on_failure = on_failure
self.xmpp_server = xmpp_server
self.proxy_user, self.proxy_pass = proxy_creds
self.old_on_receive = old_on_receive
self.start_connecting()
def start_connecting(self):
raise NotImplementedException()
def connecting_over(self):
self.onreceive(self.old_on_receive)
self.on_success()
class HTTPCONNECTConnector(ProxyConnector):
def start_connecting(self):
'''
Connects to proxy, supplies login and password to it
(if were specified while creating instance). Instructs proxy to make
connection to the target server.
'''
log.info('Proxy server contacted, performing authentification')
connector = ['CONNECT %s:%s HTTP/1.1' % self.xmpp_server,
'Proxy-Connection: Keep-Alive',
'Pragma: no-cache',
'Host: %s:%s' % self.xmpp_server,
'User-Agent: Gajim']
if self.proxy_user and self.proxy_pass:
credentials = '%s:%s' % (self.proxy_user, self.proxy_pass)
credentials = base64.encodestring(credentials).strip()
connector.append('Proxy-Authorization: Basic '+credentials)
connector.append('\r\n')
self.onreceive(self._on_headers_sent)
self.send('\r\n'.join(connector))
def _on_headers_sent(self, reply):
if reply is None:
return
self.reply = reply.replace('\r', '')
try:
proto, code, desc = reply.split('\n')[0].split(' ', 2)
except:
log.error("_on_headers_sent:", exc_info=True)
#traceback.print_exc()
self.on_failure('Invalid proxy reply')
return
if code <> '200':
log.error('Invalid proxy reply: %s %s %s' % (proto, code, desc))
self.on_failure('Invalid proxy reply')
return
if len(reply) != 2:
pass
self.connecting_over()
class SOCKS5Connector(ProxyConnector):
'''
SOCKS5 proxy connection class. Allows to use SOCKS5 proxies with
(optionally) simple authentication (only USERNAME/PASSWORD auth).
'''
def start_connecting(self):
log.info('Proxy server contacted, performing authentification')
if self.proxy_user and self.proxy_pass:
to_send = '\x05\x02\x00\x02'
else:
to_send = '\x05\x01\x00'
self.onreceive(self._on_greeting_sent)
self.send(to_send)
def _on_greeting_sent(self, reply):
if reply is None:
return
if len(reply) != 2:
self.on_failure('Invalid proxy reply')
return
if reply[0] != '\x05':
log.info('Invalid proxy reply')
self.on_failure('Invalid proxy reply')
return
if reply[1] == '\x00':
return self._on_proxy_auth('\x01\x00')
elif reply[1] == '\x02':
to_send = '\x01' + chr(len(self.proxy_user)) + self.proxy_user +\
chr(len(self.proxy_pass)) + self.proxy_pass
self.onreceive(self._on_proxy_auth)
self.send(to_send)
else:
if reply[1] == '\xff':
log.error('Authentification to proxy impossible: no acceptable '
'auth method')
self.on_failure('Authentification to proxy impossible: no '
'acceptable authentification method')
return
log.error('Invalid proxy reply')
self.on_failure('Invalid proxy reply')
return
def _on_proxy_auth(self, reply):
if reply is None:
return
if len(reply) != 2:
log.error('Invalid proxy reply')
self.on_failure('Invalid proxy reply')
return
if reply[0] != '\x01':
log.error('Invalid proxy reply')
self.on_failure('Invalid proxy reply')
return
if reply[1] != '\x00':
log.error('Authentification to proxy failed')
self.on_failure('Authentification to proxy failed')
return
log.info('Authentification successfull. Jabber server contacted.')
# Request connection
req = "\x05\x01\x00"
# If the given destination address is an IP address, we'll
# use the IPv4 address request even if remote resolving was specified.
try:
self.ipaddr = socket.inet_aton(self.xmpp_server[0])
req = req + "\x01" + self.ipaddr
except socket.error:
# Well it's not an IP number, so it's probably a DNS name.
# if self.__proxy[3]==True:
# Resolve remotely
self.ipaddr = None
req = req + "\x03" + chr(len(self.xmpp_server[0])) + self.xmpp_server[0]
# else:
# # Resolve locally
# self.ipaddr = socket.inet_aton(socket.gethostbyname(self.xmpp_server[0]))
# req = req + "\x01" + ipaddr
req = req + struct.pack(">H",self.xmpp_server[1])
self.onreceive(self._on_req_sent)
self.send(req)
def _on_req_sent(self, reply):
if reply is None:
return
if len(reply) < 10:
log.error('Invalid proxy reply')
self.on_failure('Invalid proxy reply')
return
if reply[0] != '\x05':
log.error('Invalid proxy reply')
self.on_failure('Invalid proxy reply')
return
if reply[1] != "\x00":
# Connection failed
if ord(reply[1])<9:
errors = ['general SOCKS server failure',
'connection not allowed by ruleset',
'Network unreachable',
'Host unreachable',
'Connection refused',
'TTL expired',
'Command not supported',
'Address type not supported'
]
txt = errors[ord(reply[1])-1]
else:
txt = 'Invalid proxy reply'
log.error(txt)
self.on_failure(txt)
return
# Get the bound address/port
elif reply[3] == "\x01":
begin, end = 3, 7
elif reply[3] == "\x03":
begin, end = 4, 4 + reply[4]
else:
log.error('Invalid proxy reply')
self.on_failure('Invalid proxy reply')
return
self.connecting_over()

View File

@ -201,7 +201,7 @@ class StdlibSSLWrapper(SSLWrapper):
try:
return self.sslobj.read(bufsize)
except socket.sslerror, e:
log.debug("Recv: Caught socket.sslerror:", exc_info=True)
log.debug("Recv: Caught socket.sslerror: " + repr(e), exc_info=True)
if e.args[0] not in (socket.SSL_ERROR_WANT_READ, socket.SSL_ERROR_WANT_WRITE):
raise SSLWrapper.Error(self.sock or self.sslobj, e)
return None
@ -238,7 +238,6 @@ class NonBlockingTLS(PlugIn):
'''
log.info('Starting TLS estabilishing')
PlugIn.PlugIn(self, owner)
print 'inplugin'
try:
self._owner._plug_idle(writable=False, readable=False)
res = self._startSSL()
@ -273,8 +272,17 @@ class NonBlockingTLS(PlugIn):
def _startSSL(self):
''' Immediatedly switch socket to TLS mode. Used internally.'''
log.debug("_startSSL called")
if USE_PYOPENSSL: return self._startSSL_pyOpenSSL()
else: return self._startSSL_stdlib()
if USE_PYOPENSSL: result = self._startSSL_pyOpenSSL()
else: result = self._startSSL_stdlib()
if result:
log.debug("Synchronous handshake completed")
self._owner._plug_idle(writable=True, readable=False)
return True
else:
return False
def _startSSL_pyOpenSSL(self):
log.debug("_startSSL_pyOpenSSL called")
@ -328,9 +336,8 @@ class NonBlockingTLS(PlugIn):
log.error('Error while TLS handshake: ', exc_info=True)
return False
tcpsock._sslObj.setblocking(False)
log.debug("Synchronous handshake completed")
self._owner.ssl_lib = PYOPENSSL
return self._endSSL()
return True
def _startSSL_stdlib(self):
@ -349,10 +356,6 @@ class NonBlockingTLS(PlugIn):
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):

View File

@ -15,20 +15,21 @@
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
import socket,base64
from simplexml import ustr
from client import PlugIn
from idlequeue import IdleObject
from protocol import *
import proxy_connectors
import tls_nb
import socket
import sys
import os
import errno
import time
import traceback
import base64
import logging
log = logging.getLogger('gajim.c.x.transports_nb')
@ -66,7 +67,7 @@ def get_proxy_data_from_dict(proxy):
CONNECT_TIMEOUT_SECONDS = 30
# how long to wait for a disconnect to complete
DISCONNECT_TIMEOUT_SECONDS = 10
DISCONNECT_TIMEOUT_SECONDS =5
# size of the buffer which reads data from server
# if lower, more stanzas will be fragmented and processed twice
@ -82,7 +83,7 @@ DISCONNECTING = 'DISCONNECTING'
CONNECTING = 'CONNECTING'
PROXY_CONNECTING = 'PROXY_CONNECTING'
CONNECTED = 'CONNECTED'
STATES = [DISCONNECTED, DISCONNECTING, CONNECTING, PROXY_CONNECTING, CONNECTED]
STATES = [DISCONNECTED, CONNECTING, PROXY_CONNECTING, CONNECTED, DISCONNECTING]
# transports have different arguments in constructor and same in connect()
# method
@ -124,7 +125,7 @@ class NonBlockingTransport(PlugIn):
'''
self.on_connect = on_connect
self.on_connect_failure = on_connect_failure
(self.server, self.port) = conn_5tuple[4][:2]
self.server, self.port = conn_5tuple[4][:2]
self.conn_5tuple = conn_5tuple
@ -213,7 +214,8 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
'''
Non-blocking TCP socket wrapper
'''
def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls, certs):
def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls, certs,
proxy_dict=None):
'''
Class constructor.
'''
@ -224,6 +226,8 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
# bytes remained from the last send message
self.sendbuff = ''
self.proxy_dict = proxy_dict
self.on_remote_disconnect = self.disconnect()
def start_disconnect(self):
@ -289,11 +293,28 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
self._on_connect_failure('Exception while connecting to %s:%s - %s %s' %
(self.server, self.port, errnum, errstr))
def _connect_to_proxy(self):
self.set_state(PROXY_CONNECTING)
if self.proxy_dict['type'] == 'socks5':
proxyclass = proxy_connectors.SOCKS5Connector
elif self.proxy_dict['type'] == 'http' :
proxyclass = proxy_connectors.HTTPCONNECTConnector
proxyclass(
send_method = self.send,
onreceive = self.onreceive,
old_on_receive = self.on_receive,
on_success = self._on_connect,
on_failure = self._on_connect_failure,
xmpp_server = self.proxy_dict['xmpp_server'],
proxy_creds = self.proxy_dict['credentials']
)
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
'''
Preceeds invoking of on_connect callback. TCP connection is estabilished at
this time.
'''
if self.estabilish_tls:
self.tls_init(
on_succ = lambda: NonBlockingTransport._on_connect(self),
@ -320,7 +341,10 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
if self.get_state()==CONNECTING:
log.info('%s socket wrapper connected' % id(self))
self._on_connect()
self.idlequeue.remove_timeout(self.fd)
self.peerhost = self._sock.getsockname()
if self.proxy_dict: self._connect_to_proxy()
else: self._on_connect()
return
self._do_send()
@ -330,7 +354,7 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
if self.get_state()==CONNECTING:
self._on_connect_failure('Error during connect to %s:%s' %
(self.server, self.port))
else :
else:
self.disconnect()
def disconnect(self, do_callback=True):
@ -338,6 +362,7 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
return
self.set_state(DISCONNECTED)
self.idlequeue.unplug_idle(self.fd)
if self.__dict__.has_key('NonBlockingTLS'): self.NonBlockingTLS.PlugOut()
try:
self._sock.shutdown(socket.SHUT_RDWR)
self._sock.close()
@ -401,8 +426,6 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
Plugged socket will always be watched for "error" event - in that case,
pollend() is called.
'''
self.idlequeue.plug_idle(self, writable, readable)
log.info('Plugging fd %d, W:%s, R:%s' % (self.fd, writable, readable))
self.idlequeue.plug_idle(self, writable, readable)
@ -436,10 +459,6 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
def _do_receive(self):
''' Reads all pending incoming data. Calls owner's disconnected() method if appropriate.'''
# Misc error signifying that we got disconnected
ERR_DISCONN = -2
# code for unknown/other errors
ERR_OTHER = -3
received = None
errnum = 0
errstr = 'No Error Set'
@ -448,35 +467,29 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
# get as many bites, as possible, but not more than RECV_BUFSIZE
received = self._recv(RECV_BUFSIZE)
except socket.error, (errnum, errstr):
# save exception number and message to errnum, errstr
log.info("_do_receive: got %s:" % received , exc_info=True)
except tls_nb.SSLWrapper.Error, e:
log.info("_do_receive, caugth SSL error: got %s:" % received , exc_info=True)
errnum = tls_nb.gattr(e, 'errno') or ERR_OTHER
errstr = tls_nb.gattr(e, 'exc_str')
log.info("_do_receive, caught SSL error, got %s:" % received , exc_info=True)
errnum, errstr = e.exc
if received == '':
errnum = ERR_DISCONN
errstr = "Connection closed unexpectedly"
if errnum in (ERR_DISCONN, errno.ECONNRESET, errno.ENOTCONN, errno.ESHUTDOWN):
# ECONNRESET - connection you are trying to access has been reset by the peer
# 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), exc_info=True)
if hasattr(self, 'on_remote_disconnect'): self.on_remote_disconnect()
else: self.disconnect()
if (self.ssl_lib is None and received == '') or \
(self.ssl_lib == tls_nb.PYSTDLIB and errnum == 8 ) or \
(self.ssl_lib == tls_nb.PYOPENSSL and errnum == -1 ):
# 8 in stdlib: errstr == EOF occured in violation of protocol
# -1 in pyopenssl: errstr == Unexpected EOF
log.info("Disconnected by remote server: %s %s" % (errnum, errstr), exc_info=True)
self.on_remote_disconnect()
return
if errnum:
log.error("Connection to %s:%s lost: %s %s" % ( self.server, self.port, errnum, errstr), exc_info=True)
self.disconnect()
return
# this branch is for case of non-fatal SSL errors - None is returned from
# recv() but no errnum is set
if received is None:
# because there are two types of TLS wrappers, the TLS plugin recv method
# returns None in case of error
if errnum != 0:
log.error("CConnection to %s lost: %s %s" % (self.server, errnum, errstr))
self.disconnect()
return
received = ''
return
# we have received some bytes, stop the timeout!
@ -505,10 +518,10 @@ class NonBlockingHTTP(NonBlockingTCP):
'''
def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls, certs,
on_http_request_possible, on_persistent_fallback, http_dict):
on_http_request_possible, on_persistent_fallback, http_dict, proxy_dict = None):
NonBlockingTCP.__init__(self, raise_event, on_disconnect, idlequeue,
estabilish_tls, certs)
estabilish_tls, certs, proxy_dict)
self.http_protocol, self.http_host, self.http_path = urisplit(http_dict['http_uri'])
if self.http_protocol is None:
@ -518,7 +531,7 @@ class NonBlockingHTTP(NonBlockingTCP):
self.http_port = http_dict['http_port']
self.http_version = http_dict['http_version']
self.http_persistent = http_dict['http_persistent']
self.over_proxy = http_dict['over_proxy']
self.add_proxy_headers = http_dict['add_proxy_headers']
if http_dict.has_key('proxy_user') and http_dict.has_key('proxy_pass'):
self.proxy_user, self.proxy_pass = http_dict['proxy_user'], http_dict['proxy_pass']
else:
@ -528,46 +541,39 @@ class NonBlockingHTTP(NonBlockingTCP):
self.recvbuff = ''
self.expected_length = 0
self.pending_requests = 0
self.on_persistent_fallback = on_persistent_fallback
self.on_http_request_possible = on_http_request_possible
self.just_responed = False
self.last_recv_time = 0
self.close_current_connection = False
self.on_remote_disconnect = lambda: on_persistent_fallback(self)
def send(self, raw_data, now=False):
NonBlockingTCP.send(
self,
self.build_http_message(raw_data),
now)
def http_send(self, raw_data, now=False):
self.send(self.build_http_message(raw_data), now)
def on_remote_disconnect(self):
log.warn('on_remote_disconnect called, http_persistent = %s' % self.http_persistent)
if self.http_persistent:
self.http_persistent = False
self.on_persistent_fallback()
self.disconnect(do_callback=False)
self.connect(
conn_5tuple = self.conn_5tuple,
on_connect = self.on_http_request_possible,
on_connect_failure = self.disconnect)
else:
self.disconnect()
return
def _on_receive(self,data):
'''Preceeds passing received data to owner class. Gets rid of HTTP headers
and checks them.'''
if self.get_state() == PROXY_CONNECTING:
NonBlockingTCP._on_receive(self, data)
return
if not self.recvbuff:
# recvbuff empty - fresh HTTP message was received
statusline, headers, self.recvbuff = self.parse_http_message(data)
try:
statusline, headers, self.recvbuff = self.parse_http_message(data)
except ValueError:
self.disconnect()
return
if statusline[1] != '200':
log.error('HTTP Error: %s %s' % (statusline[1], statusline[2]))
self.disconnect()
return
self.expected_length = int(headers['Content-Length'])
if headers.has_key('Connection') and headers['Connection'].strip()=='close':
self.close_current_connection = True
else:
#sth in recvbuff - append currently received data to HTTP mess in buffer
#sth in recvbuff - append currently received data to HTTP msg in buffer
self.recvbuff = '%s%s' % (self.recvbuff, data)
if self.expected_length > len(self.recvbuff):
@ -583,9 +589,10 @@ class NonBlockingHTTP(NonBlockingTCP):
self.recvbuff=''
self.expected_length=0
if not self.http_persistent:
if not self.http_persistent or self.close_current_connection:
# not-persistent connections disconnect after response
self.disconnect(do_callback = False)
self.close_current_connection = False
self.last_recv_time = time.time()
self.on_receive(data=httpbody, socket=self)
self.on_http_request_possible()
@ -601,9 +608,10 @@ class NonBlockingHTTP(NonBlockingTCP):
self.http_port, self.http_path)
headers = ['%s %s %s' % (method, absolute_uri, self.http_version),
'Host: %s:%s' % (self.http_host, self.http_port),
'User-Agent: Gajim',
'Content-Type: text/xml; charset=utf-8',
'Content-Length: %s' % len(str(httpbody))]
if self.over_proxy:
if self.add_proxy_headers:
headers.append('Proxy-Connection: keep-alive')
headers.append('Pragma: no-cache')
if self.proxy_user and self.proxy_pass:
@ -646,6 +654,9 @@ class NonBlockingHTTPBOSH(NonBlockingHTTP):
self.build_cb = build_cb
def _do_send(self):
if self.state == PROXY_CONNECTING:
NonBlockingTCP._do_send(self)
return
if not self.sendbuff:
stanza = self.build_cb(socket=self)
stanza = self.build_http_message(httpbody=stanza)
@ -669,225 +680,4 @@ class NonBlockingHTTPBOSH(NonBlockingHTTP):
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, 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,
estabilish_tls, certs)
def _on_connect(self):
'''
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()
def _on_tcp_connect(self):
'''to be implemented in each proxy socket wrapper'''
pass
class NBHTTPProxySocket(NBProxySocket):
''' This class can be used instead of NonBlockingTCP
HTTP (CONNECT) proxy connection class. Allows to use HTTP proxies like squid with
(optionally) simple authentication (using login and password).
'''
def _on_tcp_connect(self):
''' Starts connection. Connects to proxy, supplies login and password to it
(if were specified while creating instance). Instructs proxy to make
connection to the target server. Returns non-empty sting on success. '''
log.info('Proxy server contacted, performing authentification')
connector = ['CONNECT %s:%s HTTP/1.0' % self.xmpp_server,
'Proxy-Connection: Keep-Alive',
'Pragma: no-cache',
'Host: %s:%s' % self.xmpp_server,
'User-Agent: Gajim']
if self.proxy_user and self.proxy_pass:
credentials = '%s:%s' % (self.proxy_user, self.proxy_pass)
credentials = base64.encodestring(credentials).strip()
connector.append('Proxy-Authorization: Basic '+credentials)
connector.append('\r\n')
self.onreceive(self._on_headers_sent)
self.send('\r\n'.join(connector))
def _on_headers_sent(self, reply):
if reply is None:
return
self.reply = reply.replace('\r', '')
try:
proto, code, desc = reply.split('\n')[0].split(' ', 2)
except:
log.error("_on_headers_sent:", exc_info=True)
#traceback.print_exc()
self._on_connect_failure('Invalid proxy reply')
return
if code <> '200':
log.error('Invalid proxy reply: %s %s %s' % (proto, code, desc))
self._on_connect_failure('Invalid proxy reply')
return
if len(reply) != 2:
pass
NonBlockingTCP._on_connect(self)
#self.onreceive(self._on_proxy_auth)
def _on_proxy_auth(self, reply):
if self.reply.find('\n\n') == -1:
if reply is None:
self._on_connect_failure('Proxy authentification failed')
return
if reply.find('\n\n') == -1:
self.reply += reply.replace('\r', '')
self._on_connect_failure('Proxy authentification failed')
return
log.info('Authentification successfull. Jabber server contacted.')
self._on_connect(self)
class NBSOCKS5ProxySocket(NBProxySocket):
'''SOCKS5 proxy connection class. Uses TCPsocket as the base class
redefines only connect method. Allows to use SOCKS5 proxies with
(optionally) simple authentication (only USERNAME/PASSWORD auth).
'''
# TODO: replace on_proxy_failure() with
# _on_connect_failure, at the end call _on_connect()
def _on_tcp_connect(self):
log.info('Proxy server contacted, performing authentification')
if self.proxy.has_key('user') and self.proxy.has_key('password'):
to_send = '\x05\x02\x00\x02'
else:
to_send = '\x05\x01\x00'
self.onreceive(self._on_greeting_sent)
self.send(to_send)
def _on_greeting_sent(self, reply):
if reply is None:
return
if len(reply) != 2:
self.on_proxy_failure('Invalid proxy reply')
return
if reply[0] != '\x05':
log.info('Invalid proxy reply')
self._owner.disconnected()
self.on_proxy_failure('Invalid proxy reply')
return
if reply[1] == '\x00':
return self._on_proxy_auth('\x01\x00')
elif reply[1] == '\x02':
to_send = '\x01' + chr(len(self.proxy['user'])) + self.proxy['user'] +\
chr(len(self.proxy['password'])) + self.proxy['password']
self.onreceive(self._on_proxy_auth)
self.send(to_send)
else:
if reply[1] == '\xff':
log.error('Authentification to proxy impossible: no acceptable '
'auth method')
self._owner.disconnected()
self.on_proxy_failure('Authentification to proxy impossible: no '
'acceptable authentification method')
return
log.error('Invalid proxy reply')
self._owner.disconnected()
self.on_proxy_failure('Invalid proxy reply')
return
def _on_proxy_auth(self, reply):
if reply is None:
return
if len(reply) != 2:
log.error('Invalid proxy reply')
self._owner.disconnected()
self.on_proxy_failure('Invalid proxy reply')
return
if reply[0] != '\x01':
log.error('Invalid proxy reply')
self._owner.disconnected()
self.on_proxy_failure('Invalid proxy reply')
return
if reply[1] != '\x00':
log.error('Authentification to proxy failed')
self._owner.disconnected()
self.on_proxy_failure('Authentification to proxy failed')
return
log.info('Authentification successfull. Jabber server contacted.')
# Request connection
req = "\x05\x01\x00"
# If the given destination address is an IP address, we'll
# use the IPv4 address request even if remote resolving was specified.
try:
self.ipaddr = socket.inet_aton(self.server[0])
req = req + "\x01" + self.ipaddr
except socket.error:
# Well it's not an IP number, so it's probably a DNS name.
# if self.__proxy[3]==True:
# Resolve remotely
self.ipaddr = None
req = req + "\x03" + chr(len(self.server[0])) + self.server[0]
# else:
# # Resolve locally
# self.ipaddr = socket.inet_aton(socket.gethostbyname(self.server[0]))
# req = req + "\x01" + ipaddr
req = req + struct.pack(">H",self.server[1])
self.onreceive(self._on_req_sent)
self.send(req)
def _on_req_sent(self, reply):
if reply is None:
return
if len(reply) < 10:
log.error('Invalid proxy reply')
self._owner.disconnected()
self.on_proxy_failure('Invalid proxy reply')
return
if reply[0] != '\x05':
log.error('Invalid proxy reply')
self._owner.disconnected()
self.on_proxy_failure('Invalid proxy reply')
return
if reply[1] != "\x00":
# Connection failed
self._owner.disconnected()
if ord(reply[1])<9:
errors = ['general SOCKS server failure',
'connection not allowed by ruleset',
'Network unreachable',
'Host unreachable',
'Connection refused',
'TTL expired',
'Command not supported',
'Address type not supported'
]
txt = errors[ord(reply[1])-1]
else:
txt = 'Invalid proxy reply'
log.error(txt)
self.on_proxy_failure(txt)
return
# Get the bound address/port
elif reply[3] == "\x01":
begin, end = 3, 7
elif reply[3] == "\x03":
begin, end = 4, 4 + reply[4]
else:
log.error('Invalid proxy reply')
self._owner.disconnected()
self.on_proxy_failure('Invalid proxy reply')
return
if self.on_connect_proxy:
self.on_connect_proxy()