From 9a6b090506989e462254f0e619d8eec3d4b6973f Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Wed, 24 Jan 2007 21:50:59 +0000 Subject: [PATCH] begining of socks5 proxy support. error handling is missing. see #799 --- data/glade/manage_proxies_window.glade | 4 +- src/common/connection.py | 1 + src/common/xmpp/client_nb.py | 17 ++- src/common/xmpp/transports_nb.py | 152 +++++++++++++++++++++++++ src/config.py | 13 ++- 5 files changed, 179 insertions(+), 8 deletions(-) diff --git a/data/glade/manage_proxies_window.glade b/data/glade/manage_proxies_window.glade index db0055188..d4f612280 100644 --- a/data/glade/manage_proxies_window.glade +++ b/data/glade/manage_proxies_window.glade @@ -17,6 +17,7 @@ GDK_WINDOW_TYPE_HINT_NORMAL GDK_GRAVITY_NORTH_WEST True + False @@ -209,7 +210,8 @@ True - HTTP Connect + HTTP Connect +SOCKS5 False True diff --git a/src/common/connection.py b/src/common/connection.py index 3e52aec5e..15bb5a428 100644 --- a/src/common/connection.py +++ b/src/common/connection.py @@ -326,6 +326,7 @@ class Connection(ConnectionHandlers): proxy['port'] = gajim.config.get_per('proxies', p, 'port') proxy['user'] = gajim.config.get_per('proxies', p, 'user') proxy['password'] = gajim.config.get_per('proxies', p, 'pass') + proxy['type'] = gajim.config.get_per('proxies', p, 'type') else: proxy = None diff --git a/src/common/xmpp/client_nb.py b/src/common/xmpp/client_nb.py index 314f0a232..f946bcfc0 100644 --- a/src/common/xmpp/client_nb.py +++ b/src/common/xmpp/client_nb.py @@ -88,6 +88,8 @@ class NBCommonClient(CommonClient): 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() @@ -102,9 +104,18 @@ class NBCommonClient(CommonClient): server = (self.Server, self.Port) self._Server, self._Proxy, self._Ssl = server , proxy, ssl self.on_stream_start = on_stream_start - if proxy: - self.socket = transports_nb.NBHTTPPROXYsocket(self._on_connected, - self._on_connected_failure, proxy, server) + if proxy: + if proxy.has_key('type'): + type_ = proxy['type'] + if type_ == 'socks5': + self.socket = transports_nb.NBSOCKS5PROXYsocket(self._on_connected, + self._on_connected_failure, proxy, server) + elif type_ == 'http': + self.socket = transports_nb.NBHTTPPROXYsocket(self._on_connected, + self._on_connected_failure, proxy, server) + else: + self.socket = transports_nb.NBHTTPPROXYsocket(self._on_connected, + self._on_connected_failure, proxy, server) else: self.connected = 'tcp' self.socket = transports_nb.NonBlockingTcp(self._on_connected, diff --git a/src/common/xmpp/transports_nb.py b/src/common/xmpp/transports_nb.py index be55dfc89..4f90e21b8 100644 --- a/src/common/xmpp/transports_nb.py +++ b/src/common/xmpp/transports_nb.py @@ -15,6 +15,7 @@ ## GNU General Public License for more details. import socket,select,base64,dispatcher_nb +import struct from simplexml import ustr from client import PlugIn from idlequeue import IdleObject @@ -885,6 +886,8 @@ class NBHTTPPROXYsocket(NonBlockingTcp): self.DEBUG('Invalid proxy reply: %s %s %s' % (proto, code, desc),'error') self._owner.disconnected() return + if len(reply) != 2: + pass self.onreceive(self._on_proxy_auth) def _on_proxy_auth(self, reply): @@ -901,3 +904,152 @@ class NBHTTPPROXYsocket(NonBlockingTcp): def DEBUG(self, text, severity): ''' Overwrites DEBUG tag to allow debug output be presented as "CONNECTproxy".''' return self._owner.DEBUG(DBG_CONNECT_PROXY, text, severity) + +class NBSOCKS5PROXYsocket(NonBlockingTcp): + '''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). + ''' + def __init__(self, on_connect = None, on_connect_failure = None, + proxy = None, server = None, use_srv = True): + ''' Caches proxy and target addresses. + 'proxy' argument is a dictionary with mandatory keys 'host' and 'port' + (proxy address) and optional keys 'user' and 'password' to use for + authentication. 'server' argument is a tuple of host and port - + just like TCPsocket uses. ''' + self.on_connect_proxy = on_connect + self.on_connect_failure = on_connect_failure + NonBlockingTcp.__init__(self, self._on_tcp_connect, on_connect_failure, + server, use_srv) + self.DBG_LINE=DBG_CONNECT_PROXY + self.server = server + self.proxy = proxy + self.ipaddr = None + + def plugin(self, owner): + ''' Starts connection. Used interally. Returns non-empty string on + success.''' + owner.debug_flags.append(DBG_CONNECT_PROXY) + NonBlockingTcp.plugin(self, owner) + + def connect(self, dupe = None): + ''' 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. + ''' + NonBlockingTcp.connect(self, (self.proxy['host'], self.proxy['port'])) + + def _on_tcp_connect(self): + self.DEBUG('Proxy server contacted, performing authentification', 'start') + 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: + raise error('Invalid proxy reply') + if reply[0] != '\x05': + self.DEBUG('Invalid proxy reply', 'error') + self._owner.disconnected() + return + if reply[1] == '\x00': + return self._on_proxy_auth('\x01\x00') + elif reply[1] == '\x02': + # TODO: Do authentification + self.onreceive(self._on_proxy_auth) + else: + if reply[1] == '\xff': + self.DEBUG('Authentification to proxy impossible: no acceptable ' + 'auth method', 'error') + else: + self.DEBUG('Invalid proxy reply', 'error') + self._owner.disconnected() + return + + def _on_proxy_auth(self, reply): + if reply is None: + return + if len(reply) != 2: + self.DEBUG('Invalid proxy reply', 'error') + self._owner.disconnected() + raise error('Invalid proxy reply') + if reply[0] != '\x01': + self.DEBUG('Invalid proxy reply', 'error') + self._owner.disconnected() + raise error('Invalid proxy reply') + if reply[1] != '\x00': + self.DEBUG('Authentification to proxy failed', 'error') + self._owner.disconnected() + return + self.DEBUG('Authentification successfull. Jabber server contacted.','ok') + # 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" + 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: + self.DEBUG('Invalid proxy reply', 'error') + self._owner.disconnected() + raise error('Invalid proxy reply') + if reply[0] != '\x05': + self.DEBUG('Invalid proxy reply', 'error') + self._owner.disconnected() + raise error('Invalid proxy reply') + 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' + self.DEBUG(txt, 'error') + 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: + self.DEBUG('Invalid proxy reply', 'error') + self._owner.disconnected() + return + + if self.on_connect_proxy: + self.on_connect_proxy() + + def DEBUG(self, text, severity): + ''' Overwrites DEBUG tag to allow debug output be presented as "CONNECTproxy".''' + return self._owner.DEBUG(DBG_CONNECT_PROXY, text, severity) diff --git a/src/config.py b/src/config.py index 62f6e023a..29955915e 100644 --- a/src/config.py +++ b/src/config.py @@ -1655,6 +1655,7 @@ class ManageProxiesWindow: self.window.set_transient_for(gajim.interface.roster.window) self.proxies_treeview = self.xml.get_widget('proxies_treeview') self.proxyname_entry = self.xml.get_widget('proxyname_entry') + self.proxytype_combobox = self.xml.get_widget('proxytype_combobox') self.init_list() self.xml.signal_autoconnect(self) self.window.show_all() @@ -1670,7 +1671,7 @@ class ManageProxiesWindow: def init_list(self): self.xml.get_widget('remove_proxy_button').set_sensitive(False) - self.xml.get_widget('proxytype_combobox').set_sensitive(False) + self.proxytype_combobox.set_sensitive(False) self.xml.get_widget('proxy_table').set_sensitive(False) model = gtk.ListStore(str) self.proxies_treeview.set_model(model) @@ -1755,7 +1756,9 @@ class ManageProxiesWindow: 'user')) proxypass_entry.set_text(gajim.config.get_per('proxies', proxy, 'pass')) - #FIXME: if we have several proxy types, set the combobox + proxytype = gajim.config.get_per('proxies', proxy, 'type') + types = ['http', 'socks5'] + self.proxytype_combobox.set_active(types.index(proxytype)) if gajim.config.get_per('proxies', proxy, 'user'): useauth_checkbutton.set_active(True) @@ -1782,8 +1785,10 @@ class ManageProxiesWindow: model.set_value(iter, 0, new_name) def on_proxytype_combobox_changed(self, widget): - #FIXME: if we have several proxy types take them into account - pass + types = ['http', 'socks5'] + type_ = self.proxytype_combobox.get_active() + proxy = self.proxyname_entry.get_text().decode('utf-8') + gajim.config.set_per('proxies', proxy, 'type', types[type_]) def on_proxyhost_entry_changed(self, widget): value = widget.get_text().decode('utf-8')