- 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:
parent
4504861084
commit
a76c173816
|
@ -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))
|
||||
|
|
|
@ -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 *
|
||||
|
|
|
@ -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.'''
|
||||
|
|
|
@ -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,28 +119,29 @@ 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):
|
||||
for s in self.http_socks:
|
||||
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
@ -288,12 +292,29 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
|
|||
# which will also remove read_timeouts for descriptor
|
||||
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 (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 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 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()
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue