From 40ef4a16b1de10333f0e4bf6f884309693681cbb Mon Sep 17 00:00:00 2001 From: Sandro Munda Date: Sun, 3 Jun 2012 19:56:18 +0200 Subject: Updated the .gitignore to add .ropeproject/ folder --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2c491a83..e7f6bd09 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ docs/_build/ .tox/ .coverage sleekxmpp.egg-info/ +.ropeproject/ -- cgit v1.2.3 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 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(-) 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(-) 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(-) 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(-) 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(-) 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(-) 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(-) 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(-) 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). --- LICENSE | 29 ++- sleekxmpp/plugins/xep_0065/proxy.py | 6 +- sleekxmpp/thirdparty/__init__.py | 2 +- sleekxmpp/thirdparty/socks.py | 382 ++++++++++++++++++++++++++++++++++++ 4 files changed, 413 insertions(+), 6 deletions(-) create mode 100644 sleekxmpp/thirdparty/socks.py diff --git a/LICENSE b/LICENSE index a868b337..8809b2d2 100644 --- a/LICENSE +++ b/LICENSE @@ -69,8 +69,8 @@ modification, are permitted provided that the following conditions are met: * 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. - * Neither the name of Red Innovation nor the names of its contributors - may be used to endorse or promote products derived from this software + * Neither the name of Red Innovation nor the names of its contributors + may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY RED INNOVATION ``AS IS'' AND ANY @@ -167,3 +167,28 @@ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 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 DAMAGE. + +socksipy: A Python SOCKS client module. +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +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. 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(-) 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(+) 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(-) 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(-) 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(+) 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(-) 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 cf9e89d0ae257b007842bfa980ee60996ea470af Mon Sep 17 00:00:00 2001 From: Sandro Munda Date: Sat, 9 Jun 2012 18:45:13 +0200 Subject: Added the xep_0065 plugin in the setup.py --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index de89021b..193e809b 100755 --- a/setup.py +++ b/setup.py @@ -67,6 +67,7 @@ packages = [ 'sleekxmpp', 'sleekxmpp/plugins/xep_0059', 'sleekxmpp/plugins/xep_0060', 'sleekxmpp/plugins/xep_0060/stanza', + 'sleekxmpp/plugins/xep_0065', 'sleekxmpp/plugins/xep_0066', 'sleekxmpp/plugins/xep_0077', 'sleekxmpp/plugins/xep_0078', -- 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(-) 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(-) 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(-) 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(+) 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(-) 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(-) 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(-) 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(-) 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 aab2682f9ac12a77dedc085dba8de635e4f6a393 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Fri, 15 Jun 2012 16:03:22 -0700 Subject: Add examples for using IBB. --- examples/ibb_transfer/ibb_receiver.py | 149 ++++++++++++++++++++++++++++++++++ examples/ibb_transfer/ibb_sender.py | 145 +++++++++++++++++++++++++++++++++ 2 files changed, 294 insertions(+) create mode 100755 examples/ibb_transfer/ibb_receiver.py create mode 100755 examples/ibb_transfer/ibb_sender.py diff --git a/examples/ibb_transfer/ibb_receiver.py b/examples/ibb_transfer/ibb_receiver.py new file mode 100755 index 00000000..b11acabf --- /dev/null +++ b/examples/ibb_transfer/ibb_receiver.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +import sys +import logging +import getpass +from optparse import OptionParser + +import sleekxmpp + +# Python versions before 3.0 do not use UTF-8 encoding +# by default. To ensure that Unicode is handled properly +# throughout SleekXMPP, we will set the default encoding +# ourselves to UTF-8. +if sys.version_info < (3, 0): + reload(sys) + sys.setdefaultencoding('utf8') +else: + raw_input = input + + +class IBBReceiver(sleekxmpp.ClientXMPP): + + """ + A basic example of creating and using an in-band bytestream. + """ + + def __init__(self, jid, password): + sleekxmpp.ClientXMPP.__init__(self, jid, password) + + self.register_plugin('xep_0030') # Service Discovery + self.register_plugin('xep_0047', { + 'accept_stream': self.accept_stream + }) # In-band Bytestreams + + # The session_start event will be triggered when + # the bot establishes its connection with the server + # and the XML streams are ready for use. We want to + # listen for this event so that we we can initialize + # our roster. + self.add_event_handler("session_start", self.start) + + self.add_event_handler("ibb_stream_start", self.stream_opened) + self.add_event_handler("ibb_stream_data", self.stream_data) + + def start(self, event): + """ + Process the session_start event. + + Typical actions for the session_start event are + requesting the roster and broadcasting an initial + presence stanza. + + Arguments: + event -- An empty dictionary. The session_start + event does not provide any additional + data. + """ + self.send_presence() + self.get_roster() + + def accept_stream(self, iq): + """ + Check that it is ok to accept a stream request. + + Controlling stream acceptance can be done via either: + - setting 'auto_accept' to False in the plugin + configuration. The default is True. + - setting 'accept_stream' to a function which accepts + an Iq stanza as its argument, like this one. + + The accept_stream function will be used if it exists, and the + auto_accept value will be used otherwise. + """ + return True + + def stream_opened(self, stream): + # NOTE: IBB streams are bi-directional, so the original sender is + # now the opened stream's receiver. + print('Stream opened: %s from ' % (stream.sid, stream.receiver)) + + # You could run a loop reading from the stream using stream.recv(), + # or use the ibb_stream_data event. + + def stream_data(self, event): + print(event['data']) + +if __name__ == '__main__': + # Setup the command line arguments. + optp = OptionParser() + + # Output verbosity options. + optp.add_option('-q', '--quiet', help='set logging to ERROR', + action='store_const', dest='loglevel', + const=logging.ERROR, default=logging.INFO) + optp.add_option('-d', '--debug', help='set logging to DEBUG', + action='store_const', dest='loglevel', + const=logging.DEBUG, default=logging.INFO) + optp.add_option('-v', '--verbose', help='set logging to COMM', + action='store_const', dest='loglevel', + const=5, default=logging.INFO) + + # JID and password options. + optp.add_option("-j", "--jid", dest="jid", + help="JID to use") + optp.add_option("-p", "--password", dest="password", + help="password to use") + + opts, args = optp.parse_args() + + # Setup logging. + logging.basicConfig(level=opts.loglevel, + format='%(levelname)-8s %(message)s') + + if opts.jid is None: + opts.jid = raw_input("Username: ") + if opts.password is None: + opts.password = getpass.getpass("Password: ") + + xmpp = IBBReceiver(opts.jid, opts.password) + + # If you are working with an OpenFire server, you may need + # to adjust the SSL version used: + # xmpp.ssl_version = ssl.PROTOCOL_SSLv3 + + # If you want to verify the SSL certificates offered by a server: + # xmpp.ca_certs = "path/to/ca/cert" + + # Connect to the XMPP server and start processing XMPP stanzas. + if xmpp.connect(): + # If you do not have the dnspython library installed, you will need + # to manually specify the name of the server if it does not match + # the one in the JID. For example, to use Google Talk you would + # need to use: + # + # if xmpp.connect(('talk.google.com', 5222)): + # ... + xmpp.process(block=True) + print("Done") + else: + print("Unable to connect.") diff --git a/examples/ibb_transfer/ibb_sender.py b/examples/ibb_transfer/ibb_sender.py new file mode 100755 index 00000000..cd856378 --- /dev/null +++ b/examples/ibb_transfer/ibb_sender.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +import sys +import logging +import getpass +from optparse import OptionParser + +import sleekxmpp + +# Python versions before 3.0 do not use UTF-8 encoding +# by default. To ensure that Unicode is handled properly +# throughout SleekXMPP, we will set the default encoding +# ourselves to UTF-8. +if sys.version_info < (3, 0): + reload(sys) + sys.setdefaultencoding('utf8') +else: + raw_input = input + + +class IBBSender(sleekxmpp.ClientXMPP): + + """ + A basic example of creating and using an in-band bytestream. + """ + + def __init__(self, jid, password, receiver, filename): + sleekxmpp.ClientXMPP.__init__(self, jid, password) + + self.receiver = receiver + self.filename = filename + + # The session_start event will be triggered when + # the bot establishes its connection with the server + # and the XML streams are ready for use. We want to + # listen for this event so that we we can initialize + # our roster. + self.add_event_handler("session_start", self.start) + + def start(self, event): + """ + Process the session_start event. + + Typical actions for the session_start event are + requesting the roster and broadcasting an initial + presence stanza. + + Arguments: + event -- An empty dictionary. The session_start + event does not provide any additional + data. + """ + self.send_presence() + self.get_roster() + + # For the purpose of demonstration, we'll set a very small block + # size. The default block size is 4096. We'll also use a window + # allowing sending multiple blocks at a time; in this case, three + # block transfers may be in progress at any time. + stream = self['xep_0047'].open_stream(self.receiver) + + with open(self.filename) as f: + data = f.read() + stream.sendall(data) + + +if __name__ == '__main__': + # Setup the command line arguments. + optp = OptionParser() + + # Output verbosity options. + optp.add_option('-q', '--quiet', help='set logging to ERROR', + action='store_const', dest='loglevel', + const=logging.ERROR, default=logging.INFO) + optp.add_option('-d', '--debug', help='set logging to DEBUG', + action='store_const', dest='loglevel', + const=logging.DEBUG, default=logging.INFO) + optp.add_option('-v', '--verbose', help='set logging to COMM', + action='store_const', dest='loglevel', + const=5, default=logging.INFO) + + # JID and password options. + optp.add_option("-j", "--jid", dest="jid", + help="JID to use") + optp.add_option("-p", "--password", dest="password", + help="password to use") + optp.add_option("-r", "--receiver", dest="receiver", + help="JID to use") + optp.add_option("-f", "--file", dest="filename", + help="JID to use") + + opts, args = optp.parse_args() + + # Setup logging. + logging.basicConfig(level=opts.loglevel, + format='%(levelname)-8s %(message)s') + + if opts.jid is None: + opts.jid = raw_input("Username: ") + if opts.password is None: + opts.password = getpass.getpass("Password: ") + if opts.receiver is None: + opts.receiver = raw_input("Receiver: ") + if opts.filename is None: + opts.filename = raw_input("File path: ") + + # Setup the EchoBot and register plugins. Note that while plugins may + # have interdependencies, the order in which you register them does + # not matter. + xmpp = IBBSender(opts.jid, opts.password, opts.receiver, opts.filename) + xmpp.register_plugin('xep_0030') # Service Discovery + xmpp.register_plugin('xep_0004') # Data Forms + xmpp.register_plugin('xep_0047') # In-band Bytestreams + xmpp.register_plugin('xep_0060') # PubSub + xmpp.register_plugin('xep_0199') # XMPP Ping + + # If you are working with an OpenFire server, you may need + # to adjust the SSL version used: + # xmpp.ssl_version = ssl.PROTOCOL_SSLv3 + + # If you want to verify the SSL certificates offered by a server: + # xmpp.ca_certs = "path/to/ca/cert" + + # Connect to the XMPP server and start processing XMPP stanzas. + if xmpp.connect(): + # If you do not have the dnspython library installed, you will need + # to manually specify the name of the server if it does not match + # the one in the JID. For example, to use Google Talk you would + # need to use: + # + # if xmpp.connect(('talk.google.com', 5222)): + # ... + xmpp.process(block=True) + print("Done") + else: + print("Unable to connect.") -- cgit v1.2.3 From 1ab66e576786ecb0cfb9b6b163811735564b951b Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Fri, 15 Jun 2012 16:03:38 -0700 Subject: Add example for dealing with GTalk custom domain certificates. --- examples/gtalk_custom_domain.py | 165 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100755 examples/gtalk_custom_domain.py diff --git a/examples/gtalk_custom_domain.py b/examples/gtalk_custom_domain.py new file mode 100755 index 00000000..0226c146 --- /dev/null +++ b/examples/gtalk_custom_domain.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +import sys +import logging +import getpass +from optparse import OptionParser + +import sleekxmpp + +import ssl +from sleekxmpp.xmlstream import cert + + +# Python versions before 3.0 do not use UTF-8 encoding +# by default. To ensure that Unicode is handled properly +# throughout SleekXMPP, we will set the default encoding +# ourselves to UTF-8. +if sys.version_info < (3, 0): + reload(sys) + sys.setdefaultencoding('utf8') +else: + raw_input = input + + +class GTalkBot(sleekxmpp.ClientXMPP): + + """ + A demonstration of using SleekXMPP with accounts from a Google Apps + account with a custom domain, because it requires custom certificate + validation. + """ + + def __init__(self, jid, password): + sleekxmpp.ClientXMPP.__init__(self, jid, password) + + # The session_start event will be triggered when + # the bot establishes its connection with the server + # and the XML streams are ready for use. We want to + # listen for this event so that we we can initialize + # our roster. + self.add_event_handler("session_start", self.start) + + # The message event is triggered whenever a message + # stanza is received. Be aware that that includes + # MUC messages and error messages. + self.add_event_handler("message", self.message) + + # Using a Google Apps custom domain, the certificate + # does not contain the custom domain, just the GTalk + # server name. So we will need to process invalid + # certifcates ourselves and check that it really + # is from Google. + self.add_event_handler("ssl_invalid_cert", self.invalid_cert) + + def invalid_cert(self, pem_cert): + der_cert = ssl.PEM_cert_to_DER_cert(pem_cert) + try: + cert.verify('talk.google.com', der_cert) + logging.debug("CERT: Found GTalk certificate") + except cert.CertificateError as err: + log.error(err.message) + self.disconnect(send_close=False) + + def start(self, event): + """ + Process the session_start event. + + Typical actions for the session_start event are + requesting the roster and broadcasting an initial + presence stanza. + + Arguments: + event -- An empty dictionary. The session_start + event does not provide any additional + data. + """ + self.send_presence() + self.get_roster() + + def message(self, msg): + """ + Process incoming message stanzas. Be aware that this also + includes MUC messages and error messages. It is usually + a good idea to check the messages's type before processing + or sending replies. + + Arguments: + msg -- The received message stanza. See the documentation + for stanza objects and the Message stanza to see + how it may be used. + """ + if msg['type'] in ('chat', 'normal'): + msg.reply("Thanks for sending\n%(body)s" % msg).send() + + +if __name__ == '__main__': + # Setup the command line arguments. + optp = OptionParser() + + # Output verbosity options. + optp.add_option('-q', '--quiet', help='set logging to ERROR', + action='store_const', dest='loglevel', + const=logging.ERROR, default=logging.INFO) + optp.add_option('-d', '--debug', help='set logging to DEBUG', + action='store_const', dest='loglevel', + const=logging.DEBUG, default=logging.INFO) + optp.add_option('-v', '--verbose', help='set logging to COMM', + action='store_const', dest='loglevel', + const=5, default=logging.INFO) + + # JID and password options. + optp.add_option("-j", "--jid", dest="jid", + help="JID to use") + optp.add_option("-p", "--password", dest="password", + help="password to use") + + opts, args = optp.parse_args() + + # Setup logging. + logging.basicConfig(level=opts.loglevel, + format='%(levelname)-8s %(message)s') + + if opts.jid is None: + opts.jid = raw_input("Username: ") + if opts.password is None: + opts.password = getpass.getpass("Password: ") + + # Setup the GTalkBot and register plugins. Note that while plugins may + # have interdependencies, the order in which you register them does + # not matter. + xmpp = GTalkBot(opts.jid, opts.password) + xmpp.register_plugin('xep_0030') # Service Discovery + xmpp.register_plugin('xep_0004') # Data Forms + xmpp.register_plugin('xep_0060') # PubSub + xmpp.register_plugin('xep_0199') # XMPP Ping + + # If you are working with an OpenFire server, you may need + # to adjust the SSL version used: + # xmpp.ssl_version = ssl.PROTOCOL_SSLv3 + + # If you want to verify the SSL certificates offered by a server: + # xmpp.ca_certs = "path/to/ca/cert" + + # Connect to the XMPP server and start processing XMPP stanzas. + if xmpp.connect(): + # If you do not have the dnspython library installed, you will need + # to manually specify the name of the server if it does not match + # the one in the JID. For example, to use Google Talk you would + # need to use: + # + # if xmpp.connect(('talk.google.com', 5222)): + # ... + xmpp.process(block=True) + print("Done") + else: + print("Unable to connect.") -- 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(-) 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(-) 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(-) 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(-) 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 --- README.rst | 2 +- sleekxmpp/version.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 31bd7c7b..f4fb0a22 100644 --- a/README.rst +++ b/README.rst @@ -45,7 +45,7 @@ The latest source code for SleekXMPP may be found on `Github ``develop`` branch. **Latest Release** - - `1.1.10 `_ + - `1.1.11 `_ **Develop Releases** - `Latest Develop Version `_ 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. --- .gitignore | 5 ++++- sleekxmpp/plugins/xep_0065/proxy.py | 37 ++++++++++++++++++++++++++++++++++--- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index e7f6bd09..9a90daeb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -*.pyc +*.py[co] build/ dist/ MANIFEST @@ -8,3 +8,6 @@ docs/_build/ .coverage sleekxmpp.egg-info/ .ropeproject/ +4913 +*~ +.baboon/ 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(-) 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(-) 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(-) 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(-) 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(-) 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(-) 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