From a14979375b164aa44071dae30521ec15363f3b7a Mon Sep 17 00:00:00 2001 From: Sandro Munda Date: Sun, 3 Jun 2012 19:56:56 +0200 Subject: Added a partial support of the XEP 0065 - Socks5 Bytestreams --- sleekxmpp/plugins/xep_0065/__init__.py | 8 + sleekxmpp/plugins/xep_0065/proxy.py | 300 +++++++++++++++++++++++++++++++++ sleekxmpp/plugins/xep_0065/stanza.py | 38 +++++ 3 files changed, 346 insertions(+) create mode 100644 sleekxmpp/plugins/xep_0065/__init__.py create mode 100644 sleekxmpp/plugins/xep_0065/proxy.py create mode 100644 sleekxmpp/plugins/xep_0065/stanza.py (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/xep_0065/__init__.py b/sleekxmpp/plugins/xep_0065/__init__.py new file mode 100644 index 00000000..8f5dfa6b --- /dev/null +++ b/sleekxmpp/plugins/xep_0065/__init__.py @@ -0,0 +1,8 @@ +""" +""" + +from sleekxmpp.plugins.base import register_plugin +from sleekxmpp.plugins.xep_0065.proxy import xep_0065 + + +register_plugin(xep_0065) diff --git a/sleekxmpp/plugins/xep_0065/proxy.py b/sleekxmpp/plugins/xep_0065/proxy.py new file mode 100644 index 00000000..f33f7840 --- /dev/null +++ b/sleekxmpp/plugins/xep_0065/proxy.py @@ -0,0 +1,300 @@ +import sys +import logging +import struct + +from threading import Thread, Event +from hashlib import sha1 +from select import select +from uuid import uuid4 + +from sleekxmpp.plugins.base import base_plugin +from sleekxmpp import Iq +from sleekxmpp.xmlstream import register_stanza_plugin +from sleekxmpp.xmlstream.handler import Callback +from sleekxmpp.xmlstream.matcher import StanzaPath + +from socks import socksocket, PROXY_TYPE_SOCKS5 + +from stanza import Query, StreamHost, StreamHostUsed + +# Register the sleekxmpp logger +log = logging.getLogger(__name__) + +# Register xep_0065 stanzas +register_stanza_plugin(Iq, Query) +register_stanza_plugin(Query, StreamHost) +register_stanza_plugin(Query, StreamHostUsed) + + +class xep_0065(base_plugin): + """ + XEP-0065 In-Band Bytestreams + """ + + description = "In-Band Bytestreams" + dependencies = set(['xep_0030', ]) + xep = '0065' + + def plugin_init(self): + """ Initializes the xep_0065 plugin and all event callbacks. + """ + + # Shortcuts to access to the xep_0030 plugin. + self.disco = self.xmpp['xep_0030'] + + # Handler for the streamhost stanza. + self.xmpp.registerHandler( + Callback('In-Band Bytestreams', + StanzaPath('iq@type=set/q/streamhost'), + self._handle_streamhost)) + + # Handler for the streamhost-used stanza. + self.xmpp.registerHandler( + Callback('In-Band Bytestreams', + StanzaPath('iq@type=result/q/streamhost-used'), + self._handle_streamhost_used)) + + def handshake(self, to, streamer=None): + """ Starts the handshake to establish the socks5 bytestreams + connection. + """ + + # Discovers the proxy. + self.streamer = streamer or self.discover_proxy() + + # Requester requests network address from the proxy. + streamhost = self.get_network_address(self.streamer) + self.proxy_host = streamhost['q']['streamhost']['host'] + self.proxy_port = streamhost['q']['streamhost']['port'] + + # Generates the SID for this new handshake. + sid = uuid4().hex + + # Requester initiates S5B negotation with Target by sending + # IQ-set that includes the JabberID and network address of + # StreamHost as well as the StreamID (SID) of the proposed + # bytestream. + iq = self.xmpp.Iq(sto=to, stype='set') + iq['q']['sid'] = sid + iq['q']['streamhost']['jid'] = self.streamer + iq['q']['streamhost']['host'] = self.proxy_host + iq['q']['streamhost']['port'] = self.proxy_port + + # Sends the new IQ. + return iq.send() + + def discover_proxy(self): + """ Auto-discovers (using XEP 0030) the available bytestream + proxy on the XMPP server. + + Returns the JID of the proxy. + """ + + # Gets all disco items. + disco_items = self.disco.get_items(self.xmpp.server) + + for item in disco_items['disco_items']['items']: + # For each items, gets the disco info. + disco_info = self.disco.get_info(item[0]) + + # Gets and verifies if the identity is a bytestream proxy. + identities = disco_info['disco_info']['identities'] + for identity in identities: + if identity[0] == 'proxy' and identity[1] == 'bytestreams': + # Returns when the first occurence is found. + return '%s' % disco_info['from'] + + def get_network_address(self, streamer): + iq = self.xmpp.Iq(sto=streamer, stype='get') + iq['q'] + + return iq.send() + + def _handle_streamhost(self, iq): + """ Handles all streamhost stanzas. + """ + + # Registers the streamhost info. + self.streamer = iq['q']['streamhost']['jid'] + self.proxy_host = iq['q']['streamhost']['host'] + self.proxy_port = iq['q']['streamhost']['port'] + + # Sets the SID, the requester and the target. + sid = iq['q']['sid'] + requester = '%s' % iq['from'] + target = '%s' % self.xmpp.boundjid + + # Next the Target attempts to open a standard TCP socket on + # the network address of the Proxy. + self.target_thread = Proxy(sid, requester, target, self.proxy_host, + self.proxy_port, self._handle_on_recv) + self.target_thread.start() + + # Wait until the proxy is connected + self.target_thread.connected.wait() + + # Replies to the incoming iq with a streamhost-used stanza. + res_iq = iq.reply() + res_iq['q']['sid'] = sid + res_iq['q']['streamhost-used']['jid'] = self.streamer + + # Sends the IQ + return res_iq.send() + + def _handle_streamhost_used(self, iq): + """ Handles all streamhost-used stanzas. + """ + + # Sets the requester and the target. + requester = '%s' % self.xmpp.boundjid + target = '%s' % iq['from'] + + # The Requester will establish a connection to the SOCKS5 + # proxy in the same way the Target did. + self.requester_thread = Proxy(iq['q']['sid'], requester, target, + self.proxy_host, self.proxy_port, + self._handle_on_recv) + self.requester_thread.start() + + # Wait until the proxy is connected + self.requester_thread.connected.wait() + + # Requester sends IQ-set to StreamHost requesting that + # StreamHost activate the bytestream associated with the + # StreamID. + self.activate(iq['q']['sid'], target) + + def activate(self, sid, to): + """ IQ-set to StreamHost requesting that StreamHost activate + the bytestream associated with the StreamID. + """ + + # Creates the activate IQ. + act_iq = self.xmpp.Iq(sto=self.streamer, stype='set') + act_iq['q']['sid'] = sid + act_iq['q']['activate'] = to + + # Send the IQ. + act_iq.send() + + def send(self, msg): + """ Sends the msg to the socket. + + msg : The message data. + """ + + if hasattr(self, 'requester_thread'): + self.requester_thread.send(msg) + elif hasattr(self, 'target_thread'): + self.target_thread.send(msg) + + def _handle_on_recv(self, data): + """ A default callback when socket are receiving data. + """ + + log.debug('Received: %s' % data) + + +class Proxy(Thread): + + def __init__(self, sid, requester, target, proxy, proxy_port, + on_recv): + """ Initializes the proxy thread. + + sid : The StreamID. + requester : The JID of the requester. + target : The JID of the target. + proxy_host : The hostname or the IP of the proxy. + proxy_port : The port of the proxy. or + on_recv : A callback called when data are received from the + socket. + """ + + # Initializes the thread. + Thread.__init__(self) + + # Because the xep_0065 plugin uses the proxy_port as string, + # the Proxy class accepts the proxy_port argument as a string + # or an integer. Here, we force to use the port as an integer. + proxy_port = int(proxy_port) + + # Creates a connected event to warn when to proxy is + # connected. + self.connected = Event() + + # Registers the arguments. + self.sid = sid + self.requester = requester + self.target = target + self.proxy = proxy + self.proxy_port = proxy_port + self.on_recv = on_recv + + def run(self): + """ Starts the thread. + """ + + # Creates the socks5 proxy socket + self.s = socksocket() + self.s.setproxy(PROXY_TYPE_SOCKS5, self.proxy, port=self.proxy_port) + + # The hostname MUST be SHA1(SID + Requester JID + Target JID) + # where the output is hexadecimal-encoded (not binary). + digest = sha1() + digest.update(self.sid) # SID + digest.update(self.requester) # Requester JID + digest.update(self.target) # Target JID + + # Computes the digest in hex. + dest = '%s' % digest.hexdigest() + + # The port MUST be 0. + self.s.connect((dest, 0)) + log.info('Connected') + self.connected.set() + + # Listen for data on the socket + self.listen() + + def listen(self): + """ Listen for data on the socket. When receiving data, call + the callback on_recv callable. + """ + + while True: + ins, out, err = select([self.s, ], [], []) + + for s in ins: + data = self.recv_size(self.s) + self.on_recv(data) + + def recv_size(self, the_socket): + #data length is packed into 4 bytes + total_len = 0 + total_data = [] + size = sys.maxint + size_data = sock_data = '' + recv_size = 8192 + + while total_len < size: + sock_data = the_socket.recv(recv_size) + if not total_data: + if len(sock_data) > 4: + size_data += sock_data + size = struct.unpack('>i', size_data[:4])[0] + recv_size = size + if recv_size > 524288: + recv_size = 524288 + total_data.append(size_data[4:]) + else: + size_data += sock_data + else: + total_data.append(sock_data) + total_len = sum([len(i) for i in total_data]) + return ''.join(total_data) + + def send(self, msg): + """ Sends the data over the socket. + """ + + self.s.sendall(msg) diff --git a/sleekxmpp/plugins/xep_0065/stanza.py b/sleekxmpp/plugins/xep_0065/stanza.py new file mode 100644 index 00000000..4d33be98 --- /dev/null +++ b/sleekxmpp/plugins/xep_0065/stanza.py @@ -0,0 +1,38 @@ +from sleekxmpp.xmlstream import ElementBase + +# The protocol namespace defined in the Socks5Bytestream (0065) spec. +namespace = 'http://jabber.org/protocol/bytestreams' + + +class StreamHost(ElementBase): + """ The streamhost xml element. + """ + + namespace = namespace + name = 'streamhost' + plugin_attrib = 'streamhost' + interfaces = set(('host', 'jid', 'port')) + + +class StreamHostUsed(ElementBase): + """ The streamhost-used xml element. + """ + + namespace = 'http://jabber.org/protocol/bytestreams' + name = 'streamhost-used' + plugin_attrib = 'streamhost-used' + interfaces = set(('jid',)) + + +class Query(ElementBase): + """ The query xml element. + """ + + namespace = 'http://jabber.org/protocol/bytestreams' + name = 'query' + plugin_attrib = 'q' + interfaces = set(('sid', 'activate')) + sub_interfaces = set(('activate',)) + + + -- cgit v1.2.3 From 69cffce7dcaea56fa35f9c39299e98eb5576f20d Mon Sep 17 00:00:00 2001 From: Sandro Munda Date: Mon, 4 Jun 2012 07:57:14 +0200 Subject: Used the namespace in all stanzas --- sleekxmpp/plugins/xep_0065/stanza.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/xep_0065/stanza.py b/sleekxmpp/plugins/xep_0065/stanza.py index 4d33be98..ef70a368 100644 --- a/sleekxmpp/plugins/xep_0065/stanza.py +++ b/sleekxmpp/plugins/xep_0065/stanza.py @@ -18,7 +18,7 @@ class StreamHostUsed(ElementBase): """ The streamhost-used xml element. """ - namespace = 'http://jabber.org/protocol/bytestreams' + namespace = namespace name = 'streamhost-used' plugin_attrib = 'streamhost-used' interfaces = set(('jid',)) @@ -28,7 +28,7 @@ class Query(ElementBase): """ The query xml element. """ - namespace = 'http://jabber.org/protocol/bytestreams' + namespace = namespace name = 'query' plugin_attrib = 'q' interfaces = set(('sid', 'activate')) -- cgit v1.2.3 From cf24b870b124be3f41c496aa2a99b4c6f626e22f Mon Sep 17 00:00:00 2001 From: Sandro Munda Date: Mon, 4 Jun 2012 08:03:08 +0200 Subject: Registered stanza plugin in the stanza module --- sleekxmpp/plugins/xep_0065/__init__.py | 3 --- sleekxmpp/plugins/xep_0065/proxy.py | 12 ++---------- sleekxmpp/plugins/xep_0065/stanza.py | 9 ++++++--- 3 files changed, 8 insertions(+), 16 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/xep_0065/__init__.py b/sleekxmpp/plugins/xep_0065/__init__.py index 8f5dfa6b..e1066005 100644 --- a/sleekxmpp/plugins/xep_0065/__init__.py +++ b/sleekxmpp/plugins/xep_0065/__init__.py @@ -1,6 +1,3 @@ -""" -""" - from sleekxmpp.plugins.base import register_plugin from sleekxmpp.plugins.xep_0065.proxy import xep_0065 diff --git a/sleekxmpp/plugins/xep_0065/proxy.py b/sleekxmpp/plugins/xep_0065/proxy.py index f33f7840..d9d629f6 100644 --- a/sleekxmpp/plugins/xep_0065/proxy.py +++ b/sleekxmpp/plugins/xep_0065/proxy.py @@ -8,23 +8,15 @@ from select import select from uuid import uuid4 from sleekxmpp.plugins.base import base_plugin -from sleekxmpp import Iq -from sleekxmpp.xmlstream import register_stanza_plugin from sleekxmpp.xmlstream.handler import Callback from sleekxmpp.xmlstream.matcher import StanzaPath - from socks import socksocket, PROXY_TYPE_SOCKS5 -from stanza import Query, StreamHost, StreamHostUsed +import stanza -# Register the sleekxmpp logger +# Registers the sleekxmpp logger log = logging.getLogger(__name__) -# Register xep_0065 stanzas -register_stanza_plugin(Iq, Query) -register_stanza_plugin(Query, StreamHost) -register_stanza_plugin(Query, StreamHostUsed) - class xep_0065(base_plugin): """ diff --git a/sleekxmpp/plugins/xep_0065/stanza.py b/sleekxmpp/plugins/xep_0065/stanza.py index ef70a368..0990a509 100644 --- a/sleekxmpp/plugins/xep_0065/stanza.py +++ b/sleekxmpp/plugins/xep_0065/stanza.py @@ -1,4 +1,6 @@ -from sleekxmpp.xmlstream import ElementBase +from sleekxmpp import Iq +from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin + # The protocol namespace defined in the Socks5Bytestream (0065) spec. namespace = 'http://jabber.org/protocol/bytestreams' @@ -34,5 +36,6 @@ class Query(ElementBase): interfaces = set(('sid', 'activate')) sub_interfaces = set(('activate',)) - - +register_stanza_plugin(Iq, Query) +register_stanza_plugin(Query, StreamHost) +register_stanza_plugin(Query, StreamHostUsed) -- cgit v1.2.3 From b52d2768b0ff63df3b3006e419477cb9e462c1f9 Mon Sep 17 00:00:00 2001 From: Sandro Munda Date: Mon, 4 Jun 2012 08:07:29 +0200 Subject: Added some comments to the get_network_address method --- sleekxmpp/plugins/xep_0065/proxy.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/xep_0065/proxy.py b/sleekxmpp/plugins/xep_0065/proxy.py index d9d629f6..7d983840 100644 --- a/sleekxmpp/plugins/xep_0065/proxy.py +++ b/sleekxmpp/plugins/xep_0065/proxy.py @@ -97,8 +97,13 @@ class xep_0065(base_plugin): return '%s' % disco_info['from'] def get_network_address(self, streamer): + """ Gets the streamhost information of the proxy. + + streamer : The jid of the proxy. + """ + iq = self.xmpp.Iq(sto=streamer, stype='get') - iq['q'] + iq['q'] # Adds the query eleme to the iq. return iq.send() -- cgit v1.2.3 From 44ee0633f2e55c112ea1f4c48bc39867a2223f55 Mon Sep 17 00:00:00 2001 From: Sandro Munda Date: Mon, 4 Jun 2012 08:27:41 +0200 Subject: Renamed the _handle_on_recv to the on_recv method. Renamed requester_thread and target_thread to proxy. The send method is now simpler. --- sleekxmpp/plugins/xep_0065/proxy.py | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/xep_0065/proxy.py b/sleekxmpp/plugins/xep_0065/proxy.py index 7d983840..eb0b9e8c 100644 --- a/sleekxmpp/plugins/xep_0065/proxy.py +++ b/sleekxmpp/plugins/xep_0065/proxy.py @@ -123,12 +123,12 @@ class xep_0065(base_plugin): # Next the Target attempts to open a standard TCP socket on # the network address of the Proxy. - self.target_thread = Proxy(sid, requester, target, self.proxy_host, - self.proxy_port, self._handle_on_recv) - self.target_thread.start() + self.proxy_thread = Proxy(sid, requester, target, self.proxy_host, + self.proxy_port, self.on_recv) + self.proxy_thread.start() # Wait until the proxy is connected - self.target_thread.connected.wait() + self.proxy_thread.connected.wait() # Replies to the incoming iq with a streamhost-used stanza. res_iq = iq.reply() @@ -148,13 +148,13 @@ class xep_0065(base_plugin): # The Requester will establish a connection to the SOCKS5 # proxy in the same way the Target did. - self.requester_thread = Proxy(iq['q']['sid'], requester, target, + self.proxy_thread = Proxy(iq['q']['sid'], requester, target, self.proxy_host, self.proxy_port, - self._handle_on_recv) - self.requester_thread.start() + self.on_recv) + self.proxy_thread.start() # Wait until the proxy is connected - self.requester_thread.connected.wait() + self.proxy_thread.connected.wait() # Requester sends IQ-set to StreamHost requesting that # StreamHost activate the bytestream associated with the @@ -180,19 +180,21 @@ class xep_0065(base_plugin): msg : The message data. """ - if hasattr(self, 'requester_thread'): - self.requester_thread.send(msg) - elif hasattr(self, 'target_thread'): - self.target_thread.send(msg) + self.proxy_thread.send(msg) - def _handle_on_recv(self, data): - """ A default callback when socket are receiving data. + def on_recv(self, data): + """ A default behavior when socket are receiving data. + + This method should be overriden. """ - log.debug('Received: %s' % data) + log.debug('Received data: %s' % data) class Proxy(Thread): + """ Establishes in a thread a connection between the client and + the server-side Socks5 proxy. + """ def __init__(self, sid, requester, target, proxy, proxy_port, on_recv): @@ -210,6 +212,9 @@ class Proxy(Thread): # Initializes the thread. Thread.__init__(self) + # This thread is a daemon thread. + self.daemon = True + # Because the xep_0065 plugin uses the proxy_port as string, # the Proxy class accepts the proxy_port argument as a string # or an integer. Here, we force to use the port as an integer. @@ -266,7 +271,7 @@ class Proxy(Thread): self.on_recv(data) def recv_size(self, the_socket): - #data length is packed into 4 bytes + # Data length is packed into 4 bytes. total_len = 0 total_data = [] size = sys.maxint -- cgit v1.2.3 From 39505ae1ffbecb3e6edc16b532723f33c8c723f7 Mon Sep 17 00:00:00 2001 From: Sandro Munda Date: Mon, 4 Jun 2012 19:39:48 +0200 Subject: The xep_0065 plugin supports now multiple stream (multiple connected sockets). To send data over a stream, we need to pass the SID in order to retrieve the good proxy thread (and so, the good socket). --- sleekxmpp/plugins/xep_0065/proxy.py | 66 +++++++++++++++++++++++++++---------- 1 file changed, 48 insertions(+), 18 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/xep_0065/proxy.py b/sleekxmpp/plugins/xep_0065/proxy.py index eb0b9e8c..bcb43cfa 100644 --- a/sleekxmpp/plugins/xep_0065/proxy.py +++ b/sleekxmpp/plugins/xep_0065/proxy.py @@ -27,6 +27,10 @@ class xep_0065(base_plugin): dependencies = set(['xep_0030', ]) xep = '0065' + # A dict contains for each SID, the proxy thread currently + # running. + proxy_threads = {} + def plugin_init(self): """ Initializes the xep_0065 plugin and all event callbacks. """ @@ -127,6 +131,9 @@ class xep_0065(base_plugin): self.proxy_port, self.on_recv) self.proxy_thread.start() + # Registers the new thread in the proxy_thread dict. + self.proxy_threads[sid] = self.proxy_thread + # Wait until the proxy is connected self.proxy_thread.connected.wait() @@ -142,17 +149,20 @@ class xep_0065(base_plugin): """ Handles all streamhost-used stanzas. """ - # Sets the requester and the target. + # Sets the SID, the requester and the target. + sid = iq['q']['sid'] requester = '%s' % self.xmpp.boundjid target = '%s' % iq['from'] # The Requester will establish a connection to the SOCKS5 # proxy in the same way the Target did. - self.proxy_thread = Proxy(iq['q']['sid'], requester, target, - self.proxy_host, self.proxy_port, - self.on_recv) + self.proxy_thread = Proxy(sid, requester, target, self.proxy_host, + self.proxy_port, self.on_recv) self.proxy_thread.start() + # Registers the new thread in the proxy_thread dict. + self.proxy_threads[sid] = self.proxy_thread + # Wait until the proxy is connected self.proxy_thread.connected.wait() @@ -174,21 +184,34 @@ class xep_0065(base_plugin): # Send the IQ. act_iq.send() - def send(self, msg): + def send(self, sid, msg): """ Sends the msg to the socket. + sid : The SID to retrieve the good proxy stored in the + proxy_threads dict msg : The message data. """ - self.proxy_thread.send(msg) - - def on_recv(self, data): - """ A default behavior when socket are receiving data. + proxy = self.proxy_threads.get(sid) + if proxy: + proxy.send(msg) + else: + # TODO: raise an exception. + pass - This method should be overriden. + def on_recv(self, sid, data): + """ Called when receiving data """ - log.debug('Received data: %s' % data) + if not data: + try: + del self.proxy_threads[sid] + except KeyError: + # TODO: internal error, raise an exception ? + pass + else: + log.debug('Received data: %s' % data) + self.xmpp.event('socks_recv', data) class Proxy(Thread): @@ -212,9 +235,6 @@ class Proxy(Thread): # Initializes the thread. Thread.__init__(self) - # This thread is a daemon thread. - self.daemon = True - # Because the xep_0065 plugin uses the proxy_port as string, # the Proxy class accepts the proxy_port argument as a string # or an integer. Here, we force to use the port as an integer. @@ -252,26 +272,33 @@ class Proxy(Thread): # The port MUST be 0. self.s.connect((dest, 0)) - log.info('Connected') + log.info('Socket connected.') self.connected.set() # Listen for data on the socket self.listen() + # Listen returns when the socket must be closed. + self.s.close() + log.info('Socket closed.') + def listen(self): """ Listen for data on the socket. When receiving data, call the callback on_recv callable. """ - while True: + socket_open = True + while socket_open: ins, out, err = select([self.s, ], [], []) for s in ins: data = self.recv_size(self.s) - self.on_recv(data) + if not data: + socket_open = False + + self.on_recv(self.sid, data) def recv_size(self, the_socket): - # Data length is packed into 4 bytes. total_len = 0 total_data = [] size = sys.maxint @@ -280,6 +307,9 @@ class Proxy(Thread): while total_len < size: sock_data = the_socket.recv(recv_size) + if not sock_data: + return ''.join(total_data) + if not total_data: if len(sock_data) > 4: size_data += sock_data -- cgit v1.2.3 From 2f388576817b80acf07443fed2369e7d110fe1e8 Mon Sep 17 00:00:00 2001 From: Sandro Munda Date: Tue, 5 Jun 2012 08:33:21 +0200 Subject: Changed the description of the xep_0065 plugin --- sleekxmpp/plugins/xep_0065/proxy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/xep_0065/proxy.py b/sleekxmpp/plugins/xep_0065/proxy.py index bcb43cfa..865c2f90 100644 --- a/sleekxmpp/plugins/xep_0065/proxy.py +++ b/sleekxmpp/plugins/xep_0065/proxy.py @@ -23,7 +23,7 @@ class xep_0065(base_plugin): XEP-0065 In-Band Bytestreams """ - description = "In-Band Bytestreams" + description = "Socks5 Bytestreams" dependencies = set(['xep_0030', ]) xep = '0065' -- cgit v1.2.3 From 2cd936318ddf7a0b3e096f7070ec83a16ddb9e89 Mon Sep 17 00:00:00 2001 From: Sandro Munda Date: Tue, 5 Jun 2012 08:33:47 +0200 Subject: Improved the close of the proxy thread (and the socket) in the xep_0065 plugin. --- sleekxmpp/plugins/xep_0065/proxy.py | 63 +++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 27 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/xep_0065/proxy.py b/sleekxmpp/plugins/xep_0065/proxy.py index 865c2f90..86598df3 100644 --- a/sleekxmpp/plugins/xep_0065/proxy.py +++ b/sleekxmpp/plugins/xep_0065/proxy.py @@ -184,33 +184,39 @@ class xep_0065(base_plugin): # Send the IQ. act_iq.send() - def send(self, sid, msg): - """ Sends the msg to the socket. + def deactivate(self, sid): + """ Closes the Proxy thread associated to this SID. + """ + + proxy = self.proxy_threads.get(sid) + if proxy: + proxy.s.close() + del self.proxy_threads[sid] + + def close(self): + """ Closes all Proxy threads. + """ + + for sid, proxy in self.proxy_threads.items(): + proxy.s.close() + del self.proxy_threads[sid] - sid : The SID to retrieve the good proxy stored in the - proxy_threads dict - msg : The message data. + def send(self, sid, data): + """ Sends the data over the Proxy socket associated to the + SID. """ proxy = self.proxy_threads.get(sid) if proxy: - proxy.send(msg) - else: - # TODO: raise an exception. - pass + proxy.s.sendall(data) def on_recv(self, sid, data): - """ Called when receiving data + """ Calls when data is recv from the Proxy socket associated + to the SID. """ - if not data: - try: - del self.proxy_threads[sid] - except KeyError: - # TODO: internal error, raise an exception ? - pass - else: - log.debug('Received data: %s' % data) + proxy = self.proxy_threads.get(sid) + if proxy: self.xmpp.event('socks_recv', data) @@ -275,10 +281,10 @@ class Proxy(Thread): log.info('Socket connected.') self.connected.set() - # Listen for data on the socket + # Blocks until the socket need to be closed. self.listen() - # Listen returns when the socket must be closed. + # Closes the socket. self.s.close() log.info('Socket closed.') @@ -289,7 +295,16 @@ class Proxy(Thread): socket_open = True while socket_open: - ins, out, err = select([self.s, ], [], []) + ins = [] + try: + # Wait any read available data on socket. Timeout + # after 5 secs. + ins, out, err = select([self.s, ], [], [], 5) + except Exception, e: + # There's an error with the socket (maybe the socket + # has been closed and the file descriptor is bad). + log.debug('Socket error: %s' % e) + break for s in ins: data = self.recv_size(self.s) @@ -324,9 +339,3 @@ class Proxy(Thread): total_data.append(sock_data) total_len = sum([len(i) for i in total_data]) return ''.join(total_data) - - def send(self, msg): - """ Sends the data over the socket. - """ - - self.s.sendall(msg) -- cgit v1.2.3 From c59a6d0f51112b1bc35f9d492342dea63bfce6e5 Mon Sep 17 00:00:00 2001 From: Sandro Munda Date: Thu, 7 Jun 2012 18:37:42 +0200 Subject: Sent a socks_closed when the socket is closed in the xep_0065 plugin. --- sleekxmpp/plugins/xep_0065/proxy.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/xep_0065/proxy.py b/sleekxmpp/plugins/xep_0065/proxy.py index 86598df3..89f37399 100644 --- a/sleekxmpp/plugins/xep_0065/proxy.py +++ b/sleekxmpp/plugins/xep_0065/proxy.py @@ -217,7 +217,10 @@ class xep_0065(base_plugin): proxy = self.proxy_threads.get(sid) if proxy: - self.xmpp.event('socks_recv', data) + if not data: + self.xmpp.event('socks_closed', sid) + else: + self.xmpp.event('socks_recv', data) class Proxy(Thread): -- cgit v1.2.3 From dcdf5dcd098ee8033ca9036af7e2f6ab8ddba620 Mon Sep 17 00:00:00 2001 From: Sandro Munda Date: Thu, 7 Jun 2012 19:02:09 +0200 Subject: Added the Socksipy module in the thirdparty of SleekXMPP. Updated the LICENSE file with the license of the Socksipy module (New-BSD). --- sleekxmpp/plugins/xep_0065/proxy.py | 6 +- sleekxmpp/thirdparty/__init__.py | 2 +- sleekxmpp/thirdparty/socks.py | 382 ++++++++++++++++++++++++++++++++++++ 3 files changed, 386 insertions(+), 4 deletions(-) create mode 100644 sleekxmpp/thirdparty/socks.py (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/xep_0065/proxy.py b/sleekxmpp/plugins/xep_0065/proxy.py index 89f37399..f7b259f2 100644 --- a/sleekxmpp/plugins/xep_0065/proxy.py +++ b/sleekxmpp/plugins/xep_0065/proxy.py @@ -7,12 +7,12 @@ from hashlib import sha1 from select import select from uuid import uuid4 +import stanza + from sleekxmpp.plugins.base import base_plugin from sleekxmpp.xmlstream.handler import Callback from sleekxmpp.xmlstream.matcher import StanzaPath -from socks import socksocket, PROXY_TYPE_SOCKS5 - -import stanza +from sleekxmpp.thirdparty.socks import socksocket, PROXY_TYPE_SOCKS5 # Registers the sleekxmpp logger log = logging.getLogger(__name__) diff --git a/sleekxmpp/thirdparty/__init__.py b/sleekxmpp/thirdparty/__init__.py index b9c82a7f..7ec045a6 100644 --- a/sleekxmpp/thirdparty/__init__.py +++ b/sleekxmpp/thirdparty/__init__.py @@ -8,5 +8,5 @@ try: except: from sleekxmpp.thirdparty.gnupg import GPG -from sleekxmpp.thirdparty import suelta +from sleekxmpp.thirdparty import suelta, socks from sleekxmpp.thirdparty.mini_dateutil import tzutc, tzoffset, parse_iso diff --git a/sleekxmpp/thirdparty/socks.py b/sleekxmpp/thirdparty/socks.py new file mode 100644 index 00000000..a6c0d70e --- /dev/null +++ b/sleekxmpp/thirdparty/socks.py @@ -0,0 +1,382 @@ +"""SocksiPy - Python SOCKS module. +Version 1.00 + +Copyright 2006 Dan-Haim. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +3. Neither the name of Dan Haim nor the names of his contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE. + + +This module provides a standard socket-like interface for Python +for tunneling connections through SOCKS proxies. + +""" + +""" + +Minor modifications made by Christopher Gilbert (http://motomastyle.com/) +for use in PyLoris (http://pyloris.sourceforge.net/) + +Minor modifications made by Mario Vilas (http://breakingcode.wordpress.com/) +mainly to merge bug fixes found in Sourceforge + +""" + +import socket +import struct +import sys + +PROXY_TYPE_SOCKS4 = 1 +PROXY_TYPE_SOCKS5 = 2 +PROXY_TYPE_HTTP = 3 + +_defaultproxy = None +_orgsocket = socket.socket + +class ProxyError(Exception): pass +class GeneralProxyError(ProxyError): pass +class Socks5AuthError(ProxyError): pass +class Socks5Error(ProxyError): pass +class Socks4Error(ProxyError): pass +class HTTPError(ProxyError): pass + +_generalerrors = ("success", + "invalid data", + "not connected", + "not available", + "bad proxy type", + "bad input") + +_socks5errors = ("succeeded", + "general SOCKS server failure", + "connection not allowed by ruleset", + "Network unreachable", + "Host unreachable", + "Connection refused", + "TTL expired", + "Command not supported", + "Address type not supported", + "Unknown error") + +_socks5autherrors = ("succeeded", + "authentication is required", + "all offered authentication methods were rejected", + "unknown username or invalid password", + "unknown error") + +_socks4errors = ("request granted", + "request rejected or failed", + "request rejected because SOCKS server cannot connect to identd on the client", + "request rejected because the client program and identd report different user-ids", + "unknown error") + +def setdefaultproxy(proxytype=None, addr=None, port=None, rdns=True, username=None, password=None): + """setdefaultproxy(proxytype, addr[, port[, rdns[, username[, password]]]]) + Sets a default proxy which all further socksocket objects will use, + unless explicitly changed. + """ + global _defaultproxy + _defaultproxy = (proxytype, addr, port, rdns, username, password) + +def wrapmodule(module): + """wrapmodule(module) + Attempts to replace a module's socket library with a SOCKS socket. Must set + a default proxy using setdefaultproxy(...) first. + This will only work on modules that import socket directly into the namespace; + most of the Python Standard Library falls into this category. + """ + if _defaultproxy != None: + module.socket.socket = socksocket + else: + raise GeneralProxyError((4, "no proxy specified")) + +class socksocket(socket.socket): + """socksocket([family[, type[, proto]]]) -> socket object + Open a SOCKS enabled socket. The parameters are the same as + those of the standard socket init. In order for SOCKS to work, + you must specify family=AF_INET, type=SOCK_STREAM and proto=0. + """ + + def __init__(self, family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0, _sock=None): + _orgsocket.__init__(self, family, type, proto, _sock) + if _defaultproxy != None: + self.__proxy = _defaultproxy + else: + self.__proxy = (None, None, None, None, None, None) + self.__proxysockname = None + self.__proxypeername = None + + def __recvall(self, count): + """__recvall(count) -> data + Receive EXACTLY the number of bytes requested from the socket. + Blocks until the required number of bytes have been received. + """ + data = self.recv(count) + while len(data) < count: + d = self.recv(count-len(data)) + if not d: raise GeneralProxyError((0, "connection closed unexpectedly")) + data = data + d + return data + + def setproxy(self, proxytype=None, addr=None, port=None, rdns=True, username=None, password=None): + """setproxy(proxytype, addr[, port[, rdns[, username[, password]]]]) + Sets the proxy to be used. + proxytype - The type of the proxy to be used. Three types + are supported: PROXY_TYPE_SOCKS4 (including socks4a), + PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP + addr - The address of the server (IP or DNS). + port - The port of the server. Defaults to 1080 for SOCKS + servers and 8080 for HTTP proxy servers. + rdns - Should DNS queries be preformed on the remote side + (rather than the local side). The default is True. + Note: This has no effect with SOCKS4 servers. + username - Username to authenticate with to the server. + The default is no authentication. + password - Password to authenticate with to the server. + Only relevant when username is also provided. + """ + self.__proxy = (proxytype, addr, port, rdns, username, password) + + def __negotiatesocks5(self, destaddr, destport): + """__negotiatesocks5(self,destaddr,destport) + Negotiates a connection through a SOCKS5 server. + """ + # First we'll send the authentication packages we support. + if (self.__proxy[4]!=None) and (self.__proxy[5]!=None): + # The username/password details were supplied to the + # setproxy method so we support the USERNAME/PASSWORD + # authentication (in addition to the standard none). + self.sendall(struct.pack('BBBB', 0x05, 0x02, 0x00, 0x02)) + else: + # No username/password were entered, therefore we + # only support connections with no authentication. + self.sendall(struct.pack('BBB', 0x05, 0x01, 0x00)) + # We'll receive the server's response to determine which + # method was selected + chosenauth = self.__recvall(2) + if chosenauth[0:1] != chr(0x05).encode(): + self.close() + raise GeneralProxyError((1, _generalerrors[1])) + # Check the chosen authentication method + if chosenauth[1:2] == chr(0x00).encode(): + # No authentication is required + pass + elif chosenauth[1:2] == chr(0x02).encode(): + # Okay, we need to perform a basic username/password + # authentication. + self.sendall(chr(0x01).encode() + chr(len(self.__proxy[4])) + self.__proxy[4] + chr(len(self.__proxy[5])) + self.__proxy[5]) + authstat = self.__recvall(2) + if authstat[0:1] != chr(0x01).encode(): + # Bad response + self.close() + raise GeneralProxyError((1, _generalerrors[1])) + if authstat[1:2] != chr(0x00).encode(): + # Authentication failed + self.close() + raise Socks5AuthError((3, _socks5autherrors[3])) + # Authentication succeeded + else: + # Reaching here is always bad + self.close() + if chosenauth[1] == chr(0xFF).encode(): + raise Socks5AuthError((2, _socks5autherrors[2])) + else: + raise GeneralProxyError((1, _generalerrors[1])) + # Now we can request the actual connection + req = struct.pack('BBB', 0x05, 0x01, 0x00) + # If the given destination address is an IP address, we'll + # use the IPv4 address request even if remote resolving was specified. + try: + ipaddr = socket.inet_aton(destaddr) + req = req + chr(0x01).encode() + ipaddr + except socket.error: + # Well it's not an IP number, so it's probably a DNS name. + if self.__proxy[3]: + # Resolve remotely + ipaddr = None + req = req + chr(0x03).encode() + chr(len(destaddr)).encode() + destaddr + else: + # Resolve locally + ipaddr = socket.inet_aton(socket.gethostbyname(destaddr)) + req = req + chr(0x01).encode() + ipaddr + req = req + struct.pack(">H", destport) + self.sendall(req) + # Get the response + resp = self.__recvall(4) + if resp[0:1] != chr(0x05).encode(): + self.close() + raise GeneralProxyError((1, _generalerrors[1])) + elif resp[1:2] != chr(0x00).encode(): + # Connection failed + self.close() + if ord(resp[1:2])<=8: + raise Socks5Error((ord(resp[1:2]), _socks5errors[ord(resp[1:2])])) + else: + raise Socks5Error((9, _socks5errors[9])) + # Get the bound address/port + elif resp[3:4] == chr(0x01).encode(): + boundaddr = self.__recvall(4) + elif resp[3:4] == chr(0x03).encode(): + resp = resp + self.recv(1) + boundaddr = self.__recvall(ord(resp[4:5])) + else: + self.close() + raise GeneralProxyError((1,_generalerrors[1])) + boundport = struct.unpack(">H", self.__recvall(2))[0] + self.__proxysockname = (boundaddr, boundport) + if ipaddr != None: + self.__proxypeername = (socket.inet_ntoa(ipaddr), destport) + else: + self.__proxypeername = (destaddr, destport) + + def getproxysockname(self): + """getsockname() -> address info + Returns the bound IP address and port number at the proxy. + """ + return self.__proxysockname + + def getproxypeername(self): + """getproxypeername() -> address info + Returns the IP and port number of the proxy. + """ + return _orgsocket.getpeername(self) + + def getpeername(self): + """getpeername() -> address info + Returns the IP address and port number of the destination + machine (note: getproxypeername returns the proxy) + """ + return self.__proxypeername + + def __negotiatesocks4(self,destaddr,destport): + """__negotiatesocks4(self,destaddr,destport) + Negotiates a connection through a SOCKS4 server. + """ + # Check if the destination address provided is an IP address + rmtrslv = False + try: + ipaddr = socket.inet_aton(destaddr) + except socket.error: + # It's a DNS name. Check where it should be resolved. + if self.__proxy[3]: + ipaddr = struct.pack("BBBB", 0x00, 0x00, 0x00, 0x01) + rmtrslv = True + else: + ipaddr = socket.inet_aton(socket.gethostbyname(destaddr)) + # Construct the request packet + req = struct.pack(">BBH", 0x04, 0x01, destport) + ipaddr + # The username parameter is considered userid for SOCKS4 + if self.__proxy[4] != None: + req = req + self.__proxy[4] + req = req + chr(0x00).encode() + # DNS name if remote resolving is required + # NOTE: This is actually an extension to the SOCKS4 protocol + # called SOCKS4A and may not be supported in all cases. + if rmtrslv: + req = req + destaddr + chr(0x00).encode() + self.sendall(req) + # Get the response from the server + resp = self.__recvall(8) + if resp[0:1] != chr(0x00).encode(): + # Bad data + self.close() + raise GeneralProxyError((1,_generalerrors[1])) + if resp[1:2] != chr(0x5A).encode(): + # Server returned an error + self.close() + if ord(resp[1:2]) in (91, 92, 93): + self.close() + raise Socks4Error((ord(resp[1:2]), _socks4errors[ord(resp[1:2]) - 90])) + else: + raise Socks4Error((94, _socks4errors[4])) + # Get the bound address/port + self.__proxysockname = (socket.inet_ntoa(resp[4:]), struct.unpack(">H", resp[2:4])[0]) + if rmtrslv != None: + self.__proxypeername = (socket.inet_ntoa(ipaddr), destport) + else: + self.__proxypeername = (destaddr, destport) + + def __negotiatehttp(self, destaddr, destport): + """__negotiatehttp(self,destaddr,destport) + Negotiates a connection through an HTTP server. + """ + # If we need to resolve locally, we do this now + if not self.__proxy[3]: + addr = socket.gethostbyname(destaddr) + else: + addr = destaddr + self.sendall(("CONNECT " + addr + ":" + str(destport) + " HTTP/1.1\r\n" + "Host: " + destaddr + "\r\n\r\n").encode()) + # We read the response until we get the string "\r\n\r\n" + resp = self.recv(1) + while resp.find("\r\n\r\n".encode()) == -1: + resp = resp + self.recv(1) + # We just need the first line to check if the connection + # was successful + statusline = resp.splitlines()[0].split(" ".encode(), 2) + if statusline[0] not in ("HTTP/1.0".encode(), "HTTP/1.1".encode()): + self.close() + raise GeneralProxyError((1, _generalerrors[1])) + try: + statuscode = int(statusline[1]) + except ValueError: + self.close() + raise GeneralProxyError((1, _generalerrors[1])) + if statuscode != 200: + self.close() + raise HTTPError((statuscode, statusline[2])) + self.__proxysockname = ("0.0.0.0", 0) + self.__proxypeername = (addr, destport) + + def connect(self, destpair): + """connect(self, despair) + Connects to the specified destination through a proxy. + destpar - A tuple of the IP/DNS address and the port number. + (identical to socket's connect). + To select the proxy server use setproxy(). + """ + # Do a minimal input check first + if (not type(destpair) in (list,tuple)) or (len(destpair) < 2) or (type(destpair[0]) != type('')) or (type(destpair[1]) != int): + raise GeneralProxyError((5, _generalerrors[5])) + if self.__proxy[0] == PROXY_TYPE_SOCKS5: + if self.__proxy[2] != None: + portnum = self.__proxy[2] + else: + portnum = 1080 + _orgsocket.connect(self, (self.__proxy[1], portnum)) + self.__negotiatesocks5(destpair[0], destpair[1]) + elif self.__proxy[0] == PROXY_TYPE_SOCKS4: + if self.__proxy[2] != None: + portnum = self.__proxy[2] + else: + portnum = 1080 + _orgsocket.connect(self,(self.__proxy[1], portnum)) + self.__negotiatesocks4(destpair[0], destpair[1]) + elif self.__proxy[0] == PROXY_TYPE_HTTP: + if self.__proxy[2] != None: + portnum = self.__proxy[2] + else: + portnum = 8080 + _orgsocket.connect(self,(self.__proxy[1], portnum)) + self.__negotiatehttp(destpair[0], destpair[1]) + elif self.__proxy[0] == None: + _orgsocket.connect(self, (destpair[0], destpair[1])) + else: + raise GeneralProxyError((4, _generalerrors[4])) -- cgit v1.2.3 From ae01f1071ab4634d40b334e956298c2004904815 Mon Sep 17 00:00:00 2001 From: Sandro Munda Date: Thu, 7 Jun 2012 19:04:24 +0200 Subject: Fixed the callback names of the xep_0065: In-Band bytestreams -> Socks5 bytestreams --- sleekxmpp/plugins/xep_0065/proxy.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/xep_0065/proxy.py b/sleekxmpp/plugins/xep_0065/proxy.py index f7b259f2..c1df23b4 100644 --- a/sleekxmpp/plugins/xep_0065/proxy.py +++ b/sleekxmpp/plugins/xep_0065/proxy.py @@ -20,7 +20,7 @@ log = logging.getLogger(__name__) class xep_0065(base_plugin): """ - XEP-0065 In-Band Bytestreams + XEP-0065 Socks5 Bytestreams """ description = "Socks5 Bytestreams" @@ -40,13 +40,13 @@ class xep_0065(base_plugin): # Handler for the streamhost stanza. self.xmpp.registerHandler( - Callback('In-Band Bytestreams', + Callback('Socks5 Bytestreams', StanzaPath('iq@type=set/q/streamhost'), self._handle_streamhost)) # Handler for the streamhost-used stanza. self.xmpp.registerHandler( - Callback('In-Band Bytestreams', + Callback('Socks5 Bytestreams', StanzaPath('iq@type=result/q/streamhost-used'), self._handle_streamhost_used)) -- cgit v1.2.3 From 26147f5ae0f2cf2e59c25ce90ff71653125e6b69 Mon Sep 17 00:00:00 2001 From: Sandro Munda Date: Thu, 7 Jun 2012 19:08:20 +0200 Subject: Added a top level field to the xep_0065 class: name = 'xep_0065' --- sleekxmpp/plugins/xep_0065/proxy.py | 1 + 1 file changed, 1 insertion(+) (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/xep_0065/proxy.py b/sleekxmpp/plugins/xep_0065/proxy.py index c1df23b4..88138b85 100644 --- a/sleekxmpp/plugins/xep_0065/proxy.py +++ b/sleekxmpp/plugins/xep_0065/proxy.py @@ -26,6 +26,7 @@ class xep_0065(base_plugin): description = "Socks5 Bytestreams" dependencies = set(['xep_0030', ]) xep = '0065' + name = 'xep_0065' # A dict contains for each SID, the proxy thread currently # running. -- cgit v1.2.3 From 289b0523387e3c6dc7bde1161736aa791d6bcc99 Mon Sep 17 00:00:00 2001 From: Sandro Munda Date: Thu, 7 Jun 2012 19:14:37 +0200 Subject: Renamed Query to Socks5 in the xep_0065. Renamed the 'q' plugin_attrib of the Socks5 stanza to 'socks'. --- sleekxmpp/plugins/xep_0065/proxy.py | 38 ++++++++++++++++++------------------ sleekxmpp/plugins/xep_0065/stanza.py | 10 +++++----- 2 files changed, 24 insertions(+), 24 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/xep_0065/proxy.py b/sleekxmpp/plugins/xep_0065/proxy.py index 88138b85..f950b6aa 100644 --- a/sleekxmpp/plugins/xep_0065/proxy.py +++ b/sleekxmpp/plugins/xep_0065/proxy.py @@ -42,13 +42,13 @@ class xep_0065(base_plugin): # Handler for the streamhost stanza. self.xmpp.registerHandler( Callback('Socks5 Bytestreams', - StanzaPath('iq@type=set/q/streamhost'), + StanzaPath('iq@type=set/socks/streamhost'), self._handle_streamhost)) # Handler for the streamhost-used stanza. self.xmpp.registerHandler( Callback('Socks5 Bytestreams', - StanzaPath('iq@type=result/q/streamhost-used'), + StanzaPath('iq@type=result/socks/streamhost-used'), self._handle_streamhost_used)) def handshake(self, to, streamer=None): @@ -61,8 +61,8 @@ class xep_0065(base_plugin): # Requester requests network address from the proxy. streamhost = self.get_network_address(self.streamer) - self.proxy_host = streamhost['q']['streamhost']['host'] - self.proxy_port = streamhost['q']['streamhost']['port'] + self.proxy_host = streamhost['socks']['streamhost']['host'] + self.proxy_port = streamhost['socks']['streamhost']['port'] # Generates the SID for this new handshake. sid = uuid4().hex @@ -72,10 +72,10 @@ class xep_0065(base_plugin): # StreamHost as well as the StreamID (SID) of the proposed # bytestream. iq = self.xmpp.Iq(sto=to, stype='set') - iq['q']['sid'] = sid - iq['q']['streamhost']['jid'] = self.streamer - iq['q']['streamhost']['host'] = self.proxy_host - iq['q']['streamhost']['port'] = self.proxy_port + iq['socks']['sid'] = sid + iq['socks']['streamhost']['jid'] = self.streamer + iq['socks']['streamhost']['host'] = self.proxy_host + iq['socks']['streamhost']['port'] = self.proxy_port # Sends the new IQ. return iq.send() @@ -108,7 +108,7 @@ class xep_0065(base_plugin): """ iq = self.xmpp.Iq(sto=streamer, stype='get') - iq['q'] # Adds the query eleme to the iq. + iq['socks'] # Adds the query eleme to the iq. return iq.send() @@ -117,12 +117,12 @@ class xep_0065(base_plugin): """ # Registers the streamhost info. - self.streamer = iq['q']['streamhost']['jid'] - self.proxy_host = iq['q']['streamhost']['host'] - self.proxy_port = iq['q']['streamhost']['port'] + self.streamer = iq['socks']['streamhost']['jid'] + self.proxy_host = iq['socks']['streamhost']['host'] + self.proxy_port = iq['socks']['streamhost']['port'] # Sets the SID, the requester and the target. - sid = iq['q']['sid'] + sid = iq['socks']['sid'] requester = '%s' % iq['from'] target = '%s' % self.xmpp.boundjid @@ -140,8 +140,8 @@ class xep_0065(base_plugin): # Replies to the incoming iq with a streamhost-used stanza. res_iq = iq.reply() - res_iq['q']['sid'] = sid - res_iq['q']['streamhost-used']['jid'] = self.streamer + res_iq['socks']['sid'] = sid + res_iq['socks']['streamhost-used']['jid'] = self.streamer # Sends the IQ return res_iq.send() @@ -151,7 +151,7 @@ class xep_0065(base_plugin): """ # Sets the SID, the requester and the target. - sid = iq['q']['sid'] + sid = iq['socks']['sid'] requester = '%s' % self.xmpp.boundjid target = '%s' % iq['from'] @@ -170,7 +170,7 @@ class xep_0065(base_plugin): # Requester sends IQ-set to StreamHost requesting that # StreamHost activate the bytestream associated with the # StreamID. - self.activate(iq['q']['sid'], target) + self.activate(iq['socks']['sid'], target) def activate(self, sid, to): """ IQ-set to StreamHost requesting that StreamHost activate @@ -179,8 +179,8 @@ class xep_0065(base_plugin): # Creates the activate IQ. act_iq = self.xmpp.Iq(sto=self.streamer, stype='set') - act_iq['q']['sid'] = sid - act_iq['q']['activate'] = to + act_iq['socks']['sid'] = sid + act_iq['socks']['activate'] = to # Send the IQ. act_iq.send() diff --git a/sleekxmpp/plugins/xep_0065/stanza.py b/sleekxmpp/plugins/xep_0065/stanza.py index 0990a509..ae57aba8 100644 --- a/sleekxmpp/plugins/xep_0065/stanza.py +++ b/sleekxmpp/plugins/xep_0065/stanza.py @@ -26,16 +26,16 @@ class StreamHostUsed(ElementBase): interfaces = set(('jid',)) -class Query(ElementBase): +class Socks5(ElementBase): """ The query xml element. """ namespace = namespace name = 'query' - plugin_attrib = 'q' + plugin_attrib = 'socks' interfaces = set(('sid', 'activate')) sub_interfaces = set(('activate',)) -register_stanza_plugin(Iq, Query) -register_stanza_plugin(Query, StreamHost) -register_stanza_plugin(Query, StreamHostUsed) +register_stanza_plugin(Iq, Socks5) +register_stanza_plugin(Socks5, StreamHost) +register_stanza_plugin(Socks5, StreamHostUsed) -- cgit v1.2.3 From 1851ab6f5fe3548eccbc118bdb71340ee92dd9da Mon Sep 17 00:00:00 2001 From: Sandro Munda Date: Thu, 7 Jun 2012 19:24:23 +0200 Subject: Added the SID in the socks_recv xmpp event in the xep_0065 plugin. --- sleekxmpp/plugins/xep_0065/proxy.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/xep_0065/proxy.py b/sleekxmpp/plugins/xep_0065/proxy.py index f950b6aa..60b7d6ed 100644 --- a/sleekxmpp/plugins/xep_0065/proxy.py +++ b/sleekxmpp/plugins/xep_0065/proxy.py @@ -214,6 +214,12 @@ class xep_0065(base_plugin): def on_recv(self, sid, data): """ Calls when data is recv from the Proxy socket associated to the SID. + + Triggers a socks_closed event if the socket is closed. The sid + is passed to this event. + + Triggers a socks_recv event if there's available data. A dict + that contains the sid and the data is passed to this event. """ proxy = self.proxy_threads.get(sid) @@ -221,7 +227,7 @@ class xep_0065(base_plugin): if not data: self.xmpp.event('socks_closed', sid) else: - self.xmpp.event('socks_recv', data) + self.xmpp.event('socks_recv', {'sid': sid, 'data': data}) class Proxy(Thread): -- cgit v1.2.3 From 8def3758e4e849f25001e1e76616fcc3836bd1c2 Mon Sep 17 00:00:00 2001 From: Sandro Munda Date: Thu, 7 Jun 2012 19:36:25 +0200 Subject: Added the get_socket(sid) method to the xep_0065 plugin to retrieve the socket of the Proxy thread. --- sleekxmpp/plugins/xep_0065/proxy.py | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/xep_0065/proxy.py b/sleekxmpp/plugins/xep_0065/proxy.py index 60b7d6ed..4fdd2ad8 100644 --- a/sleekxmpp/plugins/xep_0065/proxy.py +++ b/sleekxmpp/plugins/xep_0065/proxy.py @@ -51,6 +51,14 @@ class xep_0065(base_plugin): StanzaPath('iq@type=result/socks/streamhost-used'), self._handle_streamhost_used)) + def get_socket(self, sid): + """ Returns the socket associated to the SID. + """ + + proxy = self.proxy_threads.get(sid) + if proxy: + return proxy.s + def handshake(self, to, streamer=None): """ Starts the handshake to establish the socks5 bytestreams connection. -- cgit v1.2.3 From 48dd01b0bb7db1d93bf2d21e681939bfcd2f1297 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Fri, 8 Jun 2012 09:31:44 -0700 Subject: Ensure that all SSL cert error handling is overridable using event handlers. Relevant events: ssl_invalid_cert ssl_invalid_chain ssl_expired_cert --- sleekxmpp/xmlstream/xmlstream.py | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py index ac0fc256..7376d56d 100644 --- a/sleekxmpp/xmlstream/xmlstream.py +++ b/sleekxmpp/xmlstream/xmlstream.py @@ -493,7 +493,8 @@ class XMLStream(object): ssl_socket = ssl.wrap_socket(self.socket, ca_certs=self.ca_certs, - cert_reqs=cert_policy) + cert_reqs=cert_policy, + do_handshake_on_connect=False) if hasattr(self.socket, 'socket'): # We are using a testing socket, so preserve the top @@ -510,6 +511,17 @@ class XMLStream(object): log.debug("Connecting to %s:%s", domain, self.address[1]) self.socket.connect(self.address) + try: + self.socket.do_handshake() + except: + log.error('CERT: Invalid certificate trust chain.') + if not self.event_handled('ssl_invalid_chain'): + self.disconnect(self.auto_reconnect, send_close=False) + else: + self.event('ssl_invalid_chain', direct=True) + return False + + if self.use_ssl and self.ssl_support: self._der_cert = self.socket.getpeercert(binary_form=True) pem_cert = ssl.DER_cert_to_PEM_cert(self._der_cert) @@ -520,8 +532,10 @@ class XMLStream(object): cert.verify(self._expected_server_name, self._der_cert) except cert.CertificateError as err: log.error(err.message) - self.event('ssl_invalid_cert', cert, direct=True) - self.disconnect(send_close=False) + if not self.event_handled('ssl_invalid_cert'): + self.disconnect(send_close=False) + else: + self.event('ssl_invalid_cert', cert, direct=True) self.set_socket(self.socket, ignore=True) #this event is where you should set your application state @@ -790,8 +804,10 @@ class XMLStream(object): self.socket.do_handshake() except: log.error('CERT: Invalid certificate trust chain.') - self.event('ssl_invalid_chain', direct=True) - self.disconnect(self.auto_reconnect, send_close=False) + if not self.event_handled('ssl_invalid_chain'): + self.disconnect(self.auto_reconnect, send_close=False) + else: + self.event('ssl_invalid_chain', direct=True) return False self._der_cert = self.socket.getpeercert(binary_form=True) @@ -803,9 +819,10 @@ class XMLStream(object): cert.verify(self._expected_server_name, self._der_cert) except cert.CertificateError as err: log.error(err.message) - self.event('ssl_invalid_cert', cert, direct=True) if not self.event_handled('ssl_invalid_cert'): self.disconnect(self.auto_reconnect, send_close=False) + else: + self.event('ssl_invalid_cert', cert, direct=True) self.set_socket(self.socket) return True @@ -820,8 +837,12 @@ class XMLStream(object): return def restart(): - log.warn("The server certificate has expired. Restarting.") - self.reconnect() + if not self.event_handled('ssl_expired_cert'): + log.warn("The server certificate has expired. Restarting.") + self.reconnect() + else: + pem_cert = ssl.DER_cert_to_PEM_cert(self._der_cert) + self.event('ssl_expired_cert', pem_cert) cert_ttl = cert.get_ttl(self._der_cert) if cert_ttl is None: -- cgit v1.2.3 From 0953896d2dc41ab08baf6a9ef6f599e77fe602de Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sat, 9 Jun 2012 10:32:25 -0700 Subject: Fix SSL handshake handling when not using legacy SSL. Fixes issue #172 --- sleekxmpp/xmlstream/xmlstream.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py index 7376d56d..8575c65b 100644 --- a/sleekxmpp/xmlstream/xmlstream.py +++ b/sleekxmpp/xmlstream/xmlstream.py @@ -511,18 +511,17 @@ class XMLStream(object): log.debug("Connecting to %s:%s", domain, self.address[1]) self.socket.connect(self.address) - try: - self.socket.do_handshake() - except: - log.error('CERT: Invalid certificate trust chain.') - if not self.event_handled('ssl_invalid_chain'): - self.disconnect(self.auto_reconnect, send_close=False) - else: - self.event('ssl_invalid_chain', direct=True) - return False - - if self.use_ssl and self.ssl_support: + try: + self.socket.do_handshake() + except (Socket.error, ssl.SSLError): + log.error('CERT: Invalid certificate trust chain.') + if not self.event_handled('ssl_invalid_chain'): + self.disconnect(self.auto_reconnect, send_close=False) + else: + self.event('ssl_invalid_chain', direct=True) + return False + self._der_cert = self.socket.getpeercert(binary_form=True) pem_cert = ssl.DER_cert_to_PEM_cert(self._der_cert) log.debug('CERT: %s', pem_cert) @@ -802,7 +801,7 @@ class XMLStream(object): try: self.socket.do_handshake() - except: + except (Socket.error, ssl.SSLError): log.error('CERT: Invalid certificate trust chain.') if not self.event_handled('ssl_invalid_chain'): self.disconnect(self.auto_reconnect, send_close=False) -- cgit v1.2.3 From 10664d723b24739bd0b868b71dbcc8dab80d02ef Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sat, 9 Jun 2012 10:43:57 -0700 Subject: Default use_tls to False for components. Issue #171 --- sleekxmpp/componentxmpp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/componentxmpp.py b/sleekxmpp/componentxmpp.py index 348a08e0..1fb9c4d9 100644 --- a/sleekxmpp/componentxmpp.py +++ b/sleekxmpp/componentxmpp.py @@ -79,7 +79,7 @@ class ComponentXMPP(BaseXMPP): self._handle_probe) def connect(self, host=None, port=None, use_ssl=False, - use_tls=True, reattempt=True): + use_tls=False, reattempt=True): """Connect to the server. Setting ``reattempt`` to ``True`` will cause connection attempts to -- cgit v1.2.3 From 2b298766c91b5f8c36783be93ad54c228a8dd6ba Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sat, 9 Jun 2012 10:47:27 -0700 Subject: Use False for use_tls for components. A log message is shown for those who try to set it to True. Fixes issue #171 --- sleekxmpp/componentxmpp.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/componentxmpp.py b/sleekxmpp/componentxmpp.py index 1fb9c4d9..33fc882d 100644 --- a/sleekxmpp/componentxmpp.py +++ b/sleekxmpp/componentxmpp.py @@ -104,10 +104,13 @@ class ComponentXMPP(BaseXMPP): self.server_name = self.boundjid.host + if use_tls: + log.info("XEP-0114 components can not use TLS") + log.debug("Connecting to %s:%s", host, port) return XMLStream.connect(self, host=host, port=port, use_ssl=use_ssl, - use_tls=use_tls, + use_tls=False, reattempt=reattempt) def incoming_filter(self, xml): -- cgit v1.2.3 From 0b51afe87abf882fd74d18fe231458e33c949dd5 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sat, 9 Jun 2012 10:53:58 -0700 Subject: Add extra check for the cert in the expiration handler. --- sleekxmpp/xmlstream/xmlstream.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'sleekxmpp') diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py index 8575c65b..6dfd5498 100644 --- a/sleekxmpp/xmlstream/xmlstream.py +++ b/sleekxmpp/xmlstream/xmlstream.py @@ -835,6 +835,10 @@ class XMLStream(object): if not self.use_tls and not self.use_ssl: return + if not self._der_cert: + log.warn("TLS or SSL was enabled, but no certificate was found.") + return + def restart(): if not self.event_handled('ssl_expired_cert'): log.warn("The server certificate has expired. Restarting.") -- cgit v1.2.3 From a7b092a305ec180856dee34bb100969cf34222ef Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sat, 9 Jun 2012 15:04:27 -0700 Subject: Fix Python3 exception handling. Fixes issue #173 --- sleekxmpp/plugins/xep_0065/proxy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/xep_0065/proxy.py b/sleekxmpp/plugins/xep_0065/proxy.py index 4fdd2ad8..012175ac 100644 --- a/sleekxmpp/plugins/xep_0065/proxy.py +++ b/sleekxmpp/plugins/xep_0065/proxy.py @@ -318,7 +318,7 @@ class Proxy(Thread): # Wait any read available data on socket. Timeout # after 5 secs. ins, out, err = select([self.s, ], [], [], 5) - except Exception, e: + except Exception as e: # There's an error with the socket (maybe the socket # has been closed and the file descriptor is bad). log.debug('Socket error: %s' % e) -- cgit v1.2.3 From f70b49882ffdfa23becc715a878d4ad41ca6f17e Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sun, 10 Jun 2012 14:15:58 -0700 Subject: Fix XEP-0065 imports and naming for Python3. --- sleekxmpp/plugins/__init__.py | 1 + sleekxmpp/plugins/xep_0065/__init__.py | 4 ++-- sleekxmpp/plugins/xep_0065/proxy.py | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/__init__.py b/sleekxmpp/plugins/__init__.py index 9ad3fbba..a4be9e65 100644 --- a/sleekxmpp/plugins/__init__.py +++ b/sleekxmpp/plugins/__init__.py @@ -27,6 +27,7 @@ __all__ = [ 'xep_0054', # vcard-temp 'xep_0059', # Result Set Management 'xep_0060', # Pubsub (Client) + 'xep_0065', # SOCKS5 Bytestreams 'xep_0066', # Out of Band Data 'xep_0077', # In-Band Registration # 'xep_0078', # Non-SASL auth. Don't automatically load diff --git a/sleekxmpp/plugins/xep_0065/__init__.py b/sleekxmpp/plugins/xep_0065/__init__.py index e1066005..c577d859 100644 --- a/sleekxmpp/plugins/xep_0065/__init__.py +++ b/sleekxmpp/plugins/xep_0065/__init__.py @@ -1,5 +1,5 @@ from sleekxmpp.plugins.base import register_plugin -from sleekxmpp.plugins.xep_0065.proxy import xep_0065 +from sleekxmpp.plugins.xep_0065.proxy import XEP_0065 -register_plugin(xep_0065) +register_plugin(XEP_0065) diff --git a/sleekxmpp/plugins/xep_0065/proxy.py b/sleekxmpp/plugins/xep_0065/proxy.py index 012175ac..b027e4e0 100644 --- a/sleekxmpp/plugins/xep_0065/proxy.py +++ b/sleekxmpp/plugins/xep_0065/proxy.py @@ -7,7 +7,7 @@ from hashlib import sha1 from select import select from uuid import uuid4 -import stanza +from sleekxmpp.plugins.xep_0065 import stanza from sleekxmpp.plugins.base import base_plugin from sleekxmpp.xmlstream.handler import Callback @@ -18,7 +18,7 @@ from sleekxmpp.thirdparty.socks import socksocket, PROXY_TYPE_SOCKS5 log = logging.getLogger(__name__) -class xep_0065(base_plugin): +class XEP_0065(base_plugin): """ XEP-0065 Socks5 Bytestreams """ -- cgit v1.2.3 From 19f65c8510f0c4b385a753f0817ea79877ccdc4a Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sun, 10 Jun 2012 14:42:54 -0700 Subject: Simplify send_presence_subscription. It is technically obsolete now, but remains because it set a default subscription type of 'subscribe'. --- sleekxmpp/basexmpp.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py index 43ea6063..ae32ebec 100644 --- a/sleekxmpp/basexmpp.py +++ b/sleekxmpp/basexmpp.py @@ -530,13 +530,16 @@ class BaseXMPP(XMLStream): :param pnick: Optional nickname of the presence's sender. """ # Python2.6 chokes on Unicode strings for dict keys. - args = {str('pto'): pto, - str('ptype'): ptype, + args = {str('ptype'): ptype, str('pshow'): pshow, str('pstatus'): pstatus, str('ppriority'): ppriority, str('pnick'): pnick} + if ptype in ('probe', 'subscribe', 'subscribed', \ + 'unsubscribe', 'unsubscribed'): + args[str('pto')] = pto.bare + if self.is_component: self.roster[pfrom].send_presence(**args) else: @@ -554,14 +557,10 @@ class BaseXMPP(XMLStream): :param ptype: The type of presence, such as ``'subscribe'``. :param pnick: Optional nickname of the presence's sender. """ - presence = self.makePresence(ptype=ptype, - pfrom=pfrom, - pto=self.getjidbare(pto)) - if pnick: - nick = ET.Element('{http://jabber.org/protocol/nick}nick') - nick.text = pnick - presence.append(nick) - presence.send() + self.send_presence(pto=pto, + pfrom=pfrom, + ptype=ptype, + pnick=pnick) @property def jid(self): -- cgit v1.2.3 From 250d28e870b63576e09dd1288b05394808518369 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Mon, 11 Jun 2012 08:28:02 -0700 Subject: Properly handle certs with no extensions. --- sleekxmpp/xmlstream/cert.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/xmlstream/cert.py b/sleekxmpp/xmlstream/cert.py index 0f58d4ed..b2711e8e 100644 --- a/sleekxmpp/xmlstream/cert.py +++ b/sleekxmpp/xmlstream/cert.py @@ -42,7 +42,7 @@ def extract_names(raw_cert): cert = decoder.decode(raw_cert, asn1Spec=Certificate())[0] tbs = cert.getComponentByName('tbsCertificate') subject = tbs.getComponentByName('subject') - extensions = tbs.getComponentByName('extensions') + extensions = tbs.getComponentByName('extensions') or [] # Extract the CommonName(s) from the cert. for rdnss in subject: -- cgit v1.2.3 From b210870f48e03a90a22d02b5ab7a3c0c81190fd7 Mon Sep 17 00:00:00 2001 From: Jay Farrimond Date: Thu, 5 Jul 2012 13:30:33 -0700 Subject: only log cert errors if not handled by user --- sleekxmpp/xmlstream/xmlstream.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py index 321e2694..3baa5b80 100644 --- a/sleekxmpp/xmlstream/xmlstream.py +++ b/sleekxmpp/xmlstream/xmlstream.py @@ -537,8 +537,8 @@ class XMLStream(object): try: cert.verify(self._expected_server_name, self._der_cert) except cert.CertificateError as err: - log.error(err.message) if not self.event_handled('ssl_invalid_cert'): + log.error(err.message) self.disconnect(send_close=False) else: self.event('ssl_invalid_cert', @@ -828,8 +828,8 @@ class XMLStream(object): try: cert.verify(self._expected_server_name, self._der_cert) except cert.CertificateError as err: - log.error(err.message) if not self.event_handled('ssl_invalid_cert'): + log.error(err.message) self.disconnect(self.auto_reconnect, send_close=False) else: self.event('ssl_invalid_cert', pem_cert, direct=True) -- cgit v1.2.3 From 23931489085391a97ab8b358e9da1c5c8cfb4e94 Mon Sep 17 00:00:00 2001 From: Jay Farrimond Date: Fri, 6 Jul 2012 13:50:15 -0700 Subject: dereference iq stanza only once for roster processing --- sleekxmpp/clientxmpp.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/clientxmpp.py b/sleekxmpp/clientxmpp.py index 7f606de7..03070b06 100644 --- a/sleekxmpp/clientxmpp.py +++ b/sleekxmpp/clientxmpp.py @@ -270,8 +270,9 @@ class ClientXMPP(BaseXMPP): roster = self.client_roster if iq['roster']['ver']: roster.version = iq['roster']['ver'] - for jid in iq['roster']['items']: - item = iq['roster']['items'][jid] + items = iq['roster']['items'] + for jid in items: + item = items[jid] roster[jid]['name'] = item['name'] roster[jid]['groups'] = item['groups'] roster[jid]['from'] = item['subscription'] in ['from', 'both'] -- cgit v1.2.3 From 09bec1c4fef78f9df021099d9b79d4f92c2af895 Mon Sep 17 00:00:00 2001 From: Florian Fieber Date: Sun, 19 Aug 2012 16:20:58 +0200 Subject: Fix certificate expiration scheduler timedelta.seconds does not store the total seconds of a time span. Internally, seconds is the next smaller unit to days, hence timedelta.seconds will never exceed (or reach) the number of seconds in a day (60*60*24=86400) --- sleekxmpp/xmlstream/xmlstream.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py index df06d067..246bc205 100644 --- a/sleekxmpp/xmlstream/xmlstream.py +++ b/sleekxmpp/xmlstream/xmlstream.py @@ -901,9 +901,15 @@ class XMLStream(object): log.warn('CERT: Certificate has expired.') restart() + try: + total_seconds = cert_ttl.total_seconds() + except AttributeError: + # for Python < 2.7 + total_seconds = (cert_ttl.microseconds + (cert_ttl.seconds + cert_ttl.days * 24 * 3600) * 10**6) / 10**6 + log.info('CERT: Time until certificate expiration: %s' % cert_ttl) self.schedule('Certificate Expiration', - cert_ttl.seconds, + total_seconds, restart) def _start_keepalive(self, event): -- cgit v1.2.3 From f52a10b061bb3ba485c70e3c37fe98cffd8808b2 Mon Sep 17 00:00:00 2001 From: Florian Fieber Date: Thu, 23 Aug 2012 03:55:17 +0200 Subject: Fix get_blocked() in XEP-0191 --- sleekxmpp/plugins/xep_0191/blocking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/xep_0191/blocking.py b/sleekxmpp/plugins/xep_0191/blocking.py index 4a87479a..57632319 100644 --- a/sleekxmpp/plugins/xep_0191/blocking.py +++ b/sleekxmpp/plugins/xep_0191/blocking.py @@ -48,7 +48,7 @@ class XEP_0191(BasePlugin): def get_blocked(self, ifrom=None, block=True, timeout=None, callback=None): iq = self.xmpp.Iq() iq['type'] = 'get' - iq['from'] = 'ifrom' + iq['from'] = ifrom iq.enable('blocklist') return iq.send(block=block, timeout=timeout, callback=callback) -- cgit v1.2.3 From 6045a6bfb3f5dc6172346601fab7e37713ca264f Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Wed, 31 Oct 2012 13:54:38 -0700 Subject: Bump version to 1.1.11 --- sleekxmpp/version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/version.py b/sleekxmpp/version.py index 010f425b..8f83adb3 100644 --- a/sleekxmpp/version.py +++ b/sleekxmpp/version.py @@ -9,5 +9,5 @@ # We don't want to have to import the entire library # just to get the version info for setup.py -__version__ = '1.1.10' -__version_info__ = (1, 1, 10, '', 0) +__version__ = '1.1.11' +__version_info__ = (1, 1, 11, '', 0) -- cgit v1.2.3 From d4449304940688b0fb6db09042ce4292cfc57859 Mon Sep 17 00:00:00 2001 From: Sandro Munda Date: Thu, 1 Nov 2012 10:58:54 +0100 Subject: Improved the gitignore files (vim temp file, .pyo file and .baboon directory). Automatically pack & unpack data through the socket. Added some comments to the pack method. Handled possible error during the unpacking of data. --- sleekxmpp/plugins/xep_0065/proxy.py | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/xep_0065/proxy.py b/sleekxmpp/plugins/xep_0065/proxy.py index b027e4e0..b8e199ac 100644 --- a/sleekxmpp/plugins/xep_0065/proxy.py +++ b/sleekxmpp/plugins/xep_0065/proxy.py @@ -1,6 +1,7 @@ import sys import logging import struct +import pickle from threading import Thread, Event from hashlib import sha1 @@ -217,7 +218,7 @@ class XEP_0065(base_plugin): proxy = self.proxy_threads.get(sid) if proxy: - proxy.s.sendall(data) + proxy.send(data) def on_recv(self, sid, data): """ Calls when data is recv from the Proxy socket associated @@ -306,6 +307,34 @@ class Proxy(Thread): self.s.close() log.info('Socket closed.') + def send(self, data): + """ Send data through the socket. + """ + + try: + packed_data = self._pack(data) + self.s.sendall(packed_data) + except pickle.PickleError as err: + log.error(err) + + def _pack(self, data): + """ Packs the data. + """ + + # The data format is: `len_data`+`data`. Useful to receive all the data + # at once (avoid splitted data) thanks to the recv_size method. + data = pickle.dumps(data) + return struct.pack('>i', len(data)) + data + + def _unpack(self, data): + """ Unpacks the data. On error, log an error message and returns None. + """ + + try: + return pickle.loads(data) + except Exception as err: + log.error(err) + def listen(self): """ Listen for data on the socket. When receiving data, call the callback on_recv callable. @@ -328,8 +357,10 @@ class Proxy(Thread): data = self.recv_size(self.s) if not data: socket_open = False - - self.on_recv(self.sid, data) + else: + unpacked_data = self._unpack(data) + if unpacked_data: + self.on_recv(self.sid, unpacked_data) def recv_size(self, the_socket): total_len = 0 -- cgit v1.2.3 From 3a7569e3ea73b50823982fff6a5f2d73470e36b8 Mon Sep 17 00:00:00 2001 From: Sandro Munda Date: Thu, 1 Nov 2012 11:36:52 +0100 Subject: Avoided to log a debug message error when the socket is normally closed. --- sleekxmpp/plugins/xep_0065/proxy.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/xep_0065/proxy.py b/sleekxmpp/plugins/xep_0065/proxy.py index b8e199ac..73c3c63d 100644 --- a/sleekxmpp/plugins/xep_0065/proxy.py +++ b/sleekxmpp/plugins/xep_0065/proxy.py @@ -2,6 +2,7 @@ import sys import logging import struct import pickle +import socket from threading import Thread, Event from hashlib import sha1 @@ -347,10 +348,14 @@ class Proxy(Thread): # Wait any read available data on socket. Timeout # after 5 secs. ins, out, err = select([self.s, ], [], [], 5) + except socket.error as (errno, err): + # 9 means the socket is closed. It can be normal. Otherwise, + # log the error. + if errno != 9: + log.debug('Socket error: %s' % err) + break except Exception as e: - # There's an error with the socket (maybe the socket - # has been closed and the file descriptor is bad). - log.debug('Socket error: %s' % e) + log.debug(e) break for s in ins: -- cgit v1.2.3 From 032d41dbb827a9cdf907b6724743bf7373575f2d Mon Sep 17 00:00:00 2001 From: Sandro Munda Date: Sun, 4 Nov 2012 11:38:57 +0100 Subject: Adapted the xep_0065 plugin to be compatible with all kind of others XMPP client. Sent a 'socks_connected' xmpp event when the streamer is connected. --- sleekxmpp/plugins/xep_0065/proxy.py | 213 ++++++------------------------------ 1 file changed, 32 insertions(+), 181 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/xep_0065/proxy.py b/sleekxmpp/plugins/xep_0065/proxy.py index 73c3c63d..dec37612 100644 --- a/sleekxmpp/plugins/xep_0065/proxy.py +++ b/sleekxmpp/plugins/xep_0065/proxy.py @@ -1,12 +1,6 @@ -import sys import logging -import struct -import pickle -import socket -from threading import Thread, Event from hashlib import sha1 -from select import select from uuid import uuid4 from sleekxmpp.plugins.xep_0065 import stanza @@ -32,7 +26,7 @@ class XEP_0065(base_plugin): # A dict contains for each SID, the proxy thread currently # running. - proxy_threads = {} + proxies = {} def plugin_init(self): """ Initializes the xep_0065 plugin and all event callbacks. @@ -57,9 +51,9 @@ class XEP_0065(base_plugin): """ Returns the socket associated to the SID. """ - proxy = self.proxy_threads.get(sid) + proxy = self.proxies.get(sid) if proxy: - return proxy.s + return proxy def handshake(self, to, streamer=None): """ Starts the handshake to establish the socks5 bytestreams @@ -138,15 +132,11 @@ class XEP_0065(base_plugin): # Next the Target attempts to open a standard TCP socket on # the network address of the Proxy. - self.proxy_thread = Proxy(sid, requester, target, self.proxy_host, - self.proxy_port, self.on_recv) - self.proxy_thread.start() + self.proxy = self._connect_proxy(sid, requester, target, + self.proxy_host, self.proxy_port) - # Registers the new thread in the proxy_thread dict. - self.proxy_threads[sid] = self.proxy_thread - - # Wait until the proxy is connected - self.proxy_thread.connected.wait() + # Registers the new proxy to the proxies dict. + self.proxies[sid] = self.proxy # Replies to the incoming iq with a streamhost-used stanza. res_iq = iq.reply() @@ -163,23 +153,18 @@ class XEP_0065(base_plugin): # Sets the SID, the requester and the target. sid = iq['socks']['sid'] requester = '%s' % self.xmpp.boundjid - target = '%s' % iq['from'] + target = '%s' % iq['from'] # The Requester will establish a connection to the SOCKS5 # proxy in the same way the Target did. - self.proxy_thread = Proxy(sid, requester, target, self.proxy_host, - self.proxy_port, self.on_recv) - self.proxy_thread.start() + self.proxy = self._connect_proxy(sid, requester, target, + self.proxy_host, self.proxy_port) # Registers the new thread in the proxy_thread dict. - self.proxy_threads[sid] = self.proxy_thread + self.proxies[sid] = self.proxy - # Wait until the proxy is connected - self.proxy_thread.connected.wait() - - # Requester sends IQ-set to StreamHost requesting that - # StreamHost activate the bytestream associated with the - # StreamID. + # Requester sends IQ-set to StreamHost requesting that StreamHost + # activate the bytestream associated with the StreamID. self.activate(iq['socks']['sid'], target) def activate(self, sid, to): @@ -199,197 +184,63 @@ class XEP_0065(base_plugin): """ Closes the Proxy thread associated to this SID. """ - proxy = self.proxy_threads.get(sid) + proxy = self.proxies.get(sid) if proxy: proxy.s.close() - del self.proxy_threads[sid] + del self.proxies[sid] def close(self): """ Closes all Proxy threads. """ - for sid, proxy in self.proxy_threads.items(): - proxy.s.close() - del self.proxy_threads[sid] + for sid, proxy in self.proxies.items(): + proxy.close() + del self.proxies[sid] def send(self, sid, data): """ Sends the data over the Proxy socket associated to the SID. """ - proxy = self.proxy_threads.get(sid) + proxy = self.get_socket(sid) if proxy: - proxy.send(data) + proxy.sendall(data) - def on_recv(self, sid, data): - """ Calls when data is recv from the Proxy socket associated - to the SID. - - Triggers a socks_closed event if the socket is closed. The sid - is passed to this event. - - Triggers a socks_recv event if there's available data. A dict - that contains the sid and the data is passed to this event. - """ - - proxy = self.proxy_threads.get(sid) - if proxy: - if not data: - self.xmpp.event('socks_closed', sid) - else: - self.xmpp.event('socks_recv', {'sid': sid, 'data': data}) - - -class Proxy(Thread): - """ Establishes in a thread a connection between the client and - the server-side Socks5 proxy. - """ - - def __init__(self, sid, requester, target, proxy, proxy_port, - on_recv): - """ Initializes the proxy thread. + def _connect_proxy(self, sid, requester, target, proxy, proxy_port): + """ Establishes a connection between the client and the server-side + Socks5 proxy. sid : The StreamID. requester : The JID of the requester. target : The JID of the target. proxy_host : The hostname or the IP of the proxy. proxy_port : The port of the proxy. or - on_recv : A callback called when data are received from the - socket. """ - # Initializes the thread. - Thread.__init__(self) - # Because the xep_0065 plugin uses the proxy_port as string, # the Proxy class accepts the proxy_port argument as a string # or an integer. Here, we force to use the port as an integer. proxy_port = int(proxy_port) - # Creates a connected event to warn when to proxy is - # connected. - self.connected = Event() - - # Registers the arguments. - self.sid = sid - self.requester = requester - self.target = target - self.proxy = proxy - self.proxy_port = proxy_port - self.on_recv = on_recv - - def run(self): - """ Starts the thread. - """ - # Creates the socks5 proxy socket - self.s = socksocket() - self.s.setproxy(PROXY_TYPE_SOCKS5, self.proxy, port=self.proxy_port) + sock = socksocket() + sock.setproxy(PROXY_TYPE_SOCKS5, proxy, port=proxy_port) # The hostname MUST be SHA1(SID + Requester JID + Target JID) # where the output is hexadecimal-encoded (not binary). digest = sha1() - digest.update(self.sid) # SID - digest.update(self.requester) # Requester JID - digest.update(self.target) # Target JID + digest.update(sid) # SID + digest.update(requester) # Requester JID + digest.update(target) # Target JID # Computes the digest in hex. dest = '%s' % digest.hexdigest() # The port MUST be 0. - self.s.connect((dest, 0)) + sock.connect((dest, 0)) log.info('Socket connected.') - self.connected.set() - - # Blocks until the socket need to be closed. - self.listen() - - # Closes the socket. - self.s.close() - log.info('Socket closed.') - - def send(self, data): - """ Send data through the socket. - """ - - try: - packed_data = self._pack(data) - self.s.sendall(packed_data) - except pickle.PickleError as err: - log.error(err) - def _pack(self, data): - """ Packs the data. - """ - - # The data format is: `len_data`+`data`. Useful to receive all the data - # at once (avoid splitted data) thanks to the recv_size method. - data = pickle.dumps(data) - return struct.pack('>i', len(data)) + data - - def _unpack(self, data): - """ Unpacks the data. On error, log an error message and returns None. - """ - - try: - return pickle.loads(data) - except Exception as err: - log.error(err) - - def listen(self): - """ Listen for data on the socket. When receiving data, call - the callback on_recv callable. - """ + # Send the XMPP event. + self.xmpp.event('socks_connected', sid) - socket_open = True - while socket_open: - ins = [] - try: - # Wait any read available data on socket. Timeout - # after 5 secs. - ins, out, err = select([self.s, ], [], [], 5) - except socket.error as (errno, err): - # 9 means the socket is closed. It can be normal. Otherwise, - # log the error. - if errno != 9: - log.debug('Socket error: %s' % err) - break - except Exception as e: - log.debug(e) - break - - for s in ins: - data = self.recv_size(self.s) - if not data: - socket_open = False - else: - unpacked_data = self._unpack(data) - if unpacked_data: - self.on_recv(self.sid, unpacked_data) - - def recv_size(self, the_socket): - total_len = 0 - total_data = [] - size = sys.maxint - size_data = sock_data = '' - recv_size = 8192 - - while total_len < size: - sock_data = the_socket.recv(recv_size) - if not sock_data: - return ''.join(total_data) - - if not total_data: - if len(sock_data) > 4: - size_data += sock_data - size = struct.unpack('>i', size_data[:4])[0] - recv_size = size - if recv_size > 524288: - recv_size = 524288 - total_data.append(size_data[4:]) - else: - size_data += sock_data - else: - total_data.append(sock_data) - total_len = sum([len(i) for i in total_data]) - return ''.join(total_data) + return sock -- cgit v1.2.3 From fea444925e3dbb6a3933637e2011795aa0f49810 Mon Sep 17 00:00:00 2001 From: Oskari Timperi Date: Wed, 2 Jan 2013 16:53:16 +0200 Subject: util/sasl/mechanisms.py: SASLMutualAuthFailed not defined SASLMutualAuthFailed was not imported from sleekxmpp.util.sasl.client --- sleekxmpp/util/sasl/mechanisms.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/util/sasl/mechanisms.py b/sleekxmpp/util/sasl/mechanisms.py index 80cb7219..e137e263 100644 --- a/sleekxmpp/util/sasl/mechanisms.py +++ b/sleekxmpp/util/sasl/mechanisms.py @@ -21,7 +21,8 @@ from base64 import b64encode, b64decode from sleekxmpp.util import bytes, hash, XOR, quote, num_to_bytes from sleekxmpp.util.sasl.client import sasl_mech, Mech, \ - SASLCancelled, SASLFailed + SASLCancelled, SASLFailed, \ + SASLMutualAuthFailed @sasl_mech(0) -- cgit v1.2.3 From 9165cbf7f6839ee8ba2a4514b297f27fb019098a Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Wed, 23 Jan 2013 02:18:27 -0800 Subject: Cleanup and expand XEP-0065 plugin. --- sleekxmpp/plugins/xep_0065/__init__.py | 2 + sleekxmpp/plugins/xep_0065/proxy.py | 319 ++++++++++++++++----------------- sleekxmpp/plugins/xep_0065/stanza.py | 54 +++--- 3 files changed, 186 insertions(+), 189 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/xep_0065/__init__.py b/sleekxmpp/plugins/xep_0065/__init__.py index c577d859..feca2ef1 100644 --- a/sleekxmpp/plugins/xep_0065/__init__.py +++ b/sleekxmpp/plugins/xep_0065/__init__.py @@ -1,4 +1,6 @@ from sleekxmpp.plugins.base import register_plugin + +from sleekxmpp.plugins.xep_0065.stanza import Socks5 from sleekxmpp.plugins.xep_0065.proxy import XEP_0065 diff --git a/sleekxmpp/plugins/xep_0065/proxy.py b/sleekxmpp/plugins/xep_0065/proxy.py index dec37612..473dd033 100644 --- a/sleekxmpp/plugins/xep_0065/proxy.py +++ b/sleekxmpp/plugins/xep_0065/proxy.py @@ -1,210 +1,195 @@ import logging +import threading +import socket from hashlib import sha1 from uuid import uuid4 -from sleekxmpp.plugins.xep_0065 import stanza +from sleekxmpp.thirdparty.socks import socksocket, PROXY_TYPE_SOCKS5 -from sleekxmpp.plugins.base import base_plugin +from sleekxmpp.stanza import Iq +from sleekxmpp.exceptions import XMPPError +from sleekxmpp.xmlstream import register_stanza_plugin from sleekxmpp.xmlstream.handler import Callback from sleekxmpp.xmlstream.matcher import StanzaPath -from sleekxmpp.thirdparty.socks import socksocket, PROXY_TYPE_SOCKS5 +from sleekxmpp.plugins.base import base_plugin + +from sleekxmpp.plugins.xep_0065 import stanza, Socks5 + -# Registers the sleekxmpp logger log = logging.getLogger(__name__) class XEP_0065(base_plugin): - """ - XEP-0065 Socks5 Bytestreams - """ - description = "Socks5 Bytestreams" - dependencies = set(['xep_0030', ]) - xep = '0065' name = 'xep_0065' - - # A dict contains for each SID, the proxy thread currently - # running. - proxies = {} + description = "Socks5 Bytestreams" + dependencies = set(['xep_0030']) def plugin_init(self): - """ Initializes the xep_0065 plugin and all event callbacks. - """ + register_stanza_plugin(Iq, Socks5) - # Shortcuts to access to the xep_0030 plugin. - self.disco = self.xmpp['xep_0030'] + self._proxies = {} + self._sessions = {} + self._sessions_lock = threading.Lock() - # Handler for the streamhost stanza. - self.xmpp.registerHandler( + self.xmpp.register_handler( Callback('Socks5 Bytestreams', StanzaPath('iq@type=set/socks/streamhost'), self._handle_streamhost)) - # Handler for the streamhost-used stanza. - self.xmpp.registerHandler( - Callback('Socks5 Bytestreams', - StanzaPath('iq@type=result/socks/streamhost-used'), - self._handle_streamhost_used)) + def session_bind(self, jid): + self.xmpp['xep_0030'].add_feature(Socks5.namespace) - def get_socket(self, sid): - """ Returns the socket associated to the SID. - """ + def plugin_end(self): + self.xmpp.remove_handler('Socks5 Bytestreams') + self.xmpp.remove_handler('Socks5 Streamhost Used') + self.xmpp['xep_0030'].del_feature(feature=Socks5.namespace) - proxy = self.proxies.get(sid) - if proxy: - return proxy + def get_socket(self, sid): + """Returns the socket associated to the SID.""" + return self._sessions.get(sid, None) - def handshake(self, to, streamer=None): + def handshake(self, to, ifrom=None, timeout=None): """ Starts the handshake to establish the socks5 bytestreams connection. """ + if not self._proxies: + self._proxies = self.discover_proxies() - # Discovers the proxy. - self.streamer = streamer or self.discover_proxy() + sid = uuid4().hex - # Requester requests network address from the proxy. - streamhost = self.get_network_address(self.streamer) - self.proxy_host = streamhost['socks']['streamhost']['host'] - self.proxy_port = streamhost['socks']['streamhost']['port'] + used = self.request_stream(to, sid=sid, ifrom=ifrom, timeout=timeout) + proxy = used['socks']['streamhost_used']['jid'] - # Generates the SID for this new handshake. - sid = uuid4().hex + if proxy not in self._proxies: + log.warning('Received unknown SOCKS5 proxy: %s', proxy) + return + + with self._sessions_lock: + self._sessions[sid] = self._connect_proxy( + sid, + self.xmpp.boundjid, + to, + self._proxies[proxy][0], + self._proxies[proxy][1]) + + # Request that the proxy activate the session with the target. + self.activate(proxy, sid, to, timeout=timeout) + return self.get_socket(sid) - # Requester initiates S5B negotation with Target by sending + def request_stream(self, to, sid=None, ifrom=None, block=True, timeout=None, callback=None): + if sid is None: + sid = uuid4().hex + + # Requester initiates S5B negotiation with Target by sending # IQ-set that includes the JabberID and network address of # StreamHost as well as the StreamID (SID) of the proposed # bytestream. - iq = self.xmpp.Iq(sto=to, stype='set') + iq = self.xmpp.Iq() + iq['to'] = to + iq['from'] = ifrom + iq['type'] = 'set' iq['socks']['sid'] = sid - iq['socks']['streamhost']['jid'] = self.streamer - iq['socks']['streamhost']['host'] = self.proxy_host - iq['socks']['streamhost']['port'] = self.proxy_port - - # Sends the new IQ. - return iq.send() + for proxy, (host, port) in self._proxies.items(): + iq['socks'].add_streamhost(proxy, host, port) + return iq.send(block=block, timeout=timeout, callback=callback) - def discover_proxy(self): - """ Auto-discovers (using XEP 0030) the available bytestream - proxy on the XMPP server. + def discover_proxies(self, jid=None, ifrom=None, timeout=None): + """Auto-discover the JIDs of SOCKS5 proxies on an XMPP server.""" + if jid is None: + if self.xmpp.is_component: + jid = self.xmpp.server + else: + jid = self.xmpp.boundjid.server - Returns the JID of the proxy. - """ + discovered = set() - # Gets all disco items. - disco_items = self.disco.get_items(self.xmpp.server) + disco_items = self.xmpp['xep_0030'].get_items(jid, timeout=timeout) for item in disco_items['disco_items']['items']: - # For each items, gets the disco info. - disco_info = self.disco.get_info(item[0]) - - # Gets and verifies if the identity is a bytestream proxy. - identities = disco_info['disco_info']['identities'] - for identity in identities: - if identity[0] == 'proxy' and identity[1] == 'bytestreams': - # Returns when the first occurence is found. - return '%s' % disco_info['from'] - - def get_network_address(self, streamer): - """ Gets the streamhost information of the proxy. - - streamer : The jid of the proxy. - """ - - iq = self.xmpp.Iq(sto=streamer, stype='get') - iq['socks'] # Adds the query eleme to the iq. - - return iq.send() + try: + disco_info = self.xmpp['xep_0030'].get_info(item[0], timeout=timeout) + except XMPPError: + continue + else: + # Verify that the identity is a bytestream proxy. + identities = disco_info['disco_info']['identities'] + for identity in identities: + if identity[0] == 'proxy' and identity[1] == 'bytestreams': + discovered.add(disco_info['from']) + + for jid in discovered: + try: + addr = self.get_network_address(jid, ifrom=ifrom, timeout=timeout) + self._proxies[jid] = (addr['socks']['streamhost']['host'], + addr['socks']['streamhost']['port']) + except XMPPError: + continue + + return self._proxies + + def get_network_address(self, proxy, ifrom=None, block=True, timeout=None, callback=None): + """Get the network information of a proxy.""" + iq = self.xmpp.Iq(sto=proxy, stype='get', sfrom=ifrom) + iq.enable('socks') + return iq.send(block=block, timeout=timeout, callback=callback) def _handle_streamhost(self, iq): - """ Handles all streamhost stanzas. - """ - - # Registers the streamhost info. - self.streamer = iq['socks']['streamhost']['jid'] - self.proxy_host = iq['socks']['streamhost']['host'] - self.proxy_port = iq['socks']['streamhost']['port'] - - # Sets the SID, the requester and the target. - sid = iq['socks']['sid'] - requester = '%s' % iq['from'] - target = '%s' % self.xmpp.boundjid - - # Next the Target attempts to open a standard TCP socket on - # the network address of the Proxy. - self.proxy = self._connect_proxy(sid, requester, target, - self.proxy_host, self.proxy_port) - - # Registers the new proxy to the proxies dict. - self.proxies[sid] = self.proxy - - # Replies to the incoming iq with a streamhost-used stanza. - res_iq = iq.reply() - res_iq['socks']['sid'] = sid - res_iq['socks']['streamhost-used']['jid'] = self.streamer - - # Sends the IQ - return res_iq.send() - - def _handle_streamhost_used(self, iq): - """ Handles all streamhost-used stanzas. - """ - - # Sets the SID, the requester and the target. + """Handle incoming SOCKS5 session request.""" sid = iq['socks']['sid'] - requester = '%s' % self.xmpp.boundjid - target = '%s' % iq['from'] - - # The Requester will establish a connection to the SOCKS5 - # proxy in the same way the Target did. - self.proxy = self._connect_proxy(sid, requester, target, - self.proxy_host, self.proxy_port) - - # Registers the new thread in the proxy_thread dict. - self.proxies[sid] = self.proxy - - # Requester sends IQ-set to StreamHost requesting that StreamHost - # activate the bytestream associated with the StreamID. - self.activate(iq['socks']['sid'], target) - - def activate(self, sid, to): - """ IQ-set to StreamHost requesting that StreamHost activate - the bytestream associated with the StreamID. - """ - - # Creates the activate IQ. - act_iq = self.xmpp.Iq(sto=self.streamer, stype='set') - act_iq['socks']['sid'] = sid - act_iq['socks']['activate'] = to + if not sid: + raise XMPPError(etype='modify', condition='not-acceptable') + + streamhosts = iq['socks']['streamhosts'] + conn = None + used_streamhost = None + + for streamhost in streamhosts: + try: + conn = self._connect_proxy(sid, + iq['from'], + self.xmpp.boundjid, + streamhost['host'], + streamhost['port']) + used_streamhost = streamhost['jid'] + break + except socket.error: + continue + else: + raise XMPPError(etype='cancel', condition='item-not-found') + + iq.reply() + with self._sessions_lock: + self._sessions[sid] = conn + iq['socks']['sid'] = sid + iq['socks']['streamhost_used']['jid'] = used_streamhost + iq.send() - # Send the IQ. - act_iq.send() + def activate(self, proxy, sid, target, ifrom=None, block=True, timeout=None, callback=None): + """Activate the socks5 session that has been negotiated.""" + iq = self.xmpp.Iq(sto=proxy, stype='set', sfrom=ifrom) + iq['socks']['sid'] = sid + iq['socks']['activate'] = target + iq.send(block=block, timeout=timeout, callback=callback) def deactivate(self, sid): - """ Closes the Proxy thread associated to this SID. - """ - - proxy = self.proxies.get(sid) - if proxy: - proxy.s.close() - del self.proxies[sid] + """Closes the proxy socket associated with this SID.""" + sock = self._sessions.get(sid) + if sock: + try: + sock.close() + except socket.error: + pass + with self._sessions_lock: + del self._sessions[sid] def close(self): - """ Closes all Proxy threads. - """ - - for sid, proxy in self.proxies.items(): - proxy.close() - del self.proxies[sid] - - def send(self, sid, data): - """ Sends the data over the Proxy socket associated to the - SID. - """ - - proxy = self.get_socket(sid) - if proxy: - proxy.sendall(data) + """Closes all proxy sockets.""" + for sid, sock in self._sessions.items(): + sock.close() + with self._sessions_lock: + self._sessions = {} def _connect_proxy(self, sid, requester, target, proxy, proxy_port): """ Establishes a connection between the client and the server-side @@ -216,31 +201,35 @@ class XEP_0065(base_plugin): proxy_host : The hostname or the IP of the proxy. proxy_port : The port of the proxy. or """ - # Because the xep_0065 plugin uses the proxy_port as string, # the Proxy class accepts the proxy_port argument as a string # or an integer. Here, we force to use the port as an integer. proxy_port = int(proxy_port) - # Creates the socks5 proxy socket sock = socksocket() sock.setproxy(PROXY_TYPE_SOCKS5, proxy, port=proxy_port) # The hostname MUST be SHA1(SID + Requester JID + Target JID) # where the output is hexadecimal-encoded (not binary). digest = sha1() - digest.update(sid) # SID - digest.update(requester) # Requester JID - digest.update(target) # Target JID + digest.update(sid) + digest.update(str(requester)) + digest.update(str(target)) - # Computes the digest in hex. - dest = '%s' % digest.hexdigest() + dest = digest.hexdigest() # The port MUST be 0. sock.connect((dest, 0)) log.info('Socket connected.') - # Send the XMPP event. + _close = sock.close + def close(*args, **kwargs): + with self._sessions_lock: + if sid in self._sessions: + del self._sessions[sid] + _close() + sock.close = close + self.xmpp.event('socks_connected', sid) return sock diff --git a/sleekxmpp/plugins/xep_0065/stanza.py b/sleekxmpp/plugins/xep_0065/stanza.py index ae57aba8..e48bf1b5 100644 --- a/sleekxmpp/plugins/xep_0065/stanza.py +++ b/sleekxmpp/plugins/xep_0065/stanza.py @@ -1,41 +1,47 @@ -from sleekxmpp import Iq +from sleekxmpp.jid import JID from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin -# The protocol namespace defined in the Socks5Bytestream (0065) spec. -namespace = 'http://jabber.org/protocol/bytestreams' +class Socks5(ElementBase): + name = 'query' + namespace = 'http://jabber.org/protocol/bytestreams' + plugin_attrib = 'socks' + interfaces = set(['sid', 'activate']) + sub_interfaces = set(['activate']) + def add_streamhost(self, jid, host, port): + sh = StreamHost(parent=self) + sh['jid'] = jid + sh['host'] = host + sh['port'] = port -class StreamHost(ElementBase): - """ The streamhost xml element. - """ - namespace = namespace +class StreamHost(ElementBase): name = 'streamhost' + namespace = 'http://jabber.org/protocol/bytestreams' plugin_attrib = 'streamhost' - interfaces = set(('host', 'jid', 'port')) + plugin_multi_attrib = 'streamhosts' + interfaces = set(['host', 'jid', 'port']) + def set_jid(self, value): + return self._set_attr('jid', str(value)) -class StreamHostUsed(ElementBase): - """ The streamhost-used xml element. - """ + def get_jid(self): + return JID(self._get_attr('jid')) - namespace = namespace + +class StreamHostUsed(ElementBase): name = 'streamhost-used' - plugin_attrib = 'streamhost-used' - interfaces = set(('jid',)) + namespace = 'http://jabber.org/protocol/bytestreams' + plugin_attrib = 'streamhost_used' + interfaces = set(['jid']) + def set_jid(self, value): + return self._set_attr('jid', str(value)) -class Socks5(ElementBase): - """ The query xml element. - """ + def get_jid(self): + return JID(self._get_attr('jid')) - namespace = namespace - name = 'query' - plugin_attrib = 'socks' - interfaces = set(('sid', 'activate')) - sub_interfaces = set(('activate',)) -register_stanza_plugin(Iq, Socks5) -register_stanza_plugin(Socks5, StreamHost) +register_stanza_plugin(Socks5, StreamHost, iterable=True) register_stanza_plugin(Socks5, StreamHostUsed) -- cgit v1.2.3 From badd327360a0c33b5229edb733e38441e74774e8 Mon Sep 17 00:00:00 2001 From: roger Date: Sat, 9 Feb 2013 11:28:21 -0300 Subject: fixed gmail new mail notification check without the tid gmail ignores the query --- sleekxmpp/plugins/gmail/notifications.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/gmail/notifications.py b/sleekxmpp/plugins/gmail/notifications.py index dbc68162..53ad2f3f 100644 --- a/sleekxmpp/plugins/gmail/notifications.py +++ b/sleekxmpp/plugins/gmail/notifications.py @@ -46,6 +46,7 @@ class Gmail(BasePlugin): self._handle_new_mail)) self._last_result_time = None + self._last_result_tid = None def plugin_end(self): self.xmpp.remove_handler('Gmail New Mail') @@ -57,13 +58,19 @@ class Gmail(BasePlugin): def check(self, block=True, timeout=None, callback=None): last_time = self._last_result_time - self._last_result_time = str(int(time.time() * 1000)) - return self.search(newer=last_time, + last_tid = self._last_result_tid + data = self.search(newer_time=last_time, + newer_tid=last_tid, block=block, timeout=timeout, callback=callback) - def search(self, query=None, newer=None, block=True, + self._last_result_time = data["gmail_messages"]["result_time"] + if data["gmail_messages"]["threads"]: + self._last_result_tid = data["gmail_messages"]["threads"][0]["tid"] + return data + + def search(self, query=None, newer_time=None, newer_tid=None, block=True, timeout=None, callback=None): if not query: log.info('Gmail: Checking for new email') @@ -73,5 +80,6 @@ class Gmail(BasePlugin): iq['type'] = 'get' iq['to'] = self.xmpp.boundjid.bare iq['gmail']['search'] = query - iq['gmail']['newer_than_time'] = newer + iq['gmail']['newer_than_time'] = newer_time + iq['gmail']['newer_than_tid'] = newer_tid return iq.send(block=block, timeout=timeout, callback=callback) -- cgit v1.2.3 From 43132dab8525299bc9277a46a21f16ddb0ad3793 Mon Sep 17 00:00:00 2001 From: roger Date: Sat, 9 Feb 2013 12:31:13 -0300 Subject: fix not blocking gmail notification check --- sleekxmpp/plugins/gmail/notifications.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/gmail/notifications.py b/sleekxmpp/plugins/gmail/notifications.py index 53ad2f3f..9cc67f04 100644 --- a/sleekxmpp/plugins/gmail/notifications.py +++ b/sleekxmpp/plugins/gmail/notifications.py @@ -59,16 +59,20 @@ class Gmail(BasePlugin): def check(self, block=True, timeout=None, callback=None): last_time = self._last_result_time last_tid = self._last_result_tid - data = self.search(newer_time=last_time, + + def check_callback(data): + self._last_result_time = data["gmail_messages"]["result_time"] + if data["gmail_messages"]["threads"]: + self._last_result_tid = \ + data["gmail_messages"]["threads"][0]["tid"] + if callback: + callback(data) + + return self.search(newer_time=last_time, newer_tid=last_tid, block=block, timeout=timeout, - callback=callback) - - self._last_result_time = data["gmail_messages"]["result_time"] - if data["gmail_messages"]["threads"]: - self._last_result_tid = data["gmail_messages"]["threads"][0]["tid"] - return data + callback=check_callback) def search(self, query=None, newer_time=None, newer_tid=None, block=True, timeout=None, callback=None): -- cgit v1.2.3