diff options
author | Yann Leboulanger <asterix@lagaule.org> | 2012-12-10 00:37:51 +0400 |
---|---|---|
committer | Yann Leboulanger <asterix@lagaule.org> | 2012-12-10 00:37:51 +0400 |
commit | 77775cf0afe79219a263e3c621cbeaa43e75d44a (patch) | |
tree | 83d5cc41c568368517f5a8552ef52c0f80bcff80 /src/common/xmpp | |
parent | 0c6117a778b854b18757ce5f81a77402131f28da (diff) |
Gajim now uses python-nbxmpp library instead of embedding it.
Diffstat (limited to 'src/common/xmpp')
-rw-r--r-- | src/common/xmpp/__init__.py | 18 | ||||
-rw-r--r-- | src/common/xmpp/auth_nb.py | 724 | ||||
-rw-r--r-- | src/common/xmpp/bosh.py | 581 | ||||
-rw-r--r-- | src/common/xmpp/c14n.py | 59 | ||||
-rw-r--r-- | src/common/xmpp/client_nb.py | 629 | ||||
-rw-r--r-- | src/common/xmpp/dispatcher_nb.py | 639 | ||||
-rw-r--r-- | src/common/xmpp/features_nb.py | 218 | ||||
-rw-r--r-- | src/common/xmpp/idlequeue.py | 550 | ||||
-rw-r--r-- | src/common/xmpp/plugin.py | 96 | ||||
-rw-r--r-- | src/common/xmpp/protocol.py | 1489 | ||||
-rw-r--r-- | src/common/xmpp/proxy_connectors.py | 238 | ||||
-rw-r--r-- | src/common/xmpp/roster_nb.py | 368 | ||||
-rw-r--r-- | src/common/xmpp/simplexml.py | 692 | ||||
-rw-r--r-- | src/common/xmpp/smacks.py | 132 | ||||
-rw-r--r-- | src/common/xmpp/stringprepare.py | 238 | ||||
-rw-r--r-- | src/common/xmpp/tls_nb.py | 461 | ||||
-rw-r--r-- | src/common/xmpp/transports_nb.py | 786 |
17 files changed, 0 insertions, 7918 deletions
diff --git a/src/common/xmpp/__init__.py b/src/common/xmpp/__init__.py deleted file mode 100644 index 46037a1cb..000000000 --- a/src/common/xmpp/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -# $Id: __init__.py,v 1.9 2005/03/07 09:34:51 snakeru Exp $ - -""" -Gajim maintains a fork of the xmpppy jabber python library. Most of the code is -inherited but has been extended by implementation of non-blocking transports -and new features like BOSH. - -Most of the xmpp classes are ancestors of PlugIn class to share a single set of methods in order to compile a featured and extensible XMPP client. - -Thanks and credits to the xmpppy developers. See: http://xmpppy.sourceforge.net/ -""" - -from protocol import * -import simplexml, protocol, auth_nb, transports_nb, roster_nb -import dispatcher_nb, features_nb, idlequeue, bosh, tls_nb, proxy_connectors -from client_nb import NonBlockingClient -from plugin import PlugIn -from smacks import Smacks diff --git a/src/common/xmpp/auth_nb.py b/src/common/xmpp/auth_nb.py deleted file mode 100644 index 10d65abed..000000000 --- a/src/common/xmpp/auth_nb.py +++ /dev/null @@ -1,724 +0,0 @@ -## auth_nb.py -## based on auth.py, changes backported up to revision 1.41 -## -## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov -## modified by Dimitur Kirov <dkirov@gmail.com> -## -## This program is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation; either version 2, or (at your option) -## any later version. -## -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. - -""" -Provides plugs for SASL and NON-SASL authentication mechanisms. -Can be used both for client and transport authentication - -See client_nb.py -""" - -from protocol import NS_SASL, NS_SESSION, NS_STREAMS, NS_BIND, NS_AUTH -from protocol import NS_STREAM_MGMT -from protocol import Node, NodeProcessed, isResultNode, Iq, Protocol, JID -from plugin import PlugIn -from smacks import Smacks -import base64 -import random -import itertools -import dispatcher_nb -import hashlib -import hmac -import hashlib - -import logging -log = logging.getLogger('gajim.c.x.auth_nb') - -def HH(some): return hashlib.md5(some).hexdigest() -def H(some): return hashlib.md5(some).digest() -def C(some): return ':'.join(some) - -try: - kerberos = __import__('kerberos') - have_kerberos = True -except ImportError: - have_kerberos = False - -GSS_STATE_STEP = 0 -GSS_STATE_WRAP = 1 -SASL_FAILURE_IN_PROGRESS = 'failure-in-process' -SASL_FAILURE = 'failure' -SASL_SUCCESS = 'success' -SASL_UNSUPPORTED = 'not-supported' -SASL_IN_PROCESS = 'in-process' - -def challenge_splitter(data): - """ - Helper function that creates a dict from challenge string - - Sample challenge string: - username="example.org",realm="somerealm",\ - nonce="OA6MG9tEQGm2hh",cnonce="OA6MHXh6VqTrRk",\ - nc=00000001,qop="auth,auth-int,auth-conf",charset=utf-8 - - Expected result for challan: - dict['qop'] = ('auth','auth-int','auth-conf') - dict['realm'] = 'somerealm' - """ - X_KEYWORD, X_VALUE, X_END = 0, 1, 2 - quotes_open = False - keyword, value = '', '' - dict_ = {} - arr = None - - expecting = X_KEYWORD - for iter_ in range(len(data) + 1): - end = False - if iter_ == len(data): - expecting = X_END - end = True - else: - char = data[iter_] - if expecting == X_KEYWORD: - if char == '=': - expecting = X_VALUE - elif char in (',', ' ', '\t'): - pass - else: - keyword = '%s%c' % (keyword, char) - elif expecting == X_VALUE: - if char == '"': - if quotes_open: - end = True - else: - quotes_open = True - elif char in (',', ' ', '\t'): - if quotes_open: - if not arr: - arr = [value] - else: - arr.append(value) - value = "" - else: - end = True - else: - value = '%s%c' % (value, char) - if end: - if arr: - arr.append(value) - dict_[keyword] = arr - arr = None - else: - dict_[keyword] = value - value, keyword = '', '' - expecting = X_KEYWORD - quotes_open = False - return dict_ - -def scram_parse(chatter): - return dict(s.split('=', 1) for s in chatter.split(',')) - -class SASL(PlugIn): - """ - Implements SASL authentication. Can be plugged into NonBlockingClient - to start authentication - """ - - def __init__(self, username, password, on_sasl): - """ - :param user: XMPP username - :param password: XMPP password - :param on_sasl: Callback, will be called after each SASL auth-step. - """ - PlugIn.__init__(self) - self.username = username - self.password = password - self.on_sasl = on_sasl - self.realm = None - - def plugin(self, owner): - if 'version' not in self._owner.Dispatcher.Stream._document_attrs: - self.startsasl = SASL_UNSUPPORTED - elif self._owner.Dispatcher.Stream.features: - try: - self.FeaturesHandler(self._owner.Dispatcher, - self._owner.Dispatcher.Stream.features) - except NodeProcessed: - pass - else: - self.startsasl = None - - def plugout(self): - """ - Remove SASL handlers from owner's dispatcher. Used internally - """ - if 'features' in self._owner.__dict__: - self._owner.UnregisterHandler('features', self.FeaturesHandler, - xmlns=NS_STREAMS) - if 'challenge' in self._owner.__dict__: - self._owner.UnregisterHandler('challenge', self.SASLHandler, - xmlns=NS_SASL) - if 'failure' in self._owner.__dict__: - self._owner.UnregisterHandler('failure', self.SASLHandler, - xmlns=NS_SASL) - if 'success' in self._owner.__dict__: - self._owner.UnregisterHandler('success', self.SASLHandler, - xmlns=NS_SASL) - - def auth(self): - """ - Start authentication. Result can be obtained via "SASL.startsasl" - attribute and will be either SASL_SUCCESS or SASL_FAILURE - - Note that successfull auth will take at least two Dispatcher.Process() - calls. - """ - if self.startsasl: - pass - elif self._owner.Dispatcher.Stream.features: - try: - self.FeaturesHandler(self._owner.Dispatcher, - self._owner.Dispatcher.Stream.features) - except NodeProcessed: - pass - else: - self._owner.RegisterHandler('features', - self.FeaturesHandler, xmlns=NS_STREAMS) - - def FeaturesHandler(self, conn, feats): - """ - Used to determine if server supports SASL auth. Used internally - """ - if not feats.getTag('mechanisms', namespace=NS_SASL): - self.startsasl='not-supported' - log.info('SASL not supported by server') - return - self.mecs = [] - for mec in feats.getTag('mechanisms', namespace=NS_SASL).getTags( - 'mechanism'): - self.mecs.append(mec.getData()) - - self._owner.RegisterHandler('challenge', self.SASLHandler, - xmlns=NS_SASL) - self._owner.RegisterHandler('failure', self.SASLHandler, xmlns=NS_SASL) - self._owner.RegisterHandler('success', self.SASLHandler, xmlns=NS_SASL) - self.MechanismHandler() - - def MechanismHandler(self): - if 'ANONYMOUS' in self.mecs and self.username is None: - self.mecs.remove('ANONYMOUS') - node = Node('auth', attrs={'xmlns': NS_SASL, - 'mechanism': 'ANONYMOUS'}) - self.mechanism = 'ANONYMOUS' - self.startsasl = SASL_IN_PROCESS - self._owner.send(str(node)) - raise NodeProcessed - if "EXTERNAL" in self.mecs: - self.mecs.remove('EXTERNAL') - sasl_data = u'%s@%s' % (self.username, self._owner.Server) - sasl_data = sasl_data.encode('utf-8').encode('base64').replace( - '\n', '') - node = Node('auth', attrs={'xmlns': NS_SASL, - 'mechanism': 'EXTERNAL'}, payload=[sasl_data]) - self.mechanism = 'EXTERNAL' - self.startsasl = SASL_IN_PROCESS - self._owner.send(str(node)) - raise NodeProcessed - if 'GSSAPI' in self.mecs and have_kerberos: - self.mecs.remove('GSSAPI') - try: - self.gss_vc = kerberos.authGSSClientInit('xmpp@' + \ - self._owner.xmpp_hostname)[1] - kerberos.authGSSClientStep(self.gss_vc, '') - response = kerberos.authGSSClientResponse(self.gss_vc) - node=Node('auth', attrs={'xmlns': NS_SASL, - 'mechanism': 'GSSAPI'}, payload=(response or '')) - self.mechanism = 'GSSAPI' - self.gss_step = GSS_STATE_STEP - self.startsasl = SASL_IN_PROCESS - self._owner.send(str(node)) - raise NodeProcessed - except kerberos.GSSError, e: - log.info('GSSAPI authentication failed: %s' % str(e)) - if 'SCRAM-SHA-1' in self.mecs: - self.mecs.remove('SCRAM-SHA-1') - self.mechanism = 'SCRAM-SHA-1' - self._owner._caller.get_password(self.set_password, self.mechanism) - self.scram_step = 0 - self.startsasl = SASL_IN_PROCESS - raise NodeProcessed - if 'DIGEST-MD5' in self.mecs: - self.mecs.remove('DIGEST-MD5') - node = Node('auth', attrs={'xmlns': NS_SASL, - 'mechanism': 'DIGEST-MD5'}) - self.mechanism = 'DIGEST-MD5' - self.startsasl = SASL_IN_PROCESS - self._owner.send(str(node)) - raise NodeProcessed - if 'PLAIN' in self.mecs: - self.mecs.remove('PLAIN') - self.mechanism = 'PLAIN' - self._owner._caller.get_password(self.set_password, self.mechanism) - self.startsasl = SASL_IN_PROCESS - raise NodeProcessed - if 'X-MESSENGER-OAUTH2' in self.mecs: - self.mecs.remove('X-MESSENGER-OAUTH2') - self.mechanism = 'X-MESSENGER-OAUTH2' - self._owner._caller.get_password(self.set_password, self.mechanism) - self.startsasl = SASL_IN_PROCESS - raise NodeProcessed - self.startsasl = SASL_FAILURE - log.info('I can only use EXTERNAL, SCRAM-SHA-1, DIGEST-MD5, GSSAPI and ' - 'PLAIN mecanisms.') - if self.on_sasl: - self.on_sasl() - return - - def SASLHandler(self, conn, challenge): - """ - Perform next SASL auth step. Used internally - """ - if challenge.getNamespace() != NS_SASL: - return - - def scram_base64(s): - return ''.join(s.encode('base64').split('\n')) - - incoming_data = challenge.getData() - data=base64.decodestring(incoming_data) - ### Handle Auth result - def on_auth_fail(reason): - log.info('Failed SASL authentification: %s' % reason) - self._owner.send(str(Node('abort', attrs={'xmlns': NS_SASL}))) - if len(self.mecs) > 0: - # There are other mechanisms to test, but wait for <failure> - # answer from server - self.startsasl = SASL_FAILURE_IN_PROGRESS - raise NodeProcessed - if self.on_sasl: - self.on_sasl() - raise NodeProcessed - - if challenge.getName() == 'failure': - if self.startsasl == SASL_FAILURE_IN_PROGRESS: - self.MechanismHandler() - raise NodeProcessed - self.startsasl = SASL_FAILURE - try: - reason = challenge.getChildren()[0] - except Exception: - reason = challenge - on_auth_fail(reason) - elif challenge.getName() == 'success': - if self.mechanism == 'SCRAM-SHA-1': - # check data-with-success - data = scram_parse(data) - if data['v'] != scram_base64(self.scram_ServerSignature): - on_auth_fail('ServerSignature is wrong') - - self.startsasl = SASL_SUCCESS - log.info('Successfully authenticated with remote server.') - handlers = self._owner.Dispatcher.dumpHandlers() - - # Bosh specific dispatcher replugging - # save old features. They will be used in case we won't get response - # on stream restart after SASL auth (happens with XMPP over BOSH - # with Openfire) - old_features = self._owner.Dispatcher.Stream.features - self._owner.Dispatcher.PlugOut() - dispatcher_nb.Dispatcher.get_instance().PlugIn(self._owner, - after_SASL=True, old_features=old_features) - self._owner.Dispatcher.restoreHandlers(handlers) - self._owner.User = self.username - - if self.on_sasl: - self.on_sasl() - raise NodeProcessed - - ### Perform auth step - log.info('Got challenge:' + data) - - if self.mechanism == 'GSSAPI': - if self.gss_step == GSS_STATE_STEP: - rc = kerberos.authGSSClientStep(self.gss_vc, incoming_data) - if rc != kerberos.AUTH_GSS_CONTINUE: - self.gss_step = GSS_STATE_WRAP - elif self.gss_step == GSS_STATE_WRAP: - rc = kerberos.authGSSClientUnwrap(self.gss_vc, incoming_data) - response = kerberos.authGSSClientResponse(self.gss_vc) - rc = kerberos.authGSSClientWrap(self.gss_vc, response, - kerberos.authGSSClientUserName(self.gss_vc)) - response = kerberos.authGSSClientResponse(self.gss_vc) - if not response: - response = '' - self._owner.send(Node('response', attrs={'xmlns': NS_SASL}, - payload=response).__str__()) - raise NodeProcessed - if self.mechanism == 'SCRAM-SHA-1': - hashfn = hashlib.sha1 - - def HMAC(k, s): - return hmac.HMAC(key=k, msg=s, digestmod=hashfn).digest() - - def XOR(x, y): - r = (chr(ord(px) ^ ord(py)) for px, py in zip(x, y)) - return ''.join(r) - - def Hi(s, salt, iters): - ii = 1 - try: - s = s.encode('utf-8') - except: - pass - ui_1 = HMAC(s, salt + '\0\0\0\01') - ui = ui_1 - for i in range(iters - 1): - ii += 1 - ui_1 = HMAC(s, ui_1) - ui = XOR(ui, ui_1) - return ui - - def scram_H(s): - return hashfn(s).digest() - - if self.scram_step == 0: - self.scram_step = 1 - self.scram_soup += ',' + data + ',' - data = scram_parse(data) - # TODO: Should check cnonce here. - # TODO: Channel binding data goes in here too. - r = 'c=' + scram_base64(self.scram_gs2) - r += ',r=' + data['r'] - self.scram_soup += r - salt = data['s'].decode('base64') - iter = int(data['i']) - SaltedPassword = Hi(self.password, salt, iter) - # TODO: Could cache this, along with salt+iter. - ClientKey = HMAC(SaltedPassword, 'Client Key') - StoredKey = scram_H(ClientKey) - ClientSignature = HMAC(StoredKey, self.scram_soup) - ClientProof = XOR(ClientKey, ClientSignature) - r += ',p=' + scram_base64(ClientProof) - ServerKey = HMAC(SaltedPassword, 'Server Key') - self.scram_ServerSignature = HMAC(ServerKey, self.scram_soup) - sasl_data = scram_base64(r) - node = Node('response', attrs={'xmlns': NS_SASL}, - payload=[sasl_data]) - self._owner.send(str(node)) - raise NodeProcessed - - if self.scram_step == 1: - data = scram_parse(data) - if data['v'].decode('base64') != self.scram_ServerSignature: - # TODO: Not clear what to do here - need to abort. - raise Exception - node = Node('response', attrs={'xmlns': NS_SASL}); - self._owner.send(str(node)) - raise NodeProcessed - - # magic foo... - chal = challenge_splitter(data) - if not self.realm and 'realm' in chal: - self.realm = chal['realm'] - if 'qop' in chal and ((isinstance(chal['qop'], str) and \ - chal['qop'] =='auth') or (isinstance(chal['qop'], list) and 'auth' in \ - chal['qop'])): - self.resp = {} - self.resp['username'] = self.username - if self.realm: - self.resp['realm'] = self.realm - else: - self.resp['realm'] = self._owner.Server - self.resp['nonce'] = chal['nonce'] - self.resp['cnonce'] = ''.join("%x" % randint(0, 2**28) for randint \ - in itertools.repeat(random.randint, 7)) - self.resp['nc'] = ('00000001') - self.resp['qop'] = 'auth' - self.resp['digest-uri'] = 'xmpp/' + self._owner.Server - self.resp['charset'] = 'utf-8' - # Password is now required - self._owner._caller.get_password(self.set_password, self.mechanism) - elif 'rspauth' in chal: - # Check rspauth value - if chal['rspauth'] != self.digest_rspauth: - on_auth_fail('rspauth is wrong') - self._owner.send(str(Node('response', attrs={'xmlns':NS_SASL}))) - else: - self.startsasl = SASL_FAILURE - log.info('Failed SASL authentification: unknown challenge') - if self.on_sasl: - self.on_sasl() - raise NodeProcessed - - @staticmethod - def _convert_to_iso88591(string): - try: - string = string.decode('utf-8').encode('iso-8859-1') - except UnicodeEncodeError: - pass - return string - - def set_password(self, password): - self.password = '' if password is None else password - if self.mechanism == 'SCRAM-SHA-1': - nonce = ''.join('%x' % randint(0, 2 ** 28) for randint in \ - itertools.repeat(random.randint, 7)) - self.scram_soup = 'n=' + self.username + ',r=' + nonce - self.scram_gs2 = 'n,,' # No CB yet. - sasl_data = (self.scram_gs2 + self.scram_soup).encode('base64').\ - replace('\n', '') - node = Node('auth', attrs={'xmlns': NS_SASL, - 'mechanism': self.mechanism}, payload=[sasl_data]) - elif self.mechanism == 'DIGEST-MD5': - hash_username = self._convert_to_iso88591(self.resp['username']) - hash_realm = self._convert_to_iso88591(self.resp['realm']) - hash_password = self._convert_to_iso88591(self.password) - A1 = C([H(C([hash_username, hash_realm, hash_password])), - self.resp['nonce'], self.resp['cnonce']]) - A2 = C(['AUTHENTICATE', self.resp['digest-uri']]) - response = HH(C([HH(A1), self.resp['nonce'], self.resp['nc'], - self.resp['cnonce'], self.resp['qop'], HH(A2)])) - A2 = C(['', self.resp['digest-uri']]) - self.digest_rspauth = HH(C([HH(A1), self.resp['nonce'], - self.resp['nc'], self.resp['cnonce'], self.resp['qop'], - HH(A2)])) - self.resp['response'] = response - sasl_data = u'' - for key in ('charset', 'username', 'realm', 'nonce', 'nc', 'cnonce', - 'digest-uri', 'response', 'qop'): - if key in ('nc', 'qop', 'response', 'charset'): - sasl_data += u"%s=%s," % (key, self.resp[key]) - else: - sasl_data += u'%s="%s",' % (key, self.resp[key]) - sasl_data = sasl_data[:-1].encode('utf-8').encode('base64').replace( - '\r', '').replace('\n', '') - node = Node('response', attrs={'xmlns': NS_SASL}, - payload=[sasl_data]) - elif self.mechanism == 'PLAIN': - sasl_data = u'\x00%s\x00%s' % (self.username, self.password) - sasl_data = sasl_data.encode('utf-8').encode('base64').replace( - '\n', '') - node = Node('auth', attrs={'xmlns': NS_SASL, 'mechanism': 'PLAIN'}, - payload=[sasl_data]) - elif self.mechanism == 'X-MESSENGER-OAUTH2': - node = Node('auth', attrs={'xmlns': NS_SASL, - 'mechanism': 'X-MESSENGER-OAUTH2'}) - node.addData(password) - self._owner.send(str(node)) - - -class NonBlockingNonSASL(PlugIn): - """ - Implements old Non-SASL (JEP-0078) authentication used in jabberd1.4 and - transport authentication - """ - - def __init__(self, user, password, resource, on_auth): - """ - Caches username, password and resource for auth - """ - PlugIn.__init__(self) - self.user = user - if password is None: - self.password = '' - else: - self.password = password - self.resource = resource - self.on_auth = on_auth - - def plugin(self, owner): - """ - Determine the best auth method (digest/0k/plain) and use it for auth. - Returns used method name on success. Used internally - """ - log.info('Querying server about possible auth methods') - self.owner = owner - - owner.Dispatcher.SendAndWaitForResponse( - Iq('get', NS_AUTH, payload=[Node('username', payload=[self.user])]), - func=self._on_username) - - def _on_username(self, resp): - if not isResultNode(resp): - log.info('No result node arrived! Aborting...') - return self.on_auth(None) - - iq=Iq(typ='set', node=resp) - query = iq.getTag('query') - query.setTagData('username', self.user) - query.setTagData('resource', self.resource) - - if query.getTag('digest'): - log.info("Performing digest authentication") - query.setTagData('digest', - hashlib.sha1(self.owner.Dispatcher.Stream._document_attrs['id'] - + self.password).hexdigest()) - if query.getTag('password'): - query.delChild('password') - self._method = 'digest' - elif query.getTag('token'): - token = query.getTagData('token') - seq = query.getTagData('sequence') - log.info("Performing zero-k authentication") - - def hasher(s): - return hashlib.sha1(s).hexdigest() - - def hash_n_times(s, count): - return count and hasher(hash_n_times(s, count-1)) or s - - hash_ = hash_n_times(hasher(hasher(self.password) + token), - int(seq)) - query.setTagData('hash', hash_) - self._method='0k' - else: - log.warn("Secure methods unsupported, performing plain text \ - authentication") - self._method = 'plain' - self._owner._caller.get_password(self._on_password, self._method) - return - resp = self.owner.Dispatcher.SendAndWaitForResponse(iq, - func=self._on_auth) - - def _on_password(self, password): - self.password = '' if password is None else password - iq=Iq('set', NS_AUTH) - query = iq.getTag('query') - query.setTagData('username', self.user) - query.setTagData('resource', self.resource) - query.setTagData('password', self.password) - resp = self.owner.Dispatcher.SendAndWaitForResponse(iq, - func=self._on_auth) - - def _on_auth(self, resp): - if isResultNode(resp): - log.info('Sucessfully authenticated with remote host.') - self.owner.User = self.user - self.owner.Resource = self.resource - self.owner._registered_name = self.owner.User + '@' + \ - self.owner.Server+ '/' + self.owner.Resource - return self.on_auth(self._method) - log.info('Authentication failed!') - return self.on_auth(None) - - -class NonBlockingBind(PlugIn): - """ - Bind some JID to the current connection to allow router know of our - location. Must be plugged after successful SASL auth - """ - - def __init__(self): - PlugIn.__init__(self) - self.bound = None - self.supports_sm = False - self.resuming = False - - def plugin(self, owner): - ''' Start resource binding, if allowed at this time. Used internally. ''' - if self._owner.Dispatcher.Stream.features: - try: - self.FeaturesHandler(self._owner.Dispatcher, - self._owner.Dispatcher.Stream.features) - except NodeProcessed: - pass - else: - self._owner.RegisterHandler('features', self.FeaturesHandler, - xmlns=NS_STREAMS) - - def FeaturesHandler(self, conn, feats): - """ - Determine if server supports resource binding and set some internal - attributes accordingly. - - It also checks if server supports stream management - """ - - if feats.getTag('sm', namespace=NS_STREAM_MGMT): - self.supports_sm = True # server supports stream management - if self.resuming: - self._owner._caller.sm.resume_request() - - if not feats.getTag('bind', namespace=NS_BIND): - log.info('Server does not requested binding.') - # we try to bind resource anyway - #self.bound='failure' - self.bound = [] - return - if feats.getTag('session', namespace=NS_SESSION): - self.session = 1 - else: - self.session = -1 - self.bound = [] - - def plugout(self): - """ - Remove Bind handler from owner's dispatcher. Used internally - """ - self._owner.UnregisterHandler('features', self.FeaturesHandler, - xmlns=NS_STREAMS) - - def NonBlockingBind(self, resource=None, on_bound=None): - """ - Perform binding. Use provided resource name or random (if not provided). - """ - if self.resuming: # We don't bind if we resume the stream - return - self.on_bound = on_bound - self._resource = resource - if self._resource: - self._resource = [Node('resource', payload=[self._resource])] - else: - self._resource = [] - - self._owner.onreceive(None) - self._owner.Dispatcher.SendAndWaitForResponse( - Protocol('iq', typ='set', payload=[Node('bind', - attrs={'xmlns': NS_BIND}, payload=self._resource)]), - func=self._on_bound) - - def _on_bound(self, resp): - if isResultNode(resp): - if resp.getTag('bind') and resp.getTag('bind').getTagData('jid'): - self.bound.append(resp.getTag('bind').getTagData('jid')) - log.info('Successfully bound %s.' % self.bound[-1]) - jid = JID(resp.getTag('bind').getTagData('jid')) - self._owner.User = jid.getNode() - self._owner.Resource = jid.getResource() - # Only negociate stream management after bounded - sm = self._owner._caller.sm - if self.supports_sm: - # starts negociation - sm.set_owner(self._owner) - sm.negociate() - self._owner.Dispatcher.sm = sm - - if hasattr(self, 'session') and self.session == -1: - # Server don't want us to initialize a session - log.info('No session required.') - self.on_bound('ok') - else: - self._owner.SendAndWaitForResponse(Protocol('iq', typ='set', - payload=[Node('session', attrs={'xmlns':NS_SESSION})]), - func=self._on_session) - return - if resp: - log.info('Binding failed: %s.' % resp.getTag('error')) - self.on_bound(None) - else: - log.info('Binding failed: timeout expired.') - self.on_bound(None) - - def _on_session(self, resp): - self._owner.onreceive(None) - if isResultNode(resp): - log.info('Successfully opened session.') - self.session = 1 - self.on_bound('ok') - else: - log.error('Session open failed.') - self.session = 0 - self.on_bound(None) diff --git a/src/common/xmpp/bosh.py b/src/common/xmpp/bosh.py deleted file mode 100644 index 9bf5be7f3..000000000 --- a/src/common/xmpp/bosh.py +++ /dev/null @@ -1,581 +0,0 @@ -## bosh.py -## -## -## Copyright (C) 2008 Tomas Karasek <tom.to.the.k@gmail.com> -## -## This file is part of Gajim. -## -## Gajim is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published -## by the Free Software Foundation; version 3 only. -## -## Gajim is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with Gajim. If not, see <http://www.gnu.org/licenses/>. - - -import locale, random -from hashlib import sha1 -from transports_nb import NonBlockingTransport, NonBlockingHTTPBOSH,\ - CONNECTED, CONNECTING, DISCONNECTED, DISCONNECTING,\ - urisplit, DISCONNECT_TIMEOUT_SECONDS -from protocol import BOSHBody, Protocol, NS_CLIENT -from simplexml import Node - -import logging -log = logging.getLogger('gajim.c.x.bosh') - -KEY_COUNT = 10 - -# Fake file descriptor - it's used for setting read_timeout in idlequeue for -# BOSH Transport. In TCP-derived transports this is file descriptor of socket. -FAKE_DESCRIPTOR = -1337 - - -class NonBlockingBOSH(NonBlockingTransport): - def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls, certs, - xmpp_server, domain, bosh_dict, proxy_creds): - NonBlockingTransport.__init__(self, raise_event, on_disconnect, idlequeue, - estabilish_tls, certs) - - self.bosh_sid = None - if locale.getdefaultlocale()[0]: - self.bosh_xml_lang = locale.getdefaultlocale()[0].split('_')[0] - else: - self.bosh_xml_lang = 'en' - - self.http_version = 'HTTP/1.1' - self.http_persistent = True - self.http_pipelining = bosh_dict['bosh_http_pipelining'] - self.bosh_to = domain - - self.route_host, self.route_port = xmpp_server - - self.bosh_wait = bosh_dict['bosh_wait'] - if not self.http_pipelining: - self.bosh_hold = 1 - else: - self.bosh_hold = bosh_dict['bosh_hold'] - self.bosh_requests = self.bosh_hold - self.bosh_uri = bosh_dict['bosh_uri'] - self.bosh_content = bosh_dict['bosh_content'] - self.over_proxy = bosh_dict['bosh_useproxy'] - if estabilish_tls: - self.bosh_secure = 'true' - else: - self.bosh_secure = 'false' - self.use_proxy_auth = bosh_dict['useauth'] - self.proxy_creds = proxy_creds - self.wait_cb_time = None - self.http_socks = [] - self.stanza_buffer = [] - self.prio_bosh_stanzas = [] - self.current_recv_handler = None - self.current_recv_socket = None - self.key_stack = None - self.ack_checker = None - self.after_init = False - self.proxy_dict = {} - if self.over_proxy and self.estabilish_tls: - self.proxy_dict['type'] = 'http' - # with SSL over proxy, we do HTTP CONNECT to proxy to open a channel to - # BOSH Connection Manager - host, port = urisplit(self.bosh_uri)[1:3] - self.proxy_dict['xmpp_server'] = (host, port) - self.proxy_dict['credentials'] = self.proxy_creds - - - def connect(self, conn_5tuple, on_connect, on_connect_failure): - NonBlockingTransport.connect(self, conn_5tuple, on_connect, on_connect_failure) - - global FAKE_DESCRIPTOR - FAKE_DESCRIPTOR = FAKE_DESCRIPTOR - 1 - self.fd = FAKE_DESCRIPTOR - - self.stanza_buffer = [] - self.prio_bosh_stanzas = [] - - self.key_stack = KeyStack(KEY_COUNT) - self.ack_checker = AckChecker() - self.after_init = True - - self.http_socks.append(self.get_new_http_socket()) - self._tcp_connecting_started() - - self.http_socks[0].connect( - conn_5tuple = conn_5tuple, - on_connect = self._on_connect, - on_connect_failure = self._on_connect_failure) - - def _on_connect(self): - self.peerhost = self.http_socks[0].peerhost - self.ssl_lib = self.http_socks[0].ssl_lib - NonBlockingTransport._on_connect(self) - - - - def set_timeout(self, timeout): - if self.get_state() != DISCONNECTED and self.fd != -1: - NonBlockingTransport.set_timeout(self, timeout) - else: - log.warn('set_timeout: TIMEOUT NOT SET: state is %s, fd is %s' % (self.get_state(), self.fd)) - - def on_http_request_possible(self): - """ - Called when HTTP request it's possible to send a HTTP request. It can be when - socket is connected or when HTTP response arrived - - There should be always one pending request to BOSH CM. - """ - log.debug('on_http_req possible, state:\n%s' % self.get_current_state()) - if self.get_state()==DISCONNECTED: return - - #Hack for making the non-secure warning dialog work - if self._owner.got_features: - if (hasattr(self._owner, 'NonBlockingNonSASL') or hasattr(self._owner, 'SASL')): - self.send_BOSH(None) - else: - # If we already got features and no auth module was plugged yet, we are - # probably waiting for confirmation of the "not-secure-connection" dialog. - # We don't send HTTP request in that case. - # see http://lists.jabber.ru/pipermail/ejabberd/2008-August/004027.html - return - else: - self.send_BOSH(None) - - - - def get_socket_in(self, state): - """ - Get sockets in desired state - """ - for s in self.http_socks: - if s.get_state()==state: return s - return None - - - def get_free_socket(self): - """ - Select and returns socket eligible for sending a data to - """ - if self.http_pipelining: - return self.get_socket_in(CONNECTED) - else: - last_recv_time, tmpsock = 0, None - for s in self.http_socks: - # we're interested only in CONNECTED socket with no requests pending - if s.get_state()==CONNECTED and s.pending_requests==0: - # if there's more of them, we want the one with the least recent data receive - # (lowest last_recv_time) - if (last_recv_time==0) or (s.last_recv_time < last_recv_time): - last_recv_time = s.last_recv_time - tmpsock = s - if tmpsock: - return tmpsock - else: - return None - - - def send_BOSH(self, payload): - """ - Tries to send a stanza in payload by appeding it to a buffer and plugging a - free socket for writing. - """ - total_pending_reqs = sum([s.pending_requests for s in self.http_socks]) - - # when called after HTTP response (Payload=None) and when there are already - # some pending requests and no data to send, or when the socket is - # disconnected, we do nothing - if payload is None and \ - total_pending_reqs > 0 and \ - self.stanza_buffer == [] and \ - self.prio_bosh_stanzas == [] or \ - self.get_state()==DISCONNECTED: - return - - # Add xmlns to stanza to help ejabberd server - if payload and isinstance(payload, Protocol): - if not payload.getNamespace(): - payload.setNamespace(NS_CLIENT) - - # now the payload is put to buffer and will be sent at some point - self.append_stanza(payload) - - # if we're about to make more requests than allowed, we don't send - stanzas will be - # sent after HTTP response from CM, exception is when we're disconnecting - then we - # send anyway - if total_pending_reqs >= self.bosh_requests and self.get_state()!=DISCONNECTING: - log.warn('attemp to make more requests than allowed by Connection Manager:\n%s' % - self.get_current_state()) - return - - # when there's free CONNECTED socket, we plug it for write and the data will - # be sent when write is possible - if self.get_free_socket(): - self.plug_socket() - return - - # if there is a connecting socket, we just wait for when it connects, - # payload will be sent in a sec when the socket connects - if self.get_socket_in(CONNECTING): return - - # being here means there are either DISCONNECTED sockets or all sockets are - # CONNECTED with too many pending requests - s = self.get_socket_in(DISCONNECTED) - - # if we have DISCONNECTED socket, lets connect it and plug for send - if s: - self.connect_and_flush(s) - else: - # otherwise create and connect a new one - ss = self.get_new_http_socket() - self.http_socks.append(ss) - self.connect_and_flush(ss) - return - - def plug_socket(self): - stanza = None - s = self.get_free_socket() - if s: - s._plug_idle(writable=True, readable=True) - else: - log.error('=====!!!!!!!!====> Couldn\'t get free socket in plug_socket())') - - def build_stanza(self, socket): - """ - Build a BOSH body tag from data in buffers and adds key, rid and ack - attributes to it - - This method is called from _do_send() of underlying transport. This is to - ensure rid and keys will be processed in correct order. If I generate - them before plugging a socket for write (and did it for two sockets/HTTP - connections) in parallel, they might be sent in wrong order, which - results in violating the BOSH session and server-side disconnect. - """ - if self.prio_bosh_stanzas: - stanza, add_payload = self.prio_bosh_stanzas.pop(0) - if add_payload: - stanza.setPayload(self.stanza_buffer) - self.stanza_buffer = [] - else: - stanza = self.boshify_stanzas(self.stanza_buffer) - self.stanza_buffer = [] - - stanza = self.ack_checker.backup_stanza(stanza, socket) - - key, newkey = self.key_stack.get() - if key: - stanza.setAttr('key', key) - if newkey: - stanza.setAttr('newkey', newkey) - - - log.info('sending msg with rid=%s to sock %s' % (stanza.getAttr('rid'), id(socket))) - self.renew_bosh_wait_timeout(self.bosh_wait + 3) - return stanza - - - def on_bosh_wait_timeout(self): - log.error('Connection Manager didn\'t respond within %s + 3 seconds --> forcing disconnect' % self.bosh_wait) - self.disconnect() - - - def renew_bosh_wait_timeout(self, timeout): - if self.wait_cb_time is not None: - self.remove_bosh_wait_timeout() - sched_time = self.idlequeue.set_alarm(self.on_bosh_wait_timeout, timeout) - self.wait_cb_time = sched_time - - def remove_bosh_wait_timeout(self): - self.idlequeue.remove_alarm( - self.on_bosh_wait_timeout, - self.wait_cb_time) - - def on_persistent_fallback(self, socket): - """ - Called from underlying transport when server closes TCP connection - - :param socket: disconnected transport object - """ - if socket.http_persistent: - log.warn('Fallback to nonpersistent HTTP (no pipelining as well)') - socket.http_persistent = False - self.http_persistent = False - self.http_pipelining = False - socket.disconnect(do_callback=False) - self.connect_and_flush(socket) - else: - socket.disconnect() - - - - def handle_body_attrs(self, stanza_attrs): - """ - Called for each incoming body stanza from dispatcher. Checks body - attributes. - """ - self.remove_bosh_wait_timeout() - - if self.after_init: - if stanza_attrs.has_key('sid'): - # session ID should be only in init response - self.bosh_sid = stanza_attrs['sid'] - - if stanza_attrs.has_key('requests'): - self.bosh_requests = int(stanza_attrs['requests']) - - if stanza_attrs.has_key('wait'): - self.bosh_wait = int(stanza_attrs['wait']) - self.after_init = False - - ack = None - if stanza_attrs.has_key('ack'): - ack = stanza_attrs['ack'] - self.ack_checker.process_incoming_ack(ack=ack, - socket=self.current_recv_socket) - - if stanza_attrs.has_key('type'): - if stanza_attrs['type'] in ['terminate', 'terminal']: - condition = 'n/a' - if stanza_attrs.has_key('condition'): - condition = stanza_attrs['condition'] - if condition == 'n/a': - log.info('Received sesion-ending terminating stanza') - else: - log.error('Received terminating stanza: %s - %s' % (condition, - bosh_errors[condition])) - self.disconnect() - return - - if stanza_attrs['type'] == 'error': - # recoverable error - pass - return - - - def append_stanza(self, stanza): - """ - Append stanza to a buffer to send - """ - if stanza: - if isinstance(stanza, tuple): - # stanza is tuple of BOSH stanza and bool value for whether to add payload - self.prio_bosh_stanzas.append(stanza) - else: - # stanza is XMPP stanza. Will be boshified before send. - self.stanza_buffer.append(stanza) - - - def send(self, stanza, now=False): - self.send_BOSH(stanza) - - - - def get_current_state(self): - t = '------ SOCKET_ID\tSOCKET_STATE\tPENDING_REQS\n' - for s in self.http_socks: - t = '%s------ %s\t%s\t%s\n' % (t, id(s), s.get_state(), s.pending_requests) - t = '%s------ prio stanzas: %s, queued XMPP stanzas: %s, not_acked stanzas: %s' \ - % (t, self.prio_bosh_stanzas, self.stanza_buffer, - self.ack_checker.get_not_acked_rids()) - return t - - - def connect_and_flush(self, socket): - socket.connect( - conn_5tuple = self.conn_5tuple, - on_connect = self.on_http_request_possible, - on_connect_failure = self.disconnect) - - - def boshify_stanzas(self, stanzas=[], body_attrs=None): - """ - Wraps zero to many stanzas by body tag with xmlns and sid - """ - log.debug('boshify_staza - type is: %s, stanza is %s' % (type(stanzas), stanzas)) - tag = BOSHBody(attrs={'sid': self.bosh_sid}) - tag.setPayload(stanzas) - return tag - - - def send_init(self, after_SASL=False): - if after_SASL: - t = BOSHBody( - attrs={ 'to': self.bosh_to, - 'sid': self.bosh_sid, - 'xml:lang': self.bosh_xml_lang, - 'xmpp:restart': 'true', - 'secure': self.bosh_secure, - 'xmlns:xmpp': 'urn:xmpp:xbosh'}) - else: - t = BOSHBody( - attrs={ 'content': self.bosh_content, - 'hold': str(self.bosh_hold), - 'route': 'xmpp:%s:%s' % (self.route_host, self.route_port), - 'to': self.bosh_to, - 'wait': str(self.bosh_wait), - 'xml:lang': self.bosh_xml_lang, - 'xmpp:version': '1.0', - 'ver': '1.6', - 'xmlns:xmpp': 'urn:xmpp:xbosh'}) - self.send_BOSH((t, True)) - - def start_disconnect(self): - NonBlockingTransport.start_disconnect(self) - self.renew_bosh_wait_timeout(DISCONNECT_TIMEOUT_SECONDS) - self.send_BOSH( - (BOSHBody(attrs={'sid': self.bosh_sid, 'type': 'terminate'}), True)) - - - def get_new_http_socket(self): - http_dict = {'http_uri': self.bosh_uri, - 'http_version': self.http_version, - 'http_persistent': self.http_persistent, - 'add_proxy_headers': self.over_proxy and not self.estabilish_tls} - if self.use_proxy_auth: - http_dict['proxy_user'], http_dict['proxy_pass'] = self.proxy_creds - - s = NonBlockingHTTPBOSH( - raise_event=self.raise_event, - on_disconnect=self.disconnect, - idlequeue = self.idlequeue, - estabilish_tls = self.estabilish_tls, - certs = self.certs, - on_http_request_possible = self.on_http_request_possible, - http_dict = http_dict, - proxy_dict = self.proxy_dict, - on_persistent_fallback = self.on_persistent_fallback) - - s.onreceive(self.on_received_http) - s.set_stanza_build_cb(self.build_stanza) - return s - - - def onreceive(self, recv_handler): - if recv_handler is None: - recv_handler = self._owner.Dispatcher.ProcessNonBlocking - self.current_recv_handler = recv_handler - - - def on_received_http(self, data, socket): - self.current_recv_socket = socket - self.current_recv_handler(data) - - - def disconnect(self, do_callback=True): - self.remove_bosh_wait_timeout() - if self.get_state() == DISCONNECTED: return - self.fd = -1 - for s in self.http_socks: - s.disconnect(do_callback=False) - NonBlockingTransport.disconnect(self, do_callback) - - -def get_rand_number(): - # with 50-bit random initial rid, session would have to go up - # to 7881299347898368 messages to raise rid over 2**53 - # (see http://www.xmpp.org/extensions/xep-0124.html#rids) - # it's also used for sequence key initialization - r = random.Random() - r.seed() - return r.getrandbits(50) - - - -class AckChecker(): - """ - Class for generating rids and generating and checking acknowledgements in - BOSH messages - """ - def __init__(self): - self.rid = get_rand_number() - self.ack = 1 - self.last_rids = {} - self.not_acked = [] - - - def get_not_acked_rids(self): return [rid for rid, st in self.not_acked] - - def backup_stanza(self, stanza, socket): - socket.pending_requests += 1 - rid = self.get_rid() - self.not_acked.append((rid, stanza)) - stanza.setAttr('rid', str(rid)) - self.last_rids[socket]=rid - - if self.rid != self.ack + 1: - stanza.setAttr('ack', str(self.ack)) - return stanza - - def process_incoming_ack(self, socket, ack=None): - socket.pending_requests -= 1 - if ack: - ack = int(ack) - else: - ack = self.last_rids[socket] - - i = len([rid for rid, st in self.not_acked if ack >= rid]) - self.not_acked = self.not_acked[i:] - - self.ack = ack - - - def get_rid(self): - self.rid = self.rid + 1 - return self.rid - - - - - -class KeyStack(): - """ - Class implementing key sequences for BOSH messages - """ - def __init__(self, count): - self.count = count - self.keys = [] - self.reset() - self.first_call = True - - def reset(self): - seed = str(get_rand_number()) - self.keys = [sha1(seed).hexdigest()] - for i in range(self.count-1): - curr_seed = self.keys[i] - self.keys.append(sha1(curr_seed).hexdigest()) - - def get(self): - if self.first_call: - self.first_call = False - return (None, self.keys.pop()) - - if len(self.keys)>1: - return (self.keys.pop(), None) - else: - last_key = self.keys.pop() - self.reset() - new_key = self.keys.pop() - return (last_key, new_key) - -# http://www.xmpp.org/extensions/xep-0124.html#errorstatus-terminal -bosh_errors = { - 'n/a': 'none or unknown condition in terminating body stanza', - 'bad-request': 'The format of an HTTP header or binding element received from the client is unacceptable (e.g., syntax error), or Script Syntax is not supported.', - 'host-gone': 'The target domain specified in the "to" attribute or the target host or port specified in the "route" attribute is no longer serviced by the connection manager.', - 'host-unknown': 'The target domain specified in the "to" attribute or the target host or port specified in the "route" attribute is unknown to the connection manager.', - 'improper-addressing': 'The initialization element lacks a "to" or "route" attribute (or the attribute has no value) but the connection manager requires one.', - 'internal-server-error': 'The connection manager has experienced an internal error that prevents it from servicing the request.', - 'item-not-found': '(1) "sid" is not valid, (2) "stream" is not valid, (3) "rid" is larger than the upper limit of the expected window, (4) connection manager is unable to resend response, (5) "key" sequence is invalid', - 'other-request': 'Another request being processed at the same time as this request caused the session to terminate.', - 'policy-violation': 'The client has broken the session rules (polling too frequently, requesting too frequently, too many simultaneous requests).', - 'remote-connection-failed': 'The connection manager was unable to connect to, or unable to connect securely to, or has lost its connection to, the server.', - 'remote-stream-error': 'Encapsulates an error in the protocol being transported.', - 'see-other-uri': 'The connection manager does not operate at this URI (e.g., the connection manager accepts only SSL or TLS connections at some https: URI rather than the http: URI requested by the client). The client may try POSTing to the URI in the content of the <uri/> child element.', - 'system-shutdown': 'The connection manager is being shut down. All active HTTP sessions are being terminated. No new sessions can be created.', - 'undefined-condition': 'The error is not one of those defined herein; the connection manager SHOULD include application-specific information in the content of the <body/> wrapper.' -} diff --git a/src/common/xmpp/c14n.py b/src/common/xmpp/c14n.py deleted file mode 100644 index 0d318a352..000000000 --- a/src/common/xmpp/c14n.py +++ /dev/null @@ -1,59 +0,0 @@ -# -*- coding:utf-8 -*- -## c14n.py -## -## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com> -## -## This file is part of Gajim. -## -## Gajim is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published -## by the Free Software Foundation; version 3 only. -## -## Gajim is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with Gajim. If not, see <http://www.gnu.org/licenses/>. -## - -""" -XML canonicalisation methods (for XEP-0116) -""" - -from simplexml import ustr - -def c14n(node, is_buggy): - s = "<" + node.name - if node.namespace: - if not node.parent or node.parent.namespace != node.namespace: - s = s + ' xmlns="%s"' % node.namespace - - sorted_attrs = sorted(node.attrs.keys()) - for key in sorted_attrs: - if not is_buggy and key == 'xmlns': - continue - val = ustr(node.attrs[key]) - # like XMLescape() but with whitespace and without > - s = s + ' %s="%s"' % ( key, normalise_attr(val) ) - s = s + ">" - cnt = 0 - if node.kids: - for a in node.kids: - if (len(node.data)-1) >= cnt: - s = s + normalise_text(node.data[cnt]) - s = s + c14n(a, is_buggy) - cnt=cnt+1 - if (len(node.data)-1) >= cnt: s = s + normalise_text(node.data[cnt]) - if not node.kids and s.endswith('>'): - s=s[:-1]+' />' - else: - s = s + "</" + node.name + ">" - return s.encode('utf-8') - -def normalise_attr(val): - return val.replace('&', '&').replace('<', '<').replace('"', '"').replace('\t', '	').replace('\n', '
').replace('\r', '
') - -def normalise_text(val): - return val.replace('&', '&').replace('<', '<').replace('>', '>').replace('\r', '
') diff --git a/src/common/xmpp/client_nb.py b/src/common/xmpp/client_nb.py deleted file mode 100644 index 61ff769f2..000000000 --- a/src/common/xmpp/client_nb.py +++ /dev/null @@ -1,629 +0,0 @@ -## client_nb.py -## based on client.py, changes backported up to revision 1.60 -## -## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov -## modified by Dimitur Kirov <dkirov@gmail.com> -## -## This program is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation; either version 2, or (at your option) -## any later version. -## -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. - -# $Id: client.py,v 1.52 2006/01/02 19:40:55 normanr Exp $ - -""" -Client class establishs connection to XMPP Server and handles authentication -""" - -import socket -import transports_nb, dispatcher_nb, auth_nb, roster_nb, protocol, bosh -from protocol import NS_TLS - -import logging -log = logging.getLogger('gajim.c.x.client_nb') - - -class NonBlockingClient: - """ - Client class is XMPP connection mountpoint. Objects for authentication, - network communication, roster, xml parsing ... are plugged to client object. - Client implements the abstract behavior - mostly negotioation and callbacks - handling, whereas underlying modules take care of feature-specific logic - """ - - def __init__(self, domain, idlequeue, caller=None): - """ - Caches connection data - - :param domain: domain - for to: attribute (from account info) - :param idlequeue: processing idlequeue - :param caller: calling object - it has to implement methods - _event_dispatcher which is called from dispatcher instance - """ - self.Namespace = protocol.NS_CLIENT - self.defaultNamespace = self.Namespace - - self.idlequeue = idlequeue - self.disconnect_handlers = [] - - self.Server = domain - self.xmpp_hostname = None # FQDN hostname to connect to - - # caller is who initiated this client, it is in needed to register - # the EventDispatcher - self._caller = caller - self._owner = self - self._registered_name = None # our full jid, set after successful auth - self.connected = '' - self.ip_addresses = [] - self.socket = None - self.on_connect = None - self.on_proxy_failure = None - self.on_connect_failure = None - self.proxy = None - self.got_features = False - self.got_see_other_host = None - self.stream_started = False - self.disconnecting = False - self.protocol_type = 'XMPP' - - def disconnect(self, message=''): - """ - Called on disconnection - disconnect callback is picked based on state of - the client. - """ - # to avoid recursive calls - if self.ip_addresses: - self._try_next_ip() - return - if self.disconnecting: return - - log.info('Disconnecting NBClient: %s' % message) - - sasl_failed = False - if 'NonBlockingRoster' in self.__dict__: - self.NonBlockingRoster.PlugOut() - if 'NonBlockingBind' in self.__dict__: - self.NonBlockingBind.PlugOut() - if 'NonBlockingNonSASL' in self.__dict__: - self.NonBlockingNonSASL.PlugOut() - if 'SASL' in self.__dict__: - if 'startsasl' in self.SASL.__dict__ and \ - self.SASL.startsasl == 'failure-in-process': - sasl_failed = True - self.SASL.startsasl = 'failure' - self._on_start_sasl() - else: - self.SASL.PlugOut() - if 'NonBlockingTCP' in self.__dict__: - self.NonBlockingTCP.PlugOut() - if 'NonBlockingHTTP' in self.__dict__: - self.NonBlockingHTTP.PlugOut() - if 'NonBlockingBOSH' in self.__dict__: - self.NonBlockingBOSH.PlugOut() - # FIXME: we never unplug dispatcher, only on next connect - # See _xmpp_connect_machine and SASLHandler - - connected = self.connected - stream_started = self.stream_started - - self.connected = '' - self.stream_started = False - - self.disconnecting = True - - log.debug('Client disconnected..') - # Don't call any callback when it's a SASL failure. - # SASL handler is already called - if connected == '' and not sasl_failed: - # if we're disconnecting before connection to XMPP sever is opened, - # we don't call disconnect handlers but on_connect_failure callback - if self.proxy: - # with proxy, we have different failure callback - log.debug('calling on_proxy_failure cb') - self.on_proxy_failure(reason=message) - else: - log.debug('calling on_connect_failure cb') - self.on_connect_failure() - elif not sasl_failed: - # we are connected to XMPP server - if not stream_started: - # if error occur before XML stream was opened, e.g. no response on - # init request, we call the on_connect_failure callback because - # proper connection is not established yet and it's not a proxy - # issue - log.debug('calling on_connect_failure cb') - self._caller.streamError = message - self.on_connect_failure() - else: - # with open connection, we are calling the disconnect handlers - for i in reversed(self.disconnect_handlers): - log.debug('Calling disconnect handler %s' % i) - i() - self.disconnecting = False - - def connect(self, on_connect, on_connect_failure, hostname=None, port=5222, - on_proxy_failure=None, on_stream_error_cb=None, proxy=None, - secure_tuple=('plain', None, None)): - """ - Open XMPP connection (open XML streams in both directions) - - :param on_connect: called after stream is successfully opened - :param on_connect_failure: called when error occures during connection - :param hostname: hostname of XMPP server from SRV request - :param port: port number of XMPP server - :param on_proxy_failure: called if error occurres during TCP connection to - proxy server or during proxy connecting process - :param proxy: dictionary with proxy data. It should contain at least - values for keys 'host' and 'port' - connection details for proxy serve - and optionally keys 'user' and 'pass' as proxy credentials - :param secure_tuple: tuple of (desired connection type, cacerts, mycerts) - connection type can be 'ssl' - TLS established after TCP connection, - 'tls' - TLS established after negotiation with starttls, or 'plain'. - cacerts, mycerts - see tls_nb.NonBlockingTLS constructor for more - details - """ - self.on_connect = on_connect - self.on_connect_failure=on_connect_failure - self.on_proxy_failure = on_proxy_failure - self.on_stream_error_cb = on_stream_error_cb - self.desired_security, self.cacerts, self.mycerts = secure_tuple - self.Connection = None - self.Port = port - self.proxy = proxy - - if hostname: - self.xmpp_hostname = hostname - else: - self.xmpp_hostname = self.Server - - # We only check for SSL here as for TLS we will first have to start a - # PLAIN connection and negotiate TLS afterwards. - # establish_tls will instruct transport to start secure connection - # directly - establish_tls = self.desired_security == 'ssl' - certs = (self.cacerts, self.mycerts) - - proxy_dict = {} - tcp_host = self.xmpp_hostname - tcp_port = self.Port - - if proxy: - # with proxies, client connects to proxy instead of directly to - # XMPP server ((hostname, port)) - # tcp_host is hostname of machine used for socket connection - # (DNS request will be done for proxy or BOSH CM hostname) - tcp_host, tcp_port, proxy_user, proxy_pass = \ - transports_nb.get_proxy_data_from_dict(proxy) - - if proxy['type'] == 'bosh': - # Setup BOSH transport - self.socket = bosh.NonBlockingBOSH.get_instance( - on_disconnect=self.disconnect, - raise_event=self.raise_event, - idlequeue=self.idlequeue, - estabilish_tls=establish_tls, - certs=certs, - proxy_creds=(proxy_user, proxy_pass), - xmpp_server=(self.xmpp_hostname, self.Port), - domain=self.Server, - bosh_dict=proxy) - self.protocol_type = 'BOSH' - self.wait_for_restart_response = \ - proxy['bosh_wait_for_restart_response'] - else: - # http proxy - proxy_dict['type'] = proxy['type'] - proxy_dict['xmpp_server'] = (self.xmpp_hostname, self.Port) - proxy_dict['credentials'] = (proxy_user, proxy_pass) - - if not proxy or proxy['type'] != 'bosh': - # Setup ordinary TCP transport - self.socket = transports_nb.NonBlockingTCP.get_instance( - on_disconnect=self.disconnect, - raise_event=self.raise_event, - idlequeue=self.idlequeue, - estabilish_tls=establish_tls, - certs=certs, - proxy_dict=proxy_dict) - - # plug transport into client as self.Connection - self.socket.PlugIn(self) - - self._resolve_hostname( - hostname=tcp_host, - port=tcp_port, - on_success=self._try_next_ip) - - def _resolve_hostname(self, hostname, port, on_success): - """ - Wrapper for getaddinfo call - - FIXME: getaddinfo blocks - """ - try: - self.ip_addresses = socket.getaddrinfo(hostname, port, - socket.AF_UNSPEC, socket.SOCK_STREAM) - except socket.gaierror, (errnum, errstr): - self.disconnect(message='Lookup failure for %s:%s, hostname: %s - %s' % - (self.Server, self.Port, hostname, errstr)) - else: - on_success() - - def _try_next_ip(self, err_message=None): - """ - Iterate over IP addresses tries to connect to it - """ - if err_message: - log.debug('While looping over DNS A records: %s' % err_message) - if self.ip_addresses == []: - msg = 'Run out of hosts for name %s:%s.' % (self.Server, self.Port) - msg = msg + ' Error for last IP: %s' % err_message - self.disconnect(msg) - else: - self.current_ip = self.ip_addresses.pop(0) - self.socket.connect( - conn_5tuple=self.current_ip, - on_connect=lambda: self._xmpp_connect(), - on_connect_failure=self._try_next_ip) - - def incoming_stream_version(self): - """ - Get version of xml stream - """ - if 'version' in self.Dispatcher.Stream._document_attrs: - return self.Dispatcher.Stream._document_attrs['version'] - else: - return None - - def _xmpp_connect(self, socket_type=None): - """ - Start XMPP connecting process - open the XML stream. Is called after TCP - connection is established or after switch to TLS when successfully - negotiated with <starttls>. - """ - # socket_type contains info which transport connection was established - if not socket_type: - if self.Connection.ssl_lib: - # When ssl_lib is set we connected via SSL - socket_type = 'ssl' - else: - # PLAIN is default - socket_type = 'plain' - self.connected = socket_type - self._xmpp_connect_machine() - - def _xmpp_connect_machine(self, mode=None, data=None): - """ - Finite automaton taking care of stream opening and features tag handling. - Calls _on_stream_start when stream is started, and disconnect() on - failure. - """ - log.info('-------------xmpp_connect_machine() >> mode: %s, data: %s...' % - (mode, str(data)[:20])) - - def on_next_receive(mode): - """ - Set desired on_receive callback on transport based on the state of - connect_machine. - """ - log.info('setting %s on next receive' % mode) - if mode is None: - self.onreceive(None) # switch to Dispatcher.ProcessNonBlocking - else: - self.onreceive(lambda _data:self._xmpp_connect_machine(mode, _data)) - - if not mode: - # starting state - if self.__dict__.has_key('Dispatcher'): - self.Dispatcher.PlugOut() - self.got_features = False - dispatcher_nb.Dispatcher.get_instance().PlugIn(self) - on_next_receive('RECEIVE_DOCUMENT_ATTRIBUTES') - - elif mode == 'FAILURE': - self.disconnect('During XMPP connect: %s' % data) - - elif mode == 'RECEIVE_DOCUMENT_ATTRIBUTES': - if data: - self.Dispatcher.ProcessNonBlocking(data) - self.ip_addresses = [] - if not hasattr(self, 'Dispatcher') or \ - self.Dispatcher.Stream._document_attrs is None: - self._xmpp_connect_machine( - mode='FAILURE', - data='Error on stream open') - return - - # if terminating stanza was received after init request then client gets - # disconnected from bosh transport plugin and we have to end the stream - # negotiating process straight away. - # fixes #4657 - if not self.connected: return - - if self.incoming_stream_version() == '1.0': - if not self.got_features: - on_next_receive('RECEIVE_STREAM_FEATURES') - else: - log.info('got STREAM FEATURES in first recv') - self._xmpp_connect_machine(mode='STREAM_STARTED') - else: - log.info('incoming stream version less than 1.0') - self._xmpp_connect_machine(mode='STREAM_STARTED') - - elif mode == 'RECEIVE_STREAM_FEATURES': - if data: - # sometimes <features> are received together with document - # attributes and sometimes on next receive... - self.Dispatcher.ProcessNonBlocking(data) - if self.got_see_other_host: - log.info('got see-other-host') - self.onreceive(None) - self.on_stream_error_cb(self, self.got_see_other_host) - elif not self.got_features: - self._xmpp_connect_machine( - mode='FAILURE', - data='Missing <features> in 1.0 stream') - else: - log.info('got STREAM FEATURES in second recv') - self._xmpp_connect_machine(mode='STREAM_STARTED') - - elif mode == 'STREAM_STARTED': - self._on_stream_start() - - def _on_stream_start(self): - """ - Called after XMPP stream is opened. TLS negotiation may follow if - supported and desired. - """ - self.stream_started = True - if not hasattr(self, 'onreceive'): - # we may already have been disconnected - return - self.onreceive(None) - - if self.connected == 'plain': - if self.desired_security == 'plain': - # if we want and have plain connection, we're done now - self._on_connect() - else: - # try to negotiate TLS - if self.incoming_stream_version() != '1.0': - # if stream version is less than 1.0, we can't do more - log.info('While connecting with type = "tls": stream version ' + - 'is less than 1.0') - self._on_connect() - return - if self.Dispatcher.Stream.features.getTag('starttls'): - # Server advertises TLS support, start negotiation - self.stream_started = False - log.info('TLS supported by remote server. Requesting TLS start.') - self._tls_negotiation_handler() - else: - log.info('While connecting with type = "tls": TLS unsupported ' + - 'by remote server') - self._on_connect() - - elif self.connected in ['ssl', 'tls']: - self._on_connect() - else: - assert False, 'Stream opened for unsupported connection' - - def _tls_negotiation_handler(self, con=None, tag=None): - """ - Take care of TLS negotioation with <starttls> - """ - log.info('-------------tls_negotiaton_handler() >> tag: %s' % tag) - if not con and not tag: - # starting state when we send the <starttls> - self.RegisterHandlerOnce('proceed', self._tls_negotiation_handler, - xmlns=NS_TLS) - self.RegisterHandlerOnce('failure', self._tls_negotiation_handler, - xmlns=NS_TLS) - self.send('<starttls xmlns="%s"/>' % NS_TLS) - else: - # we got <proceed> or <failure> - if tag.getNamespace() != NS_TLS: - self.disconnect('Unknown namespace: %s' % tag.getNamespace()) - return - tagname = tag.getName() - if tagname == 'failure': - self.disconnect('TLS <failure> received: %s' % tag) - return - log.info('Got starttls proceed response. Switching to TLS/SSL...') - # following call wouldn't work for BOSH transport but it doesn't matter - # because <starttls> negotiation with BOSH is forbidden - self.Connection.tls_init( - on_succ = lambda: self._xmpp_connect(socket_type='tls'), - on_fail = lambda: self.disconnect('error while etabilishing TLS')) - - def _on_connect(self): - """ - Preceed call of on_connect callback - """ - self.onreceive(None) - self.on_connect(self, self.connected) - - def raise_event(self, event_type, data): - """ - Raise event to connection instance. DATA_SENT and DATA_RECIVED events - are used in XML console to show XMPP traffic - """ - log.info('raising event from transport: :::::%s::::\n_____________\n%s\n_____________\n' % (event_type, data)) - if hasattr(self, 'Dispatcher'): - self.Dispatcher.Event('', event_type, data) - -############################################################################### -### follows code for authentication, resource bind, session and roster download -############################################################################### - - def auth(self, user, password, resource='', sasl=True, on_auth=None): - """ - Authenticate connnection and bind resource. If resource is not provided - random one or library name used - - :param user: XMPP username - :param password: XMPP password - :param resource: resource that shall be used for auth/connecting - :param sasl: Boolean indicating if SASL shall be used. (default: True) - :param on_auth: Callback, called after auth. On auth failure, argument - is None. - """ - self._User, self._Password = user, password - self._Resource, self._sasl = resource, sasl - self.on_auth = on_auth - self._on_doc_attrs() - return - - def _on_old_auth(self, res): - """ - Callback used by NON-SASL auth. On auth failure, res is None - """ - if res: - self.connected += '+old_auth' - self.on_auth(self, 'old_auth') - else: - self.on_auth(self, None) - - def _on_sasl_auth(self, res): - """ - Used internally. On auth failure, res is None - """ - self.onreceive(None) - if res: - self.connected += '+sasl' - self.on_auth(self, 'sasl') - else: - self.on_auth(self, None) - - def _on_doc_attrs(self): - """ - Plug authentication objects and start auth - """ - if self._sasl: - auth_nb.SASL.get_instance(self._User, self._Password, - self._on_start_sasl).PlugIn(self) - if not hasattr(self, 'SASL'): - return - if not self._sasl or self.SASL.startsasl == 'not-supported': - if not self._Resource: - self._Resource = 'xmpppy' - auth_nb.NonBlockingNonSASL.get_instance(self._User, self._Password, - self._Resource, self._on_old_auth).PlugIn(self) - return - self.SASL.auth() - return True - - def _on_start_sasl(self, data=None): - """ - Callback used by SASL, called on each auth step - """ - if data: - self.Dispatcher.ProcessNonBlocking(data) - if not 'SASL' in self.__dict__: - # SASL is pluged out, possible disconnect - return - if self.SASL.startsasl == 'in-process': - return - self.onreceive(None) - if self.SASL.startsasl == 'failure': - # wrong user/pass, stop auth - if 'SASL' in self.__dict__: - self.SASL.PlugOut() - self.connected = None # FIXME: is this intended? We use ''elsewhere - self._on_sasl_auth(None) - elif self.SASL.startsasl == 'success': - nb_bind = auth_nb.NonBlockingBind.get_instance() - sm = self._caller.sm - if sm._owner and sm.resumption: - nb_bind.resuming = True - sm.set_owner(self) - self.Dispatcher.sm = sm - nb_bind.PlugIn(self) - self.on_auth(self, 'sasl') - return - - nb_bind.PlugIn(self) - self.onreceive(self._on_auth_bind) - return True - - def _on_auth_bind(self, data): - # FIXME: Why use this callback and not bind directly? - if data: - self.Dispatcher.ProcessNonBlocking(data) - if self.NonBlockingBind.bound is None: - return - self.NonBlockingBind.NonBlockingBind(self._Resource, self._on_sasl_auth) - return True - - def initRoster(self, version=''): - """ - Plug in the roster - """ - if not self.__dict__.has_key('NonBlockingRoster'): - return roster_nb.NonBlockingRoster.get_instance(version=version).PlugIn(self) - - def getRoster(self, on_ready=None, force=False): - """ - Return the Roster instance, previously plugging it in and requesting - roster from server if needed - """ - if self.__dict__.has_key('NonBlockingRoster'): - return self.NonBlockingRoster.getRoster(on_ready, force) - return None - - def sendPresence(self, jid=None, typ=None, requestRoster=0): - """ - Send some specific presence state. Can also request roster from server if - according agrument is set - """ - if requestRoster: - # FIXME: used somewhere? - roster_nb.NonBlockingRoster.get_instance().PlugIn(self) - self.send(dispatcher_nb.Presence(to=jid, typ=typ)) - -############################################################################### -### following methods are moved from blocking client class of xmpppy -############################################################################### - - def RegisterDisconnectHandler(self, handler): - """ - Register handler that will be called on disconnect - """ - self.disconnect_handlers.append(handler) - - def UnregisterDisconnectHandler(self, handler): - """ - Unregister handler that is called on disconnect - """ - self.disconnect_handlers.remove(handler) - - def DisconnectHandler(self): - """ - Default disconnect handler. Just raises an IOError. If you choosed to use - this class in your production client, override this method or at least - unregister it. - """ - raise IOError('Disconnected from server.') - - def get_connect_type(self): - """ - Return connection state. F.e.: None / 'tls' / 'plain+non_sasl' - """ - return self.connected - - def get_peerhost(self): - """ - Gets the ip address of the account, from which is made connection to the - server (e.g. IP and port of gajim's socket) - - We will create listening socket on the same ip - """ - # FIXME: tuple (ip, port) is expected (and checked for) but port num is - # useless - return self.socket.peerhost diff --git a/src/common/xmpp/dispatcher_nb.py b/src/common/xmpp/dispatcher_nb.py deleted file mode 100644 index 5c4a7a1c9..000000000 --- a/src/common/xmpp/dispatcher_nb.py +++ /dev/null @@ -1,639 +0,0 @@ -## dispatcher_nb.py -## based on dispatcher.py -## -## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov -## modified by Dimitur Kirov <dkirov@gmail.com> -## -## This program is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation; either version 2, or (at your option) -## any later version. -## -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. - - -""" -Main xmpp decision making logic. Provides library with methods to assign -different handlers to different XMPP stanzas and namespaces -""" - -import simplexml, sys, locale -import re -from xml.parsers.expat import ExpatError -from plugin import PlugIn -from protocol import (NS_STREAMS, NS_XMPP_STREAMS, NS_HTTP_BIND, Iq, Presence, - Message, Protocol, Node, Error, ERR_FEATURE_NOT_IMPLEMENTED, StreamError) -import logging -log = logging.getLogger('gajim.c.x.dispatcher_nb') - -#: default timeout to wait for response for our id -DEFAULT_TIMEOUT_SECONDS = 25 -outgoingID = 0 - -XML_DECLARATION = '<?xml version=\'1.0\'?>' - -# FIXME: ugly -class Dispatcher(): - """ - Why is this here - I needed to redefine Dispatcher for BOSH and easiest way - was to inherit original Dispatcher (now renamed to XMPPDispatcher). Trouble - is that reference used to access dispatcher instance is in Client attribute - named by __class__.__name__ of the dispatcher instance .. long story short: - - I wrote following to avoid changing each client.Dispatcher.whatever() in xmpp - - If having two kinds of dispatcher will go well, I will rewrite the dispatcher - references in other scripts - """ - - def PlugIn(self, client_obj, after_SASL=False, old_features=None): - if client_obj.protocol_type == 'XMPP': - XMPPDispatcher().PlugIn(client_obj) - elif client_obj.protocol_type == 'BOSH': - BOSHDispatcher().PlugIn(client_obj, after_SASL, old_features) - else: - assert False # should never be reached - - @classmethod - def get_instance(cls, *args, **kwargs): - """ - Factory Method for object creation - - Use this instead of directly initializing the class in order to make - unit testing much easier. - """ - return cls(*args, **kwargs) - - -class XMPPDispatcher(PlugIn): - """ - Handles XMPP stream and is the first who takes control over a fresh stanza - - Is plugged into NonBlockingClient but can be replugged to restart handled - stream headers (used by SASL f.e.). - """ - - def __init__(self): - PlugIn.__init__(self) - self.handlers = {} - self._expected = {} - self._defaultHandler = None - self._pendingExceptions = [] - self._eventHandler = None - self._cycleHandlers = [] - self._exported_methods=[self.RegisterHandler, self.RegisterDefaultHandler, - self.RegisterEventHandler, self.UnregisterCycleHandler, - self.RegisterCycleHandler, self.RegisterHandlerOnce, - self.UnregisterHandler, self.RegisterProtocol, - self.SendAndWaitForResponse, self.SendAndCallForResponse, - self.getAnID, self.Event, self.send] - - # Let the dispatcher know if there is support for stream management - self.sm = None - - # \ufddo -> \ufdef range - c = u'\ufdd0' - r = c.encode('utf8') - while (c < u'\ufdef'): - c = unichr(ord(c) + 1) - r += '|' + c.encode('utf8') - - # \ufffe-\uffff, \u1fffe-\u1ffff, ..., \u10fffe-\u10ffff - c = u'\ufffe' - r += '|' + c.encode('utf8') - r += '|' + unichr(ord(c) + 1).encode('utf8') - while (c < u'\U0010fffe'): - c = unichr(ord(c) + 0x10000) - r += '|' + c.encode('utf8') - r += '|' + unichr(ord(c) + 1).encode('utf8') - - self.invalid_chars_re = re.compile(r) - - def getAnID(self): - global outgoingID - outgoingID += 1 - return repr(outgoingID) - - def dumpHandlers(self): - """ - Return set of user-registered callbacks in it's internal format. Used - within the library to carry user handlers set over Dispatcher replugins - """ - return self.handlers - - def restoreHandlers(self, handlers): - """ - Restore user-registered callbacks structure from dump previously obtained - via dumpHandlers. Used within the library to carry user handlers set over - Dispatcher replugins. - """ - self.handlers = handlers - - def _init(self): - """ - Register default namespaces/protocols/handlers. Used internally - """ - # FIXME: inject dependencies, do not rely that they are defined by our - # owner - self.RegisterNamespace('unknown') - self.RegisterNamespace(NS_STREAMS) - self.RegisterNamespace(self._owner.defaultNamespace) - self.RegisterProtocol('iq', Iq) - self.RegisterProtocol('presence', Presence) - self.RegisterProtocol('message', Message) - self.RegisterDefaultHandler(self.returnStanzaHandler) - self.RegisterEventHandler(self._owner._caller._event_dispatcher) - self.on_responses = {} - - def plugin(self, owner): - """ - Plug the Dispatcher instance into Client class instance and send initial - stream header. Used internally - """ - self._init() - self._owner.lastErrNode = None - self._owner.lastErr = None - self._owner.lastErrCode = None - if hasattr(self._owner, 'StreamInit'): - self._owner.StreamInit() - else: - self.StreamInit() - - def plugout(self): - """ - Prepare instance to be destructed - """ - self.Stream.dispatch = None - self.Stream.features = None - self.Stream.destroy() - self._owner = None - self.Stream = None - - def StreamInit(self): - """ - Send an initial stream header - """ - self._owner.Connection.sendqueue = [] - self.Stream = simplexml.NodeBuilder() - self.Stream.dispatch = self.dispatch - self.Stream._dispatch_depth = 2 - self.Stream.stream_header_received = self._check_stream_start - self.Stream.features = None - self._metastream = Node('stream:stream') - self._metastream.setNamespace(self._owner.Namespace) - self._metastream.setAttr('version', '1.0') - self._metastream.setAttr('xmlns:stream', NS_STREAMS) - self._metastream.setAttr('to', self._owner.Server) - if locale.getdefaultlocale()[0]: - self._metastream.setAttr('xml:lang', - locale.getdefaultlocale()[0].split('_')[0]) - self._owner.send("%s%s>" % (XML_DECLARATION, str(self._metastream)[:-2])) - - def _check_stream_start(self, ns, tag, attrs): - if ns != NS_STREAMS or tag!='stream': - raise ValueError('Incorrect stream start: (%s,%s). Terminating.' - % (tag, ns)) - - def replace_non_character(self, data): - return re.sub(self.invalid_chars_re, u'\ufffd'.encode('utf-8'), data) - - def ProcessNonBlocking(self, data): - """ - Check incoming stream for data waiting - - :param data: data received from transports/IO sockets - :return: - 1) length of processed data if some data were processed; - 2) '0' string if no data were processed but link is alive; - 3) 0 (zero) if underlying connection is closed. - """ - # FIXME: - # When an error occurs we disconnect the transport directly. Client's - # disconnect method will never be called. - # Is this intended? - # also look at transports start_disconnect() - data = self.replace_non_character(data) - for handler in self._cycleHandlers: - handler(self) - if len(self._pendingExceptions) > 0: - _pendingException = self._pendingExceptions.pop() - raise _pendingException[0], _pendingException[1], _pendingException[2] - try: - self.Stream.Parse(data) - # end stream:stream tag received - if self.Stream and self.Stream.has_received_endtag(): - self._owner.disconnect(self.Stream.streamError) - return 0 - except ExpatError: - log.error('Invalid XML received from server. Forcing disconnect.') - self._owner.Connection.disconnect() - return 0 - except ValueError, e: - log.debug('ValueError: %s' % str(e)) - self._owner.Connection.pollend() - return 0 - if len(self._pendingExceptions) > 0: - _pendingException = self._pendingExceptions.pop() - raise _pendingException[0], _pendingException[1], _pendingException[2] - if len(data) == 0: - return '0' - return len(data) - - def RegisterNamespace(self, xmlns, order='info'): - """ - Create internal structures for newly registered namespace - - You can register handlers for this namespace afterwards. By default - one namespace is already registered - (jabber:client or jabber:component:accept depending on context. - """ - log.debug('Registering namespace "%s"' % xmlns) - self.handlers[xmlns] = {} - self.RegisterProtocol('unknown', Protocol, xmlns=xmlns) - self.RegisterProtocol('default', Protocol, xmlns=xmlns) - - def RegisterProtocol(self, tag_name, Proto, xmlns=None, order='info'): - """ - Used to declare some top-level stanza name to dispatcher - - Needed to start registering handlers for such stanzas. Iq, message and - presence protocols are registered by default. - """ - if not xmlns: - xmlns=self._owner.defaultNamespace - log.debug('Registering protocol "%s" as %s(%s)' %(tag_name, Proto, xmlns)) - self.handlers[xmlns][tag_name] = {type:Proto, 'default':[]} - - def RegisterNamespaceHandler(self, xmlns, handler, typ='', ns='', - makefirst=0, system=0): - """ - Register handler for processing all stanzas for specified namespace - """ - self.RegisterHandler('default', handler, typ, ns, xmlns, makefirst, - system) - - def RegisterHandler(self, name, handler, typ='', ns='', xmlns=None, - makefirst=False, system=False): - """ - Register user callback as stanzas handler of declared type - - Callback arguments: - dispatcher instance (for replying), incoming return of previous handlers. - The callback must raise xmpp.NodeProcessed just before return if it wants - to prevent other callbacks to be called with the same stanza as argument - _and_, more importantly library from returning stanza to sender with error set. - - :param name: name of stanza. F.e. "iq". - :param handler: user callback. - :param typ: value of stanza's "type" attribute. If not specified any - value will match - :param ns: namespace of child that stanza must contain. - :param makefirst: insert handler in the beginning of handlers list instead - of adding it to the end. Note that more common handlers i.e. w/o "typ" - and " will be called first nevertheless. - :param system: call handler even if NodeProcessed Exception were raised - already. - """ - if not xmlns: - xmlns=self._owner.defaultNamespace - log.debug('Registering handler %s for "%s" type->%s ns->%s(%s)' % - (handler, name, typ, ns, xmlns)) - if not typ and not ns: - typ='default' - if xmlns not in self.handlers: - self.RegisterNamespace(xmlns, 'warn') - if name not in self.handlers[xmlns]: - self.RegisterProtocol(name, Protocol, xmlns, 'warn') - if typ+ns not in self.handlers[xmlns][name]: - self.handlers[xmlns][name][typ+ns]=[] - if makefirst: - self.handlers[xmlns][name][typ+ns].insert(0, {'func':handler, - 'system':system}) - else: - self.handlers[xmlns][name][typ+ns].append({'func':handler, - 'system':system}) - - def RegisterHandlerOnce(self, name, handler, typ='', ns='', xmlns=None, - makefirst=0, system=0): - """ - Unregister handler after first call (not implemented yet) - """ - # FIXME Drop or implement - if not xmlns: - xmlns = self._owner.defaultNamespace - self.RegisterHandler(name, handler, typ, ns, xmlns, makefirst, system) - - def UnregisterHandler(self, name, handler, typ='', ns='', xmlns=None): - """ - Unregister handler. "typ" and "ns" must be specified exactly the same as - with registering. - """ - if not xmlns: - xmlns = self._owner.defaultNamespace - if not typ and not ns: - typ='default' - if xmlns not in self.handlers: - return - if name not in self.handlers[xmlns]: - return - if typ+ns not in self.handlers[xmlns][name]: - return - for pack in self.handlers[xmlns][name][typ+ns]: - if pack['func'] == handler: - try: - self.handlers[xmlns][name][typ+ns].remove(pack) - except ValueError: - pass - - def RegisterDefaultHandler(self, handler): - """ - Specify the handler that will be used if no NodeProcessed exception were - raised. This is returnStanzaHandler by default. - """ - self._defaultHandler = handler - - def RegisterEventHandler(self, handler): - """ - Register handler that will process events. F.e. "FILERECEIVED" event. See - common/connection: _event_dispatcher() - """ - self._eventHandler = handler - - def returnStanzaHandler(self, conn, stanza): - """ - Return stanza back to the sender with <feature-not-implemented/> error - set - """ - if stanza.getType() in ('get', 'set'): - conn._owner.send(Error(stanza, ERR_FEATURE_NOT_IMPLEMENTED)) - - def RegisterCycleHandler(self, handler): - """ - Register handler that will be called on every Dispatcher.Process() call - """ - if handler not in self._cycleHandlers: - self._cycleHandlers.append(handler) - - def UnregisterCycleHandler(self, handler): - """ - Unregister handler that will is called on every Dispatcher.Process() call - """ - if handler in self._cycleHandlers: - self._cycleHandlers.remove(handler) - - def Event(self, realm, event, data): - """ - Raise some event - - :param realm: scope of event. Usually a namespace. - :param event: the event itself. F.e. "SUCCESSFUL SEND". - :param data: data that comes along with event. Depends on event. - """ - if self._eventHandler: - self._eventHandler(realm, event, data) - else: - log.warning('Received unhandled event: %s' % event) - - def dispatch(self, stanza, session=None, direct=0): - """ - Main procedure that performs XMPP stanza recognition and calling - apppropriate handlers for it. Called by simplexml - """ - # FIXME: Where do we set session and direct. Why? What are those intended - # to do? - - #log.info('dispatch called: stanza = %s, session = %s, direct= %s' - # % (stanza, session, direct)) - if not session: - session = self - session.Stream._mini_dom = None - name = stanza.getName() - - if name == 'features': - self._owner.got_features = True - session.Stream.features = stanza - if name == 'error': - if stanza.getTag('see-other-host'): - self._owner.got_see_other_host = stanza - - xmlns = stanza.getNamespace() - - # log.info('in dispatch, getting ns for %s, and the ns is %s' - # % (stanza, xmlns)) - if xmlns not in self.handlers: - log.warn("Unknown namespace: " + xmlns) - xmlns = 'unknown' - # features stanza has been handled before - if name not in self.handlers[xmlns]: - if name != 'features': - log.warn("Unknown stanza: " + name) - else: - log.debug("Got %s/%s stanza" % (xmlns, name)) - name='unknown' - else: - log.debug("Got %s/%s stanza" % (xmlns, name)) - - if stanza.__class__.__name__ == 'Node': - # FIXME: this cannot work - stanza=self.handlers[xmlns][name][type](node=stanza) - - typ = stanza.getType() - if not typ: - typ = '' - stanza.props = stanza.getProperties() - ID = stanza.getID() - - # If server supports stream management - if self.sm and self.sm.enabled and (stanza.getName() != 'r' and - stanza.getName() != 'a' and stanza.getName() != 'enabled' and - stanza.getName() != 'resumed'): - # increments the number of stanzas that has been handled - self.sm.in_h = self.sm.in_h + 1 - list_ = ['default'] # we will use all handlers: - if typ in self.handlers[xmlns][name]: - list_.append(typ) # from very common... - for prop in stanza.props: - if prop in self.handlers[xmlns][name]: - list_.append(prop) - if typ and typ+prop in self.handlers[xmlns][name]: - list_.append(typ+prop) # ...to very particular - - chain = self.handlers[xmlns]['default']['default'] - for key in list_: - if key: - chain = chain + self.handlers[xmlns][name][key] - - if ID in session._expected: - user = 0 - if isinstance(session._expected[ID], tuple): - cb, args = session._expected[ID] - log.debug("Expected stanza arrived. Callback %s(%s) found!" % - (cb, args)) - try: - cb(session,stanza,**args) - except Exception, typ: - if typ.__class__.__name__ != 'NodeProcessed': - raise - else: - log.debug("Expected stanza arrived!") - session._expected[ID] = stanza - else: - user = 1 - for handler in chain: - if user or handler['system']: - try: - handler['func'](session, stanza) - except Exception, typ: - if typ.__class__.__name__ != 'NodeProcessed': - self._pendingExceptions.insert(0, sys.exc_info()) - return - user=0 - if user and self._defaultHandler: - self._defaultHandler(session, stanza) - - def _WaitForData(self, data): - """ - Internal wrapper around ProcessNonBlocking. Will check for - """ - if data is None: - return - res = self.ProcessNonBlocking(data) - # 0 result indicates that we have closed the connection, e.g. - # we have released dispatcher, so self._owner has no methods - if not res: - return - for (_id, _iq) in self._expected.items(): - if _iq is None: - # If the expected Stanza would have arrived, ProcessNonBlocking - # would have placed the reply stanza in there - continue - if _id in self.on_responses: - if len(self._expected) == 1: - self._owner.onreceive(None) - resp, args = self.on_responses[_id] - del self.on_responses[_id] - if args is None: - resp(_iq) - else: - resp(self._owner, _iq, **args) - del self._expected[_id] - - def SendAndWaitForResponse(self, stanza, timeout=None, func=None, args=None): - """ - Send stanza and wait for recipient's response to it. Will call transports - on_timeout callback if response is not retrieved in time - - Be aware: Only timeout of latest call of SendAndWait is active. - """ - if timeout is None: - timeout = DEFAULT_TIMEOUT_SECONDS - _waitid = self.send(stanza) - if func: - self.on_responses[_waitid] = (func, args) - if timeout: - self._owner.set_timeout(timeout) - self._owner.onreceive(self._WaitForData) - self._expected[_waitid] = None - return _waitid - - def SendAndCallForResponse(self, stanza, func=None, args=None): - """ - Put stanza on the wire and call back when recipient replies. Additional - callback arguments can be specified in args - """ - self.SendAndWaitForResponse(stanza, 0, func, args) - - def send(self, stanza, now=False): - """ - Wrap transports send method when plugged into NonBlockingClient. Makes - sure stanzas get ID and from tag. - """ - ID = None - if type(stanza) not in [type(''), type(u'')]: - if isinstance(stanza, Protocol): - ID = stanza.getID() - if ID is None: - stanza.setID(self.getAnID()) - ID = stanza.getID() - if self._owner._registered_name and not stanza.getAttr('from'): - stanza.setAttr('from', self._owner._registered_name) - - # If no ID then it is a whitespace - if self.sm and self.sm.enabled and ID: - self.sm.uqueue.append(stanza) - self.sm.out_h = self.sm.out_h + 1 - if len(self.sm.uqueue) > self.sm.max_queue: - self.sm.request_ack() - - self._owner.Connection.send(stanza, now) - return ID - - -class BOSHDispatcher(XMPPDispatcher): - - def PlugIn(self, owner, after_SASL=False, old_features=None): - self.old_features = old_features - self.after_SASL = after_SASL - XMPPDispatcher.PlugIn(self, owner) - - def StreamInit(self): - """ - Send an initial stream header - """ - self.Stream = simplexml.NodeBuilder() - self.Stream.dispatch = self.dispatch - self.Stream._dispatch_depth = 2 - self.Stream.stream_header_received = self._check_stream_start - self.Stream.features = self.old_features - - self._metastream = Node('stream:stream') - self._metastream.setNamespace(self._owner.Namespace) - self._metastream.setAttr('version', '1.0') - self._metastream.setAttr('xmlns:stream', NS_STREAMS) - self._metastream.setAttr('to', self._owner.Server) - if locale.getdefaultlocale()[0]: - self._metastream.setAttr('xml:lang', - locale.getdefaultlocale()[0].split('_')[0]) - - self.restart = True - self._owner.Connection.send_init(after_SASL=self.after_SASL) - - def StreamTerminate(self): - """ - Send a stream terminator - """ - self._owner.Connection.send_terminator() - - def ProcessNonBlocking(self, data=None): - if self.restart: - fromstream = self._metastream - fromstream.setAttr('from', fromstream.getAttr('to')) - fromstream.delAttr('to') - data = '%s%s>%s' % (XML_DECLARATION, str(fromstream)[:-2], data) - self.restart = False - return XMPPDispatcher.ProcessNonBlocking(self, data) - - def dispatch(self, stanza, session=None, direct=0): - if stanza.getName() == 'body' and stanza.getNamespace() == NS_HTTP_BIND: - - stanza_attrs = stanza.getAttrs() - if 'authid' in stanza_attrs: - # should be only in init response - # auth module expects id of stream in document attributes - self.Stream._document_attrs['id'] = stanza_attrs['authid'] - self._owner.Connection.handle_body_attrs(stanza_attrs) - - children = stanza.getChildren() - if children: - for child in children: - # if child doesn't have any ns specified, simplexml (or expat) - # thinks it's of parent's (BOSH body) namespace, so we have to - # rewrite it to jabber:client - if child.getNamespace() == NS_HTTP_BIND: - child.setNamespace(self._owner.defaultNamespace) - XMPPDispatcher.dispatch(self, child, session, direct) - else: - XMPPDispatcher.dispatch(self, stanza, session, direct) diff --git a/src/common/xmpp/features_nb.py b/src/common/xmpp/features_nb.py deleted file mode 100644 index 9dd39082e..000000000 --- a/src/common/xmpp/features_nb.py +++ /dev/null @@ -1,218 +0,0 @@ -## features.py -## -## Copyright (C) 2003-2004 Alexey "Snake" Nezhdanov -## Copyright (C) 2007 Julien Pivotto <roidelapluie@gmail.com> -## -## This program is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation; either version 2, or (at your option) -## any later version. -## -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. - -# $Id: features.py,v 1.22 2005/09/30 20:13:04 mikealbon Exp $ - -""" -Different stuff that wasn't worth separating it into modules -(Registration, Privacy Lists, ...) -""" - -from protocol import NS_REGISTER, NS_PRIVACY, NS_DATA, Iq, isResultNode, Node - -def _on_default_response(disp, iq, cb): - def _on_response(resp): - if isResultNode(resp): - if cb: - cb(True) - elif cb: - cb(False) - disp.SendAndCallForResponse(iq, _on_response) - -############################################################################### -### Registration -############################################################################### - -REGISTER_DATA_RECEIVED = 'REGISTER DATA RECEIVED' - -def getRegInfo(disp, host, info={}, sync=True): - """ - Get registration form from remote host. Info dict can be prefilled - :param disp: plugged dispatcher instance - :param info: dict, like {'username':'joey'}. - - See JEP-0077 for details. - """ - iq=Iq('get', NS_REGISTER, to=host) - for i in info.keys(): - iq.setTagData(i, info[i]) - if sync: - disp.SendAndCallForResponse(iq, lambda resp: - _ReceivedRegInfo(disp.Dispatcher, resp, host)) - else: - disp.SendAndCallForResponse(iq, _ReceivedRegInfo, {'agent': host }) - -def _ReceivedRegInfo(con, resp, agent): - Iq('get', NS_REGISTER, to=agent) - if not isResultNode(resp): - error_msg = resp.getErrorMsg() - con.Event(NS_REGISTER, REGISTER_DATA_RECEIVED, (agent, None, False, error_msg, '')) - return - tag=resp.getTag('query', namespace=NS_REGISTER) - if not tag: - error_msg = resp.getErrorMsg() - con.Event(NS_REGISTER, REGISTER_DATA_RECEIVED, (agent, None, False, error_msg, '')) - return - df=tag.getTag('x', namespace=NS_DATA) - if df: - con.Event(NS_REGISTER, REGISTER_DATA_RECEIVED, (agent, df, True, '', - tag)) - return - df={} - for i in resp.getQueryPayload(): - if not isinstance(i, Node): - continue - df[i.getName()] = i.getData() - con.Event(NS_REGISTER, REGISTER_DATA_RECEIVED, (agent, df, False, '', '')) - -def register(disp, host, info, cb, args=None): - """ - Perform registration on remote server with provided info - - If registration fails you can get additional info from the dispatcher's - owner attributes lastErrNode, lastErr and lastErrCode. - """ - iq=Iq('set', NS_REGISTER, to=host) - if not isinstance(info, dict): - info=info.asDict() - for i in info.keys(): - iq.setTag('query').setTagData(i, info[i]) - disp.SendAndCallForResponse(iq, cb, args) - -def unregister(disp, host, cb): - """ - Unregisters with host (permanently removes account). Returns true on success - """ - iq = Iq('set', NS_REGISTER, to=host, payload=[Node('remove')]) - _on_default_response(disp, iq, cb) - -def changePasswordTo(disp, newpassword, host=None, cb = None): - """ - Changes password on specified or current (if not specified) server. Returns - true on success. - """ - if not host: - host = disp._owner.Server - iq = Iq('set', NS_REGISTER, to=host, payload=[Node('username', - payload=[disp._owner.Server]), Node('password', payload=[newpassword])]) - _on_default_response(disp, iq, cb) - -############################################################################### -### Privacy List -############################################################################### - -PL_TYPE_JID = 'jid' -PL_TYPE_GROUP = 'group' -PL_TYPE_SUBC = 'subscription' -PL_ACT_ALLOW = 'allow' -PL_ACT_DENY = 'deny' - -PRIVACY_LISTS_RECEIVED = 'PRIVACY LISTS RECEIVED' -PRIVACY_LIST_RECEIVED = 'PRIVACY LIST RECEIVED' -PRIVACY_LISTS_ACTIVE_DEFAULT = 'PRIVACY LISTS ACTIVE DEFAULT' - -def getPrivacyLists(disp): - """ - Request privacy lists from connected server. Returns dictionary of existing - lists on success. - """ - iq = Iq('get', NS_PRIVACY) - def _on_response(resp): - dict_ = {'lists': []} - if not isResultNode(resp): - disp.Event(NS_PRIVACY, PRIVACY_LISTS_RECEIVED, (False)) - return - for list_ in resp.getQueryPayload(): - if list_.getName()=='list': - dict_['lists'].append(list_.getAttr('name')) - else: - dict_[list_.getName()]=list_.getAttr('name') - disp.Event(NS_PRIVACY, PRIVACY_LISTS_RECEIVED, (dict_)) - disp.SendAndCallForResponse(iq, _on_response) - -def getActiveAndDefaultPrivacyLists(disp): - iq = Iq('get', NS_PRIVACY) - def _on_response(resp): - dict_ = {'active': '', 'default': ''} - if not isResultNode(resp): - disp.Event(NS_PRIVACY, PRIVACY_LISTS_ACTIVE_DEFAULT, (False)) - return - for list_ in resp.getQueryPayload(): - if list_.getName() == 'active': - dict_['active'] = list_.getAttr('name') - elif list_.getName() == 'default': - dict_['default'] = list_.getAttr('name') - disp.Event(NS_PRIVACY, PRIVACY_LISTS_ACTIVE_DEFAULT, (dict_)) - disp.SendAndCallForResponse(iq, _on_response) - -def getPrivacyList(disp, listname): - """ - Request specific privacy list listname. Returns list of XML nodes (rules) - taken from the server responce. - """ - def _on_response(resp): - if not isResultNode(resp): - disp.Event(NS_PRIVACY, PRIVACY_LIST_RECEIVED, (False)) - return - disp.Event(NS_PRIVACY, PRIVACY_LIST_RECEIVED, (resp)) - iq = Iq('get', NS_PRIVACY, payload=[Node('list', {'name': listname})]) - disp.SendAndCallForResponse(iq, _on_response) - -def setActivePrivacyList(disp, listname=None, typ='active', cb=None): - """ - Switch privacy list 'listname' to specified type. By default the type is - 'active'. Returns true on success. - """ - if listname: - attrs={'name':listname} - else: - attrs={} - iq = Iq('set', NS_PRIVACY, payload=[Node(typ, attrs)]) - _on_default_response(disp, iq, cb) - -def setDefaultPrivacyList(disp, listname=None): - """ - Set the default privacy list as 'listname'. Returns true on success - """ - return setActivePrivacyList(disp, listname, 'default') - -def setPrivacyList(disp, listname, tags): - """ - Set the ruleset - - 'list' should be the simpleXML node formatted according to RFC 3921 - (XMPP-IM) I.e. Node('list',{'name':listname},payload=[...]). - - Returns true on success. - """ - iq = Iq('set', NS_PRIVACY, xmlns = '') - list_query = iq.getTag('query').setTag('list', {'name': listname}) - for item in tags: - if 'type' in item and 'value' in item: - item_tag = list_query.setTag('item', {'action': item['action'], - 'order': item['order'], 'type': item['type'], - 'value': item['value']}) - else: - item_tag = list_query.setTag('item', {'action': item['action'], - 'order': item['order']}) - if 'child' in item: - for child_tag in item['child']: - item_tag.setTag(child_tag) - _on_default_response(disp, iq, None) - -def delPrivacyList(disp, listname, cb=None): - ''' Deletes privacy list 'listname'. Returns true on success. ''' - iq = Iq('set', NS_PRIVACY, payload=[Node('list', {'name':listname})]) - _on_default_response(disp, iq, cb) diff --git a/src/common/xmpp/idlequeue.py b/src/common/xmpp/idlequeue.py deleted file mode 100644 index 86e33418f..000000000 --- a/src/common/xmpp/idlequeue.py +++ /dev/null @@ -1,550 +0,0 @@ -## idlequeue.py -## -## Copyright (C) 2006 Dimitur Kirov <dkirov@gmail.com> -## -## This program is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation; either version 2, or (at your option) -## any later version. -## -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. - - -""" -Idlequeues are Gajim's network heartbeat. Transports can be plugged as idle -objects and be informed about possible IO -""" - -import os -import select -import logging -log = logging.getLogger('gajim.c.x.idlequeue') - -# needed for get_idleqeue -try: - import gobject - HAVE_GOBJECT = True -except ImportError: - HAVE_GOBJECT = False - -# needed for idlecommand -if os.name == 'nt': - from subprocess import * # python24 only. we ask this for Windows -elif os.name == 'posix': - import fcntl - -FLAG_WRITE = 20 # write only -FLAG_READ = 19 # read only -FLAG_READ_WRITE = 23 # read and write -FLAG_CLOSE = 16 # wait for close - -PENDING_READ = 3 # waiting read event -PENDING_WRITE = 4 # waiting write event -IS_CLOSED = 16 # channel closed - - -def get_idlequeue(): - """ - Get an appropriate idlequeue - """ - if os.name == 'nt': - # gobject.io_add_watch does not work on windows - return SelectIdleQueue() - else: - if HAVE_GOBJECT: - # Gajim's default Idlequeue - return GlibIdleQueue() - else: - # GUI less implementation - return SelectIdleQueue() - - -class IdleObject: - """ - Idle listener interface. Listed methods are called by IdleQueue. - """ - - def __init__(self): - self.fd = -1 #: filedescriptor, must be unique for each IdleObject - - def pollend(self): - """ - Called on stream failure - """ - pass - - def pollin(self): - """ - Called on new read event - """ - pass - - def pollout(self): - """ - Called on new write event (connect in sockets is a pollout) - """ - pass - - def read_timeout(self): - """ - Called when timeout happened - """ - pass - - -class IdleCommand(IdleObject): - """ - Can be subclassed to execute commands asynchronously by the idlequeue. - Result will be optained via file descriptor of created pipe - """ - - def __init__(self, on_result): - IdleObject.__init__(self) - # how long (sec.) to wait for result ( 0 - forever ) - # it is a class var, instead of a constant and we can override it. - self.commandtimeout = 0 - # when we have some kind of result (valid, ot not) we call this handler - self.result_handler = on_result - # if it is True, we can safetely execute the command - self.canexecute = True - self.idlequeue = None - self.result ='' - - def set_idlequeue(self, idlequeue): - self.idlequeue = idlequeue - - def _return_result(self): - if self.result_handler: - self.result_handler(self.result) - self.result_handler = None - - def _compose_command_args(self): - return ['echo', 'da'] - - def _compose_command_line(self): - """ - Return one line representation of command and its arguments - """ - return reduce(lambda left, right: left + ' ' + right, - self._compose_command_args()) - - def wait_child(self): - if self.pipe.poll() is None: - # result timeout - if self.endtime < self.idlequeue.current_time(): - self._return_result() - self.pipe.stdout.close() - self.pipe.stdin.close() - else: - # child is still active, continue to wait - self.idlequeue.set_alarm(self.wait_child, 0.1) - else: - # child has quit - self.result = self.pipe.stdout.read() - self._return_result() - self.pipe.stdout.close() - self.pipe.stdin.close() - - def start(self): - if not self.canexecute: - self.result = '' - self._return_result() - return - if os.name == 'nt': - self._start_nt() - elif os.name == 'posix': - self._start_posix() - - def _start_nt(self): - # if gajim is started from noninteraactive shells stdin is closed and - # cannot be forwarded, so we have to keep it open - self.pipe = Popen(self._compose_command_args(), stdout=PIPE, - bufsize = 1024, shell = True, stderr = STDOUT, stdin = PIPE) - if self.commandtimeout >= 0: - self.endtime = self.idlequeue.current_time() + self.commandtimeout - self.idlequeue.set_alarm(self.wait_child, 0.1) - - def _start_posix(self): - self.pipe = os.popen(self._compose_command_line()) - self.fd = self.pipe.fileno() - fcntl.fcntl(self.pipe, fcntl.F_SETFL, os.O_NONBLOCK) - self.idlequeue.plug_idle(self, False, True) - if self.commandtimeout >= 0: - self.idlequeue.set_read_timeout(self.fd, self.commandtimeout) - - def end(self): - self.idlequeue.unplug_idle(self.fd) - try: - self.pipe.close() - except: - pass - - def pollend(self): - self.idlequeue.remove_timeout(self.fd) - self.end() - self._return_result() - - def pollin(self): - try: - res = self.pipe.read() - except Exception, e: - res = '' - if res == '': - return self.pollend() - else: - self.result += res - - def read_timeout(self): - self.end() - self._return_result() - - -class IdleQueue: - """ - IdleQueue provide three distinct time based features. Uses select.poll() - - 1. Alarm timeout: Execute a callback after foo seconds - 2. Timeout event: Call read_timeout() of an plugged object if a timeout - has been set, but not removed in time. - 3. Check file descriptor of plugged objects for read, write and error - events - """ - - # (timeout, boolean) - # Boolean is True if timeout is specified in seconds, False means miliseconds - PROCESS_TIMEOUT = (100, False) - - def __init__(self): - self.queue = {} - - # when there is a timeout it executes obj.read_timeout() - # timeout is not removed automatically! - # {fd1: {timeout1: func1, timeout2: func2}} - # timout are unique (timeout1 must be != timeout2) - # If func1 is None, read_time function is called - self.read_timeouts = {} - - # cb, which are executed after XX sec., alarms are removed automatically - self.alarms = {} - self._init_idle() - - def _init_idle(self): - """ - Hook method for subclassed. Will be called by __init__ - """ - self.selector = select.poll() - - def set_alarm(self, alarm_cb, seconds): - """ - Set up a new alarm. alarm_cb will be called after specified seconds. - """ - alarm_time = self.current_time() + seconds - # almost impossible, but in case we have another alarm_cb at this time - if alarm_time in self.alarms: - self.alarms[alarm_time].append(alarm_cb) - else: - self.alarms[alarm_time] = [alarm_cb] - return alarm_time - - def remove_alarm(self, alarm_cb, alarm_time): - """ - Remove alarm callback alarm_cb scheduled on alarm_time. Returns True if - it was removed sucessfully, otherwise False - """ - if not alarm_time in self.alarms: - return False - i = -1 - for i in range(len(self.alarms[alarm_time])): - # let's not modify the list inside the loop - if self.alarms[alarm_time][i] is alarm_cb: - break - if i != -1: - del self.alarms[alarm_time][i] - if self.alarms[alarm_time] == []: - del self.alarms[alarm_time] - return True - else: - return False - - def remove_timeout(self, fd, timeout=None): - """ - Remove the read timeout - """ - log.info('read timeout removed for fd %s' % fd) - if fd in self.read_timeouts: - if timeout: - if timeout in self.read_timeouts[fd]: - del(self.read_timeouts[fd][timeout]) - if len(self.read_timeouts[fd]) == 0: - del(self.read_timeouts[fd]) - else: - del(self.read_timeouts[fd]) - - def set_read_timeout(self, fd, seconds, func=None): - """ - Seta a new timeout. If it is not removed after specified seconds, - func or obj.read_timeout() will be called - - A filedescriptor fd can have several timeouts. - """ - log_txt = 'read timeout set for fd %s on %s seconds' % (fd, seconds) - if func: - log_txt += ' with function ' + str(func) - log.info(log_txt) - timeout = self.current_time() + seconds - if fd in self.read_timeouts: - self.read_timeouts[fd][timeout] = func - else: - self.read_timeouts[fd] = {timeout: func} - - def _check_time_events(self): - """ - Execute and remove alarm callbacks and execute func() or read_timeout() - for plugged objects if specified time has ellapsed - """ - current_time = self.current_time() - - for fd, timeouts in self.read_timeouts.items(): - if fd not in self.queue: - self.remove_timeout(fd) - continue - for timeout, func in timeouts.items(): - if timeout > current_time: - continue - if func: - log.debug('Calling %s for fd %s' % (func, fd)) - func() - else: - log.debug('Calling read_timeout for fd %s' % fd) - self.queue[fd].read_timeout() - self.remove_timeout(fd, timeout) - - times = self.alarms.keys() - for alarm_time in times: - if alarm_time > current_time: - continue - if alarm_time in self.alarms: - for callback in self.alarms[alarm_time]: - callback() - if alarm_time in self.alarms: - del(self.alarms[alarm_time]) - - def plug_idle(self, obj, writable=True, readable=True): - """ - Plug an IdleObject into idlequeue. Filedescriptor fd must be set - - :param obj: the IdleObject - :param writable: True if obj has data to sent - :param readable: True if obj expects data to be reiceived - """ - if obj.fd == -1: - return - if obj.fd in self.queue: - self.unplug_idle(obj.fd) - self.queue[obj.fd] = obj - if writable: - if not readable: - flags = FLAG_WRITE - else: - flags = FLAG_READ_WRITE - else: - if readable: - flags = FLAG_READ - else: - # when we paused a FT, we expect only a close event - flags = FLAG_CLOSE - self._add_idle(obj.fd, flags) - - def _add_idle(self, fd, flags): - """ - Hook method for subclasses, called by plug_idle - """ - self.selector.register(fd, flags) - - def unplug_idle(self, fd): - """ - Remove plugged IdleObject, specified by filedescriptor fd - """ - if fd in self.queue: - del(self.queue[fd]) - self._remove_idle(fd) - - def current_time(self): - from time import time - return time() - - def _remove_idle(self, fd): - """ - Hook method for subclassed, called by unplug_idle - """ - self.selector.unregister(fd) - - def _process_events(self, fd, flags): - obj = self.queue.get(fd) - if obj is None: - self.unplug_idle(fd) - return False - - read_write = False - if flags & PENDING_READ: - #print 'waiting read on %d, flags are %d' % (fd, flags) - obj.pollin() - read_write = True - - elif flags & PENDING_WRITE and not flags & IS_CLOSED: - obj.pollout() - read_write = True - - if flags & IS_CLOSED: - # io error, don't expect more events - self.remove_timeout(obj.fd) - self.unplug_idle(obj.fd) - obj.pollend() - return False - - if read_write: - return True - return False - - def process(self): - """ - Process idlequeue. Check for any pending timeout or alarm events. Call - IdleObjects on possible and requested read, write and error events on - their file descriptors - - Call this in regular intervals. - """ - if not self.queue: - # check for timeouts/alert also when there are no active fds - self._check_time_events() - return True - try: - waiting_descriptors = self.selector.poll(0) - except select.error, e: - waiting_descriptors = [] - if e[0] != 4: # interrupt - raise - for fd, flags in waiting_descriptors: - self._process_events(fd, flags) - self._check_time_events() - return True - - -class SelectIdleQueue(IdleQueue): - """ - Extends IdleQueue to use select.select() for polling - - This class exisists for the sake of gtk2.8 on windows, which doesn't seem to - support io_add_watch properly (yet) - """ - - def _init_idle(self): - """ - Create a dict, which maps file/pipe/sock descriptor to glib event id - """ - self.read_fds = {} - self.write_fds = {} - self.error_fds = {} - - def _add_idle(self, fd, flags): - """ - This method is called when we plug a new idle object. Remove descriptor - to read/write/error lists, according flags - """ - if flags & 3: - self.read_fds[fd] = fd - if flags & 4: - self.write_fds[fd] = fd - self.error_fds[fd] = fd - - def _remove_idle(self, fd): - """ - This method is called when we unplug a new idle object. Remove descriptor - from read/write/error lists - """ - if fd in self.read_fds: - del(self.read_fds[fd]) - if fd in self.write_fds: - del(self.write_fds[fd]) - if fd in self.error_fds: - del(self.error_fds[fd]) - - def process(self): - if not self.write_fds and not self.read_fds: - self._check_time_events() - return True - try: - waiting_descriptors = select.select(self.read_fds.keys(), - self.write_fds.keys(), self.error_fds.keys(), 0) - except select.error, e: - waiting_descriptors = ((), (), ()) - if e[0] != 4: # interrupt - raise - for fd in waiting_descriptors[0]: - q = self.queue.get(fd) - if q: - q.pollin() - for fd in waiting_descriptors[1]: - q = self.queue.get(fd) - if q: - q.pollout() - for fd in waiting_descriptors[2]: - q = self.queue.get(fd) - if q: - q.pollend() - self._check_time_events() - return True - - -class GlibIdleQueue(IdleQueue): - """ - Extends IdleQueue to use glib io_add_wath, instead of select/poll In another - 'non gui' implementation of Gajim IdleQueue can be used safetly - """ - - # (timeout, boolean) - # Boolean is True if timeout is specified in seconds, False means miliseconds - PROCESS_TIMEOUT = (2, True) - - def _init_idle(self): - """ - Creates a dict, which maps file/pipe/sock descriptor to glib event id - """ - self.events = {} - # time() is already called in glib, we just get the last value - # overrides IdleQueue.current_time() - self.current_time = gobject.get_current_time - - def _add_idle(self, fd, flags): - """ - This method is called when we plug a new idle object. Start listening for - events from fd - """ - res = gobject.io_add_watch(fd, flags, self._process_events, - priority=gobject.PRIORITY_LOW) - # store the id of the watch, so that we can remove it on unplug - self.events[fd] = res - - def _process_events(self, fd, flags): - try: - return IdleQueue._process_events(self, fd, flags) - except Exception: - self._remove_idle(fd) - self._add_idle(fd, flags) - raise - - def _remove_idle(self, fd): - """ - This method is called when we unplug a new idle object. Stop listening - for events from fd - """ - if not fd in self.events: - return - gobject.source_remove(self.events[fd]) - del(self.events[fd]) - - def process(self): - self._check_time_events() diff --git a/src/common/xmpp/plugin.py b/src/common/xmpp/plugin.py deleted file mode 100644 index 5ef82c931..000000000 --- a/src/common/xmpp/plugin.py +++ /dev/null @@ -1,96 +0,0 @@ -## plugin.py -## -## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov -## -## This program is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation; either version 2, or (at your option) -## any later version. -## -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. - -# $Id: client.py,v 1.52 2006/01/02 19:40:55 normanr Exp $ - -""" -Provides PlugIn class functionality to develop extentions for xmpppy -""" - -import logging -log = logging.getLogger('gajim.c.x.plugin') - -class PlugIn: - """ - Abstract xmpppy plugin infrastructure code, providing plugging in/out and - debugging functionality - - Inherit to develop pluggable objects. No code change on the owner class - required (the object where we plug into) - - For every instance of PlugIn class the 'owner' is the class in what the plug - was plugged. - """ - - def __init__(self): - self._exported_methods=[] - - def PlugIn(self, owner): - """ - Attach to owner and register ourself and our _exported_methods in it. - If defined by a subclass, call self.plugin(owner) to execute hook - code after plugging - """ - self._owner=owner - log.info('Plugging %s __INTO__ %s' % (self, self._owner)) - if self.__class__.__name__ in owner.__dict__: - log.debug('Plugging ignored: another instance already plugged.') - return - self._old_owners_methods=[] - for method in self._exported_methods: - if method.__name__ in owner.__dict__: - self._old_owners_methods.append(owner.__dict__[method.__name__]) - owner.__dict__[method.__name__]=method - if self.__class__.__name__.endswith('Dispatcher'): - # FIXME: I need BOSHDispatcher or XMPPDispatcher on .Dispatcher - # there must be a better way.. - owner.__dict__['Dispatcher']=self - else: - owner.__dict__[self.__class__.__name__]=self - - # Execute hook - if hasattr(self, 'plugin'): - return self.plugin(owner) - - def PlugOut(self): - """ - Unregister our _exported_methods from owner and detach from it. - If defined by a subclass, call self.plugout() after unplugging to execute - hook code - """ - log.info('Plugging %s __OUT__ of %s.' % (self, self._owner)) - for method in self._exported_methods: - del self._owner.__dict__[method.__name__] - for method in self._old_owners_methods: - self._owner.__dict__[method.__name__]=method - # FIXME: Dispatcher workaround - if self.__class__.__name__.endswith('Dispatcher'): - del self._owner.__dict__['Dispatcher'] - else: - del self._owner.__dict__[self.__class__.__name__] - # Execute hook - if hasattr(self, 'plugout'): - return self.plugout() - del self._owner - - @classmethod - def get_instance(cls, *args, **kwargs): - """ - Factory Method for object creation - - Use this instead of directly initializing the class in order to make - unit testing easier. For testing, this method can be patched to inject - mock objects. - """ - return cls(*args, **kwargs) diff --git a/src/common/xmpp/protocol.py b/src/common/xmpp/protocol.py deleted file mode 100644 index d4c5fb254..000000000 --- a/src/common/xmpp/protocol.py +++ /dev/null @@ -1,1489 +0,0 @@ -## protocol.py -## -## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov -## -## This program is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation; either version 2, or (at your option) -## any later version. -## -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. - -# $Id: protocol.py,v 1.52 2006/01/09 22:08:57 normanr Exp $ - -""" -Protocol module contains tools that are needed for processing of xmpp-related -data structures, including jabber-objects like JID or different stanzas and -sub- stanzas) handling routines -""" - -from simplexml import Node, NodeBuilder -import time -import string -import hashlib - -def ascii_upper(s): - trans_table = string.maketrans(string.ascii_lowercase, - string.ascii_uppercase) - return s.translate(trans_table) - -NS_ACTIVITY = 'http://jabber.org/protocol/activity' # XEP-0108 -NS_ADDRESS = 'http://jabber.org/protocol/address' # XEP-0033 -NS_AGENTS = 'jabber:iq:agents' -NS_AMP = 'http://jabber.org/protocol/amp' -NS_AMP_ERRORS = NS_AMP + '#errors' -NS_ARCHIVE = 'urn:xmpp:archive' # XEP-0136 -NS_ARCHIVE_AUTO = NS_ARCHIVE + ':auto' # XEP-0136 -NS_ARCHIVE_MANAGE = NS_ARCHIVE + ':manage' # XEP-0136 -NS_ARCHIVE_MANUAL = NS_ARCHIVE + ':manual' # XEP-0136 -NS_ARCHIVE_PREF = NS_ARCHIVE + ':pref' -NS_ATOM = 'http://www.w3.org/2005/Atom' -NS_ATTENTION = 'urn:xmpp:attention:0' # XEP-0224 -NS_AUTH = 'jabber:iq:auth' -NS_AVATAR = 'http://www.xmpp.org/extensions/xep-0084.html#ns-metadata' -NS_BIND = 'urn:ietf:params:xml:ns:xmpp-bind' -NS_BOB = 'urn:xmpp:bob' # XEP-0231 -NS_BOOKMARKS = 'storage:bookmarks' # XEP-0048 -NS_BROWSE = 'jabber:iq:browse' -NS_BROWSING = 'http://jabber.org/protocol/browsing' # XEP-0195 -NS_BYTESTREAM = 'http://jabber.org/protocol/bytestreams' # XEP-0065 -NS_CAPS = 'http://jabber.org/protocol/caps' # XEP-0115 -NS_CAPTCHA = 'urn:xmpp:captcha' # XEP-0158 -NS_CARBONS = 'urn:xmpp:carbons:1' # XEP-0280 -NS_CHATSTATES = 'http://jabber.org/protocol/chatstates' # XEP-0085 -NS_CHATTING = 'http://jabber.org/protocol/chatting' # XEP-0194 -NS_CLIENT = 'jabber:client' -NS_CONDITIONS = 'urn:xmpp:muc:conditions:0' # XEP-0306 -NS_COMMANDS = 'http://jabber.org/protocol/commands' -NS_COMPONENT_ACCEPT = 'jabber:component:accept' -NS_COMPONENT_1 = 'http://jabberd.jabberstudio.org/ns/component/1.0' -NS_COMPRESS = 'http://jabber.org/protocol/compress' # XEP-0138 -NS_CONFERENCE = 'jabber:x:conference' -NS_DATA = 'jabber:x:data' # XEP-0004 -NS_DATA_MEDIA = 'urn:xmpp:media-element' # XEP-0221 -NS_DELAY = 'jabber:x:delay' -NS_DELAY2 = 'urn:xmpp:delay' -NS_DIALBACK = 'jabber:server:dialback' -NS_DISCO = 'http://jabber.org/protocol/disco' -NS_DISCO_INFO = NS_DISCO + '#info' -NS_DISCO_ITEMS = NS_DISCO + '#items' -NS_ENCRYPTED = 'jabber:x:encrypted' # XEP-0027 -NS_ESESSION = 'http://www.xmpp.org/extensions/xep-0116.html#ns' -NS_ESESSION_INIT = 'http://www.xmpp.org/extensions/xep-0116.html#ns-init' # XEP-0116 -NS_EVENT = 'jabber:x:event' # XEP-0022 -NS_FEATURE = 'http://jabber.org/protocol/feature-neg' -NS_FILE = 'http://jabber.org/protocol/si/profile/file-transfer' # XEP-0096 -NS_FORWARD = 'urn:xmpp:forward:0' # XEP-0297 -NS_GAMING = 'http://jabber.org/protocol/gaming' # XEP-0196 -NS_GATEWAY = 'jabber:iq:gateway' # XEP-0100 -NS_GEOLOC = 'http://jabber.org/protocol/geoloc' # XEP-0080 -NS_GROUPCHAT = 'gc-1.0' -NS_HTTP_AUTH = 'http://jabber.org/protocol/http-auth' # XEP-0070 -NS_HTTP_BIND = 'http://jabber.org/protocol/httpbind' # XEP-0124 -NS_IBB = 'http://jabber.org/protocol/ibb' -NS_INVISIBLE = 'presence-invisible' # Jabberd2 -NS_IQ = 'iq' # Jabberd2 -NS_JINGLE ='urn:xmpp:jingle:1' # XEP-0166 -NS_JINGLE_ERRORS = 'urn:xmpp:jingle:errors:1' # XEP-0166 -NS_JINGLE_RTP = 'urn:xmpp:jingle:apps:rtp:1' # XEP-0167 -NS_JINGLE_RTP_AUDIO = 'urn:xmpp:jingle:apps:rtp:audio' # XEP-0167 -NS_JINGLE_RTP_VIDEO = 'urn:xmpp:jingle:apps:rtp:video' # XEP-0167 -NS_JINGLE_FILE_TRANSFER ='urn:xmpp:jingle:apps:file-transfer:3' # XEP-0234 -NS_JINGLE_XTLS='urn:xmpp:jingle:security:xtls:0' # XTLS: EXPERIMENTAL security layer of jingle -NS_JINGLE_RAW_UDP = 'urn:xmpp:jingle:transports:raw-udp:1' # XEP-0177 -NS_JINGLE_ICE_UDP = 'urn:xmpp:jingle:transports:ice-udp:1' # XEP-0176 -NS_JINGLE_BYTESTREAM ='urn:xmpp:jingle:transports:s5b:1' # XEP-0260 -NS_JINGLE_IBB = 'urn:xmpp:jingle:transports:ibb:1' # XEP-0261 -NS_LAST = 'jabber:iq:last' -NS_LOCATION = 'http://jabber.org/protocol/geoloc' # XEP-0080 -NS_MESSAGE = 'message' # Jabberd2 -NS_MOOD = 'http://jabber.org/protocol/mood' # XEP-0107 -NS_MUC = 'http://jabber.org/protocol/muc' -NS_MUC_USER = NS_MUC + '#user' -NS_MUC_ADMIN = NS_MUC + '#admin' -NS_MUC_OWNER = NS_MUC + '#owner' -NS_MUC_UNIQUE = NS_MUC + '#unique' -NS_MUC_CONFIG = NS_MUC + '#roomconfig' -NS_NICK = 'http://jabber.org/protocol/nick' # XEP-0172 -NS_OFFLINE = 'http://www.jabber.org/jeps/jep-0030.html' # XEP-0013 -NS_PHYSLOC = 'http://jabber.org/protocol/physloc' # XEP-0112 -NS_PING = 'urn:xmpp:ping' # XEP-0199 -NS_PRESENCE = 'presence' # Jabberd2 -NS_PRIVACY = 'jabber:iq:privacy' -NS_PRIVATE = 'jabber:iq:private' -NS_PROFILE = 'http://jabber.org/protocol/profile' # XEP-0154 -NS_PUBSUB = 'http://jabber.org/protocol/pubsub' # XEP-0060 -NS_PUBSUB_EVENT = 'http://jabber.org/protocol/pubsub#event' -NS_PUBSUB_PUBLISH_OPTIONS = NS_PUBSUB + '#publish-options' # XEP-0060 -NS_PUBSUB_OWNER = 'http://jabber.org/protocol/pubsub#owner' # XEP-0060 -NS_REGISTER = 'jabber:iq:register' -NS_ROSTER = 'jabber:iq:roster' -NS_ROSTERNOTES = 'storage:rosternotes' -NS_ROSTERX = 'http://jabber.org/protocol/rosterx' # XEP-0144 -NS_ROSTER_VER = 'urn:xmpp:features:rosterver' # XEP-0273 -NS_RPC = 'jabber:iq:rpc' # XEP-0009 -NS_RSM = 'http://jabber.org/protocol/rsm' -NS_SASL = 'urn:ietf:params:xml:ns:xmpp-sasl' -NS_SECLABEL = 'urn:xmpp:sec-label:0' -NS_SECLABEL_CATALOG = 'urn:xmpp:sec-label:catalog:2' -NS_SEARCH = 'jabber:iq:search' -NS_SERVER = 'jabber:server' -NS_SESSION = 'urn:ietf:params:xml:ns:xmpp-session' -NS_SI = 'http://jabber.org/protocol/si' # XEP-0096 -NS_SI_PUB = 'http://jabber.org/protocol/sipub' # XEP-0137 -NS_SIGNED = 'jabber:x:signed' # XEP-0027 -NS_SSN = 'urn:xmpp:ssn' # XEP-0155 -NS_STANZA_CRYPTO = 'http://www.xmpp.org/extensions/xep-0200.html#ns' # XEP-0200 -NS_STANZAS = 'urn:ietf:params:xml:ns:xmpp-stanzas' -NS_STREAM = 'http://affinix.com/jabber/stream' -NS_STREAMS = 'http://etherx.jabber.org/streams' -NS_TIME = 'jabber:iq:time' # XEP-0900 -NS_TIME_REVISED = 'urn:xmpp:time' # XEP-0202 -NS_TLS = 'urn:ietf:params:xml:ns:xmpp-tls' -NS_TUNE = 'http://jabber.org/protocol/tune' # XEP-0118 -NS_VACATION = 'http://jabber.org/protocol/vacation' -NS_VCARD = 'vcard-temp' -NS_GMAILNOTIFY = 'google:mail:notify' -NS_GTALKSETTING = 'google:setting' -NS_VCARD_UPDATE = NS_VCARD + ':x:update' -NS_VERSION = 'jabber:iq:version' -NS_VIEWING = 'http://jabber.org/protocol/viewing' # XEP--197 -NS_WAITINGLIST = 'http://jabber.org/protocol/waitinglist' # XEP-0130 -NS_XHTML_IM = 'http://jabber.org/protocol/xhtml-im' # XEP-0071 -NS_XHTML = 'http://www.w3.org/1999/xhtml' # " -NS_DATA_LAYOUT = 'http://jabber.org/protocol/xdata-layout' # XEP-0141 -NS_DATA_VALIDATE = 'http://jabber.org/protocol/xdata-validate' # XEP-0122 -NS_XMPP_STREAMS = 'urn:ietf:params:xml:ns:xmpp-streams' -NS_RECEIPTS = 'urn:xmpp:receipts' -NS_PUBKEY_PUBKEY = 'urn:xmpp:pubkey:2' # XEP-0189 -NS_PUBKEY_REVOKE = 'urn:xmpp:revoke:2' -NS_PUBKEY_ATTEST = 'urn:xmpp:attest:2' -NS_STREAM_MGMT = 'urn:xmpp:sm:2' # XEP-198 -NS_HASHES = 'urn:xmpp:hashes:1' # XEP-300 -NS_HASHES_MD5 = 'urn:xmpp:hash-function-textual-names:md5' -NS_HASHES_SHA1 = 'urn:xmpp:hash-function-textual-names:sha-1' -NS_HASHES_SHA256 = 'urn:xmpp:hash-function-textual-names:sha-256' -NS_HASHES_SHA512 = 'urn:xmpp:hash-function-textual-names:sha-512' - -xmpp_stream_error_conditions = ''' -bad-format -- -- -- The entity has sent XML that cannot be processed. -bad-namespace-prefix -- -- -- The entity has sent a namespace prefix that is unsupported, or has sent no namespace prefix on an element that requires such a prefix. -conflict -- -- -- The server is closing the active stream for this entity because a new stream has been initiated that conflicts with the existing stream. -connection-timeout -- -- -- The entity has not generated any traffic over the stream for some period of time. -host-gone -- -- -- The value of the 'to' attribute provided by the initiating entity in the stream header corresponds to a hostname that is no longer hosted by the server. -host-unknown -- -- -- The value of the 'to' attribute provided by the initiating entity in the stream header does not correspond to a hostname that is hosted by the server. -improper-addressing -- -- -- A stanza sent between two servers lacks a 'to' or 'from' attribute (or the attribute has no value). -internal-server-error -- -- -- The server has experienced a misconfiguration or an otherwise-undefined internal error that prevents it from servicing the stream. -invalid-from -- cancel -- -- The JID or hostname provided in a 'from' address does not match an authorized JID or validated domain negotiated between servers via SASL or dialback, or between a client and a server via authentication and resource authorization. -invalid-id -- -- -- The stream ID or dialback ID is invalid or does not match an ID previously provided. -invalid-namespace -- -- -- The streams namespace name is something other than "http://etherx.jabber.org/streams" or the dialback namespace name is something other than "jabber:server:dialback". -invalid-xml -- -- -- The entity has sent invalid XML over the stream to a server that performs validation. -not-authorized -- -- -- The entity has attempted to send data before the stream has been authenticated, or otherwise is not authorized to perform an action related to stream negotiation. -policy-violation -- -- -- The entity has violated some local service policy. -remote-connection-failed -- -- -- The server is unable to properly connect to a remote resource that is required for authentication or authorization. -resource-constraint -- -- -- The server lacks the system resources necessary to service the stream. -restricted-xml -- -- -- The entity has attempted to send restricted XML features such as a comment, processing instruction, DTD, entity reference, or unescaped character. -see-other-host -- -- -- The server will not provide service to the initiating entity but is redirecting traffic to another host. -system-shutdown -- -- -- The server is being shut down and all active streams are being closed. -undefined-condition -- -- -- The error condition is not one of those defined by the other conditions in this list. -unsupported-encoding -- -- -- The initiating entity has encoded the stream in an encoding that is not supported by the server. -unsupported-stanza-type -- -- -- The initiating entity has sent a first-level child of the stream that is not supported by the server. -unsupported-version -- -- -- The value of the 'version' attribute provided by the initiating entity in the stream header specifies a version of XMPP that is not supported by the server. -xml-not-well-formed -- -- -- The initiating entity has sent XML that is not well-formed.''' - -xmpp_stanza_error_conditions = ''' -bad-request -- 400 -- modify -- The sender has sent XML that is malformed or that cannot be processed. -conflict -- 409 -- cancel -- Access cannot be granted because an existing resource or session exists with the same name or address. -feature-not-implemented -- 501 -- cancel -- The feature requested is not implemented by the recipient or server and therefore cannot be processed. -forbidden -- 403 -- auth -- The requesting entity does not possess the required permissions to perform the action. -gone -- 302 -- modify -- The recipient or server can no longer be contacted at this address. -internal-server-error -- 500 -- wait -- The server could not process the stanza because of a misconfiguration or an otherwise-undefined internal server error. -item-not-found -- 404 -- cancel -- The addressed JID or item requested cannot be found. -jid-malformed -- 400 -- modify -- The value of the 'to' attribute in the sender's stanza does not adhere to the syntax defined in Addressing Scheme. -not-acceptable -- 406 -- cancel -- The recipient or server understands the request but is refusing to process it because it does not meet criteria defined by the recipient or server. -not-allowed -- 405 -- cancel -- The recipient or server does not allow any entity to perform the action. -not-authorized -- 401 -- auth -- The sender must provide proper credentials before being allowed to perform the action, or has provided improper credentials. -payment-required -- 402 -- auth -- The requesting entity is not authorized to access the requested service because payment is required. -recipient-unavailable -- 404 -- wait -- The intended recipient is temporarily unavailable. -redirect -- 302 -- modify -- The recipient or server is redirecting requests for this information to another entity. -registration-required -- 407 -- auth -- The requesting entity is not authorized to access the requested service because registration is required. -remote-server-not-found -- 404 -- cancel -- A remote server or service specified as part or all of the JID of the intended recipient does not exist. -remote-server-timeout -- 504 -- wait -- A remote server or service specified as part or all of the JID of the intended recipient could not be contacted within a reasonable amount of time. -resource-constraint -- 500 -- wait -- The server or recipient lacks the system resources necessary to service the request. -service-unavailable -- 503 -- cancel -- The server or recipient does not currently provide the requested service. -subscription-required -- 407 -- auth -- The requesting entity is not authorized to access the requested service because a subscription is required. -undefined-condition -- 500 -- -- Undefined Condition -unexpected-request -- 400 -- wait -- The recipient or server understood the request but was not expecting it at this time (e.g., the request was out of order).''' - -sasl_error_conditions = ''' -aborted -- -- -- The receiving entity acknowledges an <abort/> element sent by the initiating entity; sent in reply to the <abort/> element. -incorrect-encoding -- -- -- The data provided by the initiating entity could not be processed because the [BASE64]Josefsson, S., The Base16, Base32, and Base64 Data Encodings, July 2003. encoding is incorrect (e.g., because the encoding does not adhere to the definition in Section 3 of [BASE64]Josefsson, S., The Base16, Base32, and Base64 Data Encodings, July 2003.); sent in reply to a <response/> element or an <auth/> element with initial response data. -invalid-authzid -- -- -- The authzid provided by the initiating entity is invalid, either because it is incorrectly formatted or because the initiating entity does not have permissions to authorize that ID; sent in reply to a <response/> element or an <auth/> element with initial response data. -invalid-mechanism -- -- -- The initiating entity did not provide a mechanism or requested a mechanism that is not supported by the receiving entity; sent in reply to an <auth/> element. -mechanism-too-weak -- -- -- The mechanism requested by the initiating entity is weaker than server policy permits for that initiating entity; sent in reply to a <response/> element or an <auth/> element with initial response data. -not-authorized -- -- -- The authentication failed because the initiating entity did not provide valid credentials (this includes but is not limited to the case of an unknown username); sent in reply to a <response/> element or an <auth/> element with initial response data. -temporary-auth-failure -- -- -- The authentication failed because of a temporary error condition within the receiving entity; sent in reply to an <auth/> element or <response/> element.''' - -ERRORS, _errorcodes = {}, {} -for ns, errname, errpool in ((NS_XMPP_STREAMS, 'STREAM', -xmpp_stream_error_conditions), (NS_STANZAS, 'ERR', xmpp_stanza_error_conditions), -(NS_SASL, 'SASL', sasl_error_conditions)): - for err in errpool.split('\n')[1:]: - cond, code, typ, text = err.split(' -- ') - name = errname + '_' + ascii_upper(cond).replace('-', '_') - locals()[name] = ns + ' ' + cond - ERRORS[ns + ' ' + cond] = [code, typ, text] - if code: - _errorcodes[code] = cond -del ns, errname, errpool, err, cond, code, typ, text - -def isResultNode(node): - """ - Return true if the node is a positive reply - """ - return node and node.getType() == 'result' - -def isErrorNode(node): - """ - Return true if the node is a negative reply - """ - return node and node.getType() == 'error' - -class NodeProcessed(Exception): - """ - Exception that should be raised by handler when the handling should be - stopped - """ - pass - -class StreamError(Exception): - """ - Base exception class for stream errors - """ - pass - -class BadFormat(StreamError): - pass - -class BadNamespacePrefix(StreamError): - pass - -class Conflict(StreamError): - pass - -class ConnectionTimeout(StreamError): - pass - -class HostGone(StreamError): - pass - -class HostUnknown(StreamError): - pass - -class ImproperAddressing(StreamError): - pass - -class InternalServerError(StreamError): - pass - -class InvalidFrom(StreamError): - pass - -class InvalidID(StreamError): - pass - -class InvalidNamespace(StreamError): - pass - -class InvalidXML(StreamError): - pass - -class NotAuthorized(StreamError): - pass - -class PolicyViolation(StreamError): - pass - -class RemoteConnectionFailed(StreamError): - pass - -class ResourceConstraint(StreamError): - pass - -class RestrictedXML(StreamError): - pass - -class SeeOtherHost(StreamError): - pass - -class SystemShutdown(StreamError): - pass - -class UndefinedCondition(StreamError): - pass - -class UnsupportedEncoding(StreamError): - pass - -class UnsupportedStanzaType(StreamError): - pass - -class UnsupportedVersion(StreamError): - pass - -class XMLNotWellFormed(StreamError): - pass - -stream_exceptions = {'bad-format': BadFormat, - 'bad-namespace-prefix': BadNamespacePrefix, - 'conflict': Conflict, - 'connection-timeout': ConnectionTimeout, - 'host-gone': HostGone, - 'host-unknown': HostUnknown, - 'improper-addressing': ImproperAddressing, - 'internal-server-error': InternalServerError, - 'invalid-from': InvalidFrom, - 'invalid-id': InvalidID, - 'invalid-namespace': InvalidNamespace, - 'invalid-xml': InvalidXML, - 'not-authorized': NotAuthorized, - 'policy-violation': PolicyViolation, - 'remote-connection-failed': RemoteConnectionFailed, - 'resource-constraint': ResourceConstraint, - 'restricted-xml': RestrictedXML, - 'see-other-host': SeeOtherHost, - 'system-shutdown': SystemShutdown, - 'undefined-condition': UndefinedCondition, - 'unsupported-encoding': UnsupportedEncoding, - 'unsupported-stanza-type': UnsupportedStanzaType, - 'unsupported-version': UnsupportedVersion, - 'xml-not-well-formed': XMLNotWellFormed} - -class JID: - """ - JID can be built from string, modified, compared, serialised into string - """ - - def __init__(self, jid=None, node='', domain='', resource=''): - """ - JID can be specified as string (jid argument) or as separate parts - - Examples: - JID('node@domain/resource') - JID(node='node',domain='domain.org') - """ - if not jid and not domain: - raise ValueError('JID must contain at least domain name') - elif type(jid) == type(self): - self.node, self.domain = jid.node, jid.domain - self.resource = jid.resource - elif domain: - self.node, self.domain, self.resource = node, domain, resource - else: - if jid.find('@') + 1: - self.node, jid = jid.split('@', 1) - else: - self.node = '' - if jid.find('/')+1: - self.domain, self.resource = jid.split('/', 1) - else: - self.domain, self.resource = jid, '' - - def getNode(self): - """ - Return the node part of the JID - """ - return self.node - - def setNode(self, node): - """ - Set the node part of the JID to new value. Specify None to remove - the node part - """ - self.node = node.lower() - - def getDomain(self): - """ - Return the domain part of the JID - """ - return self.domain - - def setDomain(self, domain): - """ - Set the domain part of the JID to new value - """ - self.domain = domain.lower() - - def getResource(self): - """ - Return the resource part of the JID - """ - return self.resource - - def setResource(self, resource): - """ - Set the resource part of the JID to new value. Specify None to remove the - resource part - """ - self.resource = resource - - def getStripped(self): - """ - Return the bare representation of JID. I.e. string value w/o resource - """ - return self.__str__(0) - - def __eq__(self, other): - """ - Compare the JID to another instance or to string for equality - """ - try: - other = JID(other) - except ValueError: - return 0 - return self.resource == other.resource and \ - self.__str__(0) == other.__str__(0) - - def __ne__(self, other): - """ - Compare the JID to another instance or to string for non-equality - """ - return not self.__eq__(other) - - def bareMatch(self, other): - """ - Compare the node and domain parts of the JID's for equality - """ - return self.__str__(0) == JID(other).__str__(0) - - def __str__(self, wresource=1): - """ - Serialise JID into string - """ - if self.node: - jid = self.node + '@' + self.domain - else: - jid = self.domain - if wresource and self.resource: - return jid + '/' + self.resource - return jid - - def __hash__(self): - """ - Produce hash of the JID, Allows to use JID objects as keys of the - dictionary - """ - return hash(str(self)) - -class BOSHBody(Node): - """ - <body> tag that wraps usual XMPP stanzas in XMPP over BOSH - """ - - def __init__(self, attrs={}, payload=[], node=None): - Node.__init__(self, tag='body', attrs=attrs, payload=payload, node=node) - self.setNamespace(NS_HTTP_BIND) - - -class Protocol(Node): - """ - A "stanza" object class. Contains methods that are common for presences, iqs - and messages - """ - - def __init__(self, name=None, to=None, typ=None, frm=None, attrs={}, - payload=[], timestamp=None, xmlns=None, node=None): - """ - Constructor, name is the name of the stanza - i.e. 'message' or 'presence'or 'iq' - - to is the value of 'to' attribure, 'typ' - 'type' attribute - frn - from attribure, attrs - other attributes mapping, - payload - same meaning as for simplexml payload definition - timestamp - the time value that needs to be stamped over stanza - xmlns - namespace of top stanza node - node - parsed or unparsed stana to be taken as prototype. - """ - if not attrs: - attrs = {} - if to: - attrs['to'] = to - if frm: - attrs['from'] = frm - if typ: - attrs['type'] = typ - Node.__init__(self, tag=name, attrs=attrs, payload=payload, node=node) - if not node and xmlns: - self.setNamespace(xmlns) - if self['to']: - self.setTo(self['to']) - if self['from']: - self.setFrom(self['from']) - if node and type(self) == type(node) and \ - self.__class__ == node.__class__ and self.attrs.has_key('id'): - del self.attrs['id'] - self.timestamp = None - for d in self.getTags('delay', namespace=NS_DELAY2): - try: - if d.getAttr('stamp') < self.getTimestamp2(): - self.setTimestamp(d.getAttr('stamp')) - except Exception: - pass - if not self.timestamp: - for x in self.getTags('x', namespace=NS_DELAY): - try: - if x.getAttr('stamp') < self.getTimestamp(): - self.setTimestamp(x.getAttr('stamp')) - except Exception: - pass - if timestamp is not None: - self.setTimestamp(timestamp) # To auto-timestamp stanza just pass timestamp='' - - def getTo(self): - """ - Return value of the 'to' attribute - """ - try: - return self['to'] - except: - return None - - def getFrom(self): - """ - Return value of the 'from' attribute - """ - try: - return self['from'] - except: - return None - - def getTimestamp(self): - """ - Return the timestamp in the 'yyyymmddThhmmss' format - """ - if self.timestamp: - return self.timestamp - return time.strftime('%Y%m%dT%H:%M:%S', time.gmtime()) - - def getTimestamp2(self): - """ - Return the timestamp in the 'yyyymmddThhmmss' format - """ - if self.timestamp: - return self.timestamp - return time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) - - def getID(self): - """ - Return the value of the 'id' attribute - """ - return self.getAttr('id') - - def setTo(self, val): - """ - Set the value of the 'to' attribute - """ - self.setAttr('to', JID(val)) - - def getType(self): - """ - Return the value of the 'type' attribute - """ - return self.getAttr('type') - - def setFrom(self, val): - """ - Set the value of the 'from' attribute - """ - self.setAttr('from', JID(val)) - - def setType(self, val): - """ - Set the value of the 'type' attribute - """ - self.setAttr('type', val) - - def setID(self, val): - """ - Set the value of the 'id' attribute - """ - self.setAttr('id', val) - - def getError(self): - """ - Return the error-condition (if present) or the textual description - of the error (otherwise) - """ - errtag = self.getTag('error') - if errtag: - for tag in errtag.getChildren(): - if tag.getName() != 'text': - return tag.getName() - return errtag.getData() - - def getErrorMsg(self): - """ - Return the textual description of the error (if present) - or the error condition - """ - errtag = self.getTag('error') - if errtag: - for tag in errtag.getChildren(): - if tag.getName() == 'text': - return tag.getData() - return self.getError() - - def getErrorCode(self): - """ - Return the error code. Obsolete. - """ - return self.getTagAttr('error', 'code') - - def getStatusConditions(self): - """ - Return the status conditions list as defined in XEP-0306. - """ - conds = [] - condtag = self.getTag('conditions', namespace=NS_CONDITIONS) - if condtag: - for tag in condtag.getChildren(): - conds.append(tag.getName()) - return conds - - def setError(self, error, code=None): - """ - Set the error code. Obsolete. Use error-conditions instead - """ - if code: - if str(code) in _errorcodes.keys(): - error = ErrorNode(_errorcodes[str(code)], text=error) - else: - error = ErrorNode(ERR_UNDEFINED_CONDITION, code=code, - typ='cancel', text=error) - elif type(error) in [type(''), type(u'')]: - error=ErrorNode(error) - self.setType('error') - self.addChild(node=error) - - def setTimestamp(self, val=None): - """ - Set the timestamp. timestamp should be the yyyymmddThhmmss string - """ - if not val: - val = time.strftime('%Y%m%dT%H:%M:%S', time.gmtime()) - self.timestamp=val - self.setTag('x', {'stamp': self.timestamp}, namespace=NS_DELAY) - - def getProperties(self): - """ - Return the list of namespaces to which belongs the direct childs of element - """ - props = [] - for child in self.getChildren(): - prop = child.getNamespace() - if prop not in props: - props.append(prop) - return props - - def __setitem__(self, item, val): - """ - Set the item 'item' to the value 'val' - """ - if item in ['to', 'from']: - val = JID(val) - return self.setAttr(item, val) - - -class Message(Protocol): - """ - XMPP Message stanza - "push" mechanism - """ - - def __init__(self, to=None, body=None, xhtml=None, typ=None, subject=None, - attrs={}, frm=None, payload=[], timestamp=None, xmlns=NS_CLIENT, - node=None): - """ - You can specify recipient, text of message, type of message any - additional attributes, sender of the message, any additional payload - (f.e. jabber:x:delay element) and namespace in one go. - - Alternatively you can pass in the other XML object as the 'node' - parameted to replicate it as message - """ - Protocol.__init__(self, 'message', to=to, typ=typ, attrs=attrs, frm=frm, - payload=payload, timestamp=timestamp, xmlns=xmlns, node=node) - if body: - self.setBody(body) - if xhtml: - self.setXHTML(xhtml) - if subject is not None: - self.setSubject(subject) - - def getBody(self): - """ - Return text of the message - """ - return self.getTagData('body') - - def getXHTML(self, xmllang=None): - """ - Return serialized xhtml-im element text of the message - - TODO: Returning a DOM could make rendering faster. - """ - xhtml = self.getTag('html') - if xhtml: - if xmllang: - body = xhtml.getTag('body', attrs={'xml:lang': xmllang}) - else: - body = xhtml.getTag('body') - return str(body) - return None - - def getSubject(self): - """ - Return subject of the message - """ - return self.getTagData('subject') - - def getThread(self): - """ - Return thread of the message - """ - return self.getTagData('thread') - - def setBody(self, val): - """ - Set the text of the message""" - self.setTagData('body', val) - - def setXHTML(self, val, xmllang=None): - """ - Sets the xhtml text of the message (XEP-0071). The parameter is the - "inner html" to the body. - """ - try: - if xmllang: - dom = NodeBuilder('<body xmlns="%s" xml:lang="%s">%s</body>' \ - % (NS_XHTML, xmllang, val)).getDom() - else: - dom = NodeBuilder('<body xmlns="%s">%s</body>' % (NS_XHTML, - val), 0).getDom() - if self.getTag('html'): - self.getTag('html').addChild(node=dom) - else: - self.setTag('html', namespace=NS_XHTML_IM).addChild(node=dom) - except Exception, e: - print "Error", e - # FIXME: log. we could not set xhtml (parse error, whatever) - - def setSubject(self, val): - """ - Set the subject of the message - """ - self.setTagData('subject', val) - - def setThread(self, val): - """ - Set the thread of the message - """ - self.setTagData('thread', val) - - def buildReply(self, text=None): - """ - Builds and returns another message object with specified text. The to, - from, thread and type properties of new message are pre-set as reply to - this message - """ - m = Message(to=self.getFrom(), frm=self.getTo(), body=text, - typ=self.getType()) - th = self.getThread() - if th: - m.setThread(th) - return m - - def getStatusCode(self): - """ - Return the status code of the message (for groupchat config change) - """ - attrs = [] - for xtag in self.getTags('x'): - for child in xtag.getTags('status'): - attrs.append(child.getAttr('code')) - return attrs - -class Presence(Protocol): - - def __init__(self, to=None, typ=None, priority=None, show=None, status=None, - attrs={}, frm=None, timestamp=None, payload=[], xmlns=NS_CLIENT, - node=None): - """ - You can specify recipient, type of message, priority, show and status - values any additional attributes, sender of the presence, timestamp, any - additional payload (f.e. jabber:x:delay element) and namespace in one go. - Alternatively you can pass in the other XML object as the 'node' - parameted to replicate it as presence - """ - Protocol.__init__(self, 'presence', to=to, typ=typ, attrs=attrs, frm=frm, - payload=payload, timestamp=timestamp, xmlns=xmlns, node=node) - if priority: - self.setPriority(priority) - if show: - self.setShow(show) - if status: - self.setStatus(status) - - def getPriority(self): - """ - Return the priority of the message - """ - return self.getTagData('priority') - - def getShow(self): - """ - Return the show value of the message - """ - return self.getTagData('show') - - def getStatus(self): - """ - Return the status string of the message - """ - return self.getTagData('status') - - def setPriority(self, val): - """ - Set the priority of the message - """ - self.setTagData('priority', val) - - def setShow(self, val): - """ - Set the show value of the message - """ - self.setTagData('show', val) - - def setStatus(self, val): - """ - Set the status string of the message - """ - self.setTagData('status', val) - - def _muc_getItemAttr(self, tag, attr): - for xtag in self.getTags('x'): - if xtag.getNamespace() not in (NS_MUC_USER, NS_MUC_ADMIN): - continue - for child in xtag.getTags(tag): - return child.getAttr(attr) - - def _muc_getSubTagDataAttr(self, tag, attr): - for xtag in self.getTags('x'): - if xtag.getNamespace() not in (NS_MUC_USER, NS_MUC_ADMIN): - continue - for child in xtag.getTags('item'): - for cchild in child.getTags(tag): - return cchild.getData(), cchild.getAttr(attr) - return None, None - - def getRole(self): - """ - Return the presence role (for groupchat) - """ - return self._muc_getItemAttr('item', 'role') - - def getAffiliation(self): - """ - Return the presence affiliation (for groupchat) - """ - return self._muc_getItemAttr('item', 'affiliation') - - def getNewNick(self): - """ - Return the status code of the presence (for groupchat) - """ - return self._muc_getItemAttr('item', 'nick') - - def getJid(self): - """ - Return the presence jid (for groupchat) - """ - return self._muc_getItemAttr('item', 'jid') - - def getReason(self): - """ - Returns the reason of the presence (for groupchat) - """ - return self._muc_getSubTagDataAttr('reason', '')[0] - - def getActor(self): - """ - Return the reason of the presence (for groupchat) - """ - return self._muc_getSubTagDataAttr('actor', 'jid')[1] - - def getStatusCode(self): - """ - Return the status code of the presence (for groupchat) - """ - attrs = [] - for xtag in self.getTags('x'): - for child in xtag.getTags('status'): - attrs.append(child.getAttr('code')) - return attrs - -class Iq(Protocol): - """ - XMPP Iq object - get/set dialog mechanism - """ - - def __init__(self, typ=None, queryNS=None, attrs={}, to=None, frm=None, - payload=[], xmlns=NS_CLIENT, node=None): - """ - You can specify type, query namespace any additional attributes, - recipient of the iq, sender of the iq, any additional payload (f.e. - jabber:x:data node) and namespace in one go. - - Alternatively you can pass in the other XML object as the 'node' - parameted to replicate it as an iq - """ - Protocol.__init__(self, 'iq', to=to, typ=typ, attrs=attrs, frm=frm, - xmlns=xmlns, node=node) - if payload: - self.setQueryPayload(payload) - if queryNS: - self.setQueryNS(queryNS) - - def getQuery(self): - """ - Return the IQ's child element if it exists, None otherwise. - """ - children = self.getChildren() - if children and self.getType() != 'error' and \ - children[0].getName() != 'error': - return children[0] - - def getQueryNS(self): - """ - Return the namespace of the 'query' child element - """ - tag = self.getQuery() - if tag: - return tag.getNamespace() - - def getQuerynode(self): - """ - Return the 'node' attribute value of the 'query' child element - """ - tag = self.getQuery() - if tag: - return tag.getAttr('node') - - def getQueryPayload(self): - """ - Return the 'query' child element payload - """ - tag = self.getQuery() - if tag: - return tag.getPayload() - - def getQueryChildren(self): - """ - Return the 'query' child element child nodes - """ - tag = self.getQuery() - if tag: - return tag.getChildren() - - def setQuery(self, name=None): - """ - Change the name of the query node, creating it if needed. Keep the - existing name if none is given (use 'query' if it's a creation). - Return the query node. - """ - query = self.getQuery() - if query is None: - query = self.addChild('query') - if name is not None: - query.setName(name) - return query - - def setQueryNS(self, namespace): - """ - Set the namespace of the 'query' child element - """ - self.setQuery().setNamespace(namespace) - - def setQueryPayload(self, payload): - """ - Set the 'query' child element payload - """ - self.setQuery().setPayload(payload) - - def setQuerynode(self, node): - """ - Set the 'node' attribute value of the 'query' child element - """ - self.setQuery().setAttr('node', node) - - def buildReply(self, typ): - """ - Build and return another Iq object of specified type. The to, from and - query child node of new Iq are pre-set as reply to this Iq. - """ - iq = Iq(typ, to=self.getFrom(), frm=self.getTo(), - attrs={'id': self.getID()}) - iq.setQuery(self.getQuery().getName()).setNamespace(self.getQueryNS()) - return iq - -class Hashes(Node): - """ - Hash elements for various XEPs as defined in XEP-300 - """ - - """ - RECOMENDED HASH USE: - Algorithm Support - MD2 MUST NOT - MD4 MUST NOT - MD5 MAY - SHA-1 MUST - SHA-256 MUST - SHA-512 SHOULD - """ - - supported = ('md5', 'sha-1', 'sha-256', 'sha-512') - - def __init__(self, nsp=NS_HASHES): - Node.__init__(self, None, {}, [], None, None, False, None) - self.setNamespace(nsp) - self.setName('hash') - - def calculateHash(self, algo, file_string): - """ - Calculate the hash and add it. It is preferable doing it here - instead of doing it all over the place in Gajim. - """ - hl = None - hash_ = None - # file_string can be a string or a file - if type(file_string) == str: # if it is a string - if algo == 'md5': - hl = hashlib.md5() - elif algo == 'sha-1': - hl = hashlib.sha1() - elif algo == 'sha-256': - hl = hashlib.sha256() - elif algo == 'sha-512': - hl = hashlib.sha512() - - if hl: - hl.update(file_string) - hash_ = hl.hexdigest() - else: # if it is a file - - if algo == 'md5': - hl = hashlib.md5() - elif algo == 'sha-1': - hl = hashlib.sha1() - elif algo == 'sha-256': - hl = hashlib.sha256() - elif algo == 'sha-512': - hl = hashlib.sha512() - - if hl: - for line in file_string: - hl.update(line) - hash_ = hl.hexdigest() - - return hash_ - - def addHash(self, hash_, algo): - self.setAttr('algo', algo) - self.setData(hash_) - -class Acks(Node): - """ - Acknowledgement elements for Stream Management - """ - def __init__(self, nsp=NS_STREAM_MGMT): - Node.__init__(self, None, {}, [], None, None, False, None) - self.setNamespace(nsp) - - def buildAnswer(self, handled): - """ - handled is the number of stanzas handled - """ - self.setName('a') - self.setAttr('h', handled) - - def buildRequest(self): - self.setName('r') - - def buildEnable(self, resume=False): - self.setName('enable') - if resume: - self.setAttr('resume', 'true') - - def buildResume(self, handled, previd): - self.setName('resume') - self.setAttr('h', handled) - self.setAttr('previd', previd) - -class ErrorNode(Node): - """ - XMPP-style error element - - In the case of stanza error should be attached to XMPP stanza. - In the case of stream-level errors should be used separately. - """ - - def __init__(self, name, code=None, typ=None, text=None): - """ - Mandatory parameter: name - name of error condition. - Optional parameters: code, typ, text. - Used for backwards compartibility with older jabber protocol. - """ - if name in ERRORS: - cod, type_, txt = ERRORS[name] - ns = name.split()[0] - else: - cod, ns, type_, txt = '500', NS_STANZAS, 'cancel', '' - if typ: - type_ = typ - if code: - cod = code - if text: - txt = text - Node.__init__(self, 'error', {}, [Node(name)]) - if type_: - self.setAttr('type', type_) - if not cod: - self.setName('stream:error') - if txt: - self.addChild(node=Node(ns + ' text', {}, [txt])) - if cod: - self.setAttr('code', cod) - -class Error(Protocol): - """ - Used to quickly transform received stanza into error reply - """ - - def __init__(self, node, error, reply=1): - """ - Create error reply basing on the received 'node' stanza and the 'error' - error condition - - If the 'node' is not the received stanza but locally created ('to' and - 'from' fields needs not swapping) specify the 'reply' argument as false. - """ - if reply: - Protocol.__init__(self, to=node.getFrom(), frm=node.getTo(), node=node) - else: - Protocol.__init__(self, node=node) - self.setError(error) - if node.getType() == 'error': - self.__str__ = self.__dupstr__ - - def __dupstr__(self, dup1=None, dup2=None): - """ - Dummy function used as preventor of creating error node in reply to error - node. I.e. you will not be able to serialise "double" error into string. - """ - return '' - -class DataField(Node): - """ - This class is used in the DataForm class to describe the single data item - - If you are working with jabber:x:data (XEP-0004, XEP-0068, XEP-0122) then - you will need to work with instances of this class. - """ - - def __init__(self, name=None, value=None, typ=None, required=0, desc=None, - options=[], node=None): - """ - Create new data field of specified name,value and type - - Also 'required','desc' and 'options' fields can be set. Alternatively - other XML object can be passed in as the 'node' parameted - to replicate it as a new datafiled. - """ - Node.__init__(self, 'field', node=node) - if name: - self.setVar(name) - if isinstance(value, (list, tuple)): - self.setValues(value) - elif value: - self.setValue(value) - if typ: - self.setType(typ) - elif not typ and not node: - self.setType('text-single') - if required: - self.setRequired(required) - if desc: - self.setDesc(desc) - if options: - self.setOptions(options) - - def setRequired(self, req=1): - """ - Change the state of the 'required' flag - """ - if req: - self.setTag('required') - else: - try: - self.delChild('required') - except ValueError: - return - - def isRequired(self): - """ - Return in this field a required one - """ - return self.getTag('required') - - def setDesc(self, desc): - """ - Set the description of this field - """ - self.setTagData('desc', desc) - - def getDesc(self): - """ - Return the description of this field - """ - return self.getTagData('desc') - - def setValue(self, val): - """ - Set the value of this field - """ - self.setTagData('value', val) - - def getValue(self): - return self.getTagData('value') - - def setValues(self, lst): - """ - Set the values of this field as values-list. Replaces all previous filed - values! If you need to just add a value - use addValue method - """ - while self.getTag('value'): - self.delChild('value') - for val in lst: - self.addValue(val) - - def addValue(self, val): - """ - Add one more value to this field. Used in 'get' iq's or such - """ - self.addChild('value', {}, [val]) - - def getValues(self): - """ - Return the list of values associated with this field - """ - ret = [] - for tag in self.getTags('value'): - ret.append(tag.getData()) - return ret - - def getOptions(self): - """ - Return label-option pairs list associated with this field - """ - ret = [] - for tag in self.getTags('option'): - ret.append([tag.getAttr('label'), tag.getTagData('value')]) - return ret - - def setOptions(self, lst): - """ - Set label-option pairs list associated with this field - """ - while self.getTag('option'): - self.delChild('option') - for opt in lst: - self.addOption(opt) - - def addOption(self, opt): - """ - Add one more label-option pair to this field - """ - if isinstance(opt, basestring): - self.addChild('option').setTagData('value', opt) - else: - self.addChild('option', {'label': opt[0]}).setTagData('value', - opt[1]) - - def getType(self): - """ - Get type of this field - """ - return self.getAttr('type') - - def setType(self, val): - """ - Set type of this field - """ - return self.setAttr('type', val) - - def getVar(self): - """ - Get 'var' attribute value of this field - """ - return self.getAttr('var') - - def setVar(self, val): - """ - Set 'var' attribute value of this field - """ - return self.setAttr('var', val) - -class DataForm(Node): - """ - Used for manipulating dataforms in XMPP - - Relevant XEPs: 0004, 0068, 0122. Can be used in disco, pub-sub and many - other applications. - """ - def __init__(self, typ=None, data=[], title=None, node=None): - """ - Create new dataform of type 'typ'. 'data' is the list of DataField - instances that this dataform contains, 'title' - the title string. You - can specify the 'node' argument as the other node to be used as base for - constructing this dataform - - title and instructions is optional and SHOULD NOT contain newlines. - Several instructions MAY be present. - 'typ' can be one of ('form' | 'submit' | 'cancel' | 'result' ) - 'typ' of reply iq can be ( 'result' | 'set' | 'set' | 'result' ) respectively. - 'cancel' form can not contain any fields. All other forms contains AT LEAST one field. - 'title' MAY be included in forms of type "form" and "result" - """ - Node.__init__(self, 'x', node=node) - if node: - newkids = [] - for n in self.getChildren(): - if n.getName() == 'field': - newkids.append(DataField(node=n)) - else: - newkids.append(n) - self.kids = newkids - if typ: - self.setType(typ) - self.setNamespace(NS_DATA) - if title: - self.setTitle(title) - if isinstance(data, dict): - newdata = [] - for name in data.keys(): - newdata.append(DataField(name, data[name])) - data = newdata - for child in data: - if isinstance(child, basestring): - self.addInstructions(child) - elif child.__class__.__name__ == 'DataField': - self.kids.append(child) - else: - self.kids.append(DataField(node=child)) - - def getType(self): - """ - Return the type of dataform - """ - return self.getAttr('type') - - def setType(self, typ): - """ - Set the type of dataform - """ - self.setAttr('type', typ) - - def getTitle(self): - """ - Return the title of dataform - """ - return self.getTagData('title') - - def setTitle(self, text): - """ - Set the title of dataform - """ - self.setTagData('title', text) - - def getInstructions(self): - """ - Return the instructions of dataform - """ - return self.getTagData('instructions') - - def setInstructions(self, text): - """ - Set the instructions of dataform - """ - self.setTagData('instructions', text) - - def addInstructions(self, text): - """ - Add one more instruction to the dataform - """ - self.addChild('instructions', {}, [text]) - - def getField(self, name): - """ - Return the datafield object with name 'name' (if exists) - """ - return self.getTag('field', attrs={'var': name}) - - def setField(self, name): - """ - Create if nessessary or get the existing datafield object with name - 'name' and return it - """ - f = self.getField(name) - if f: - return f - return self.addChild(node=DataField(name)) - - def asDict(self): - """ - Represent dataform as simple dictionary mapping of datafield names to - their values - """ - ret = {} - for field in self.getTags('field'): - name = field.getAttr('var') - typ = field.getType() - if isinstance(typ, basestring) and typ.endswith('-multi'): - val = [] - for i in field.getTags('value'): - val.append(i.getData()) - else: - val = field.getTagData('value') - ret[name] = val - if self.getTag('instructions'): - ret['instructions'] = self.getInstructions() - return ret - - def __getitem__(self, name): - """ - Simple dictionary interface for getting datafields values by their names - """ - item = self.getField(name) - if item: - return item.getValue() - raise IndexError('No such field') - - def __setitem__(self, name, val): - """ - Simple dictionary interface for setting datafields values by their names - """ - return self.setField(name).setValue(val) - diff --git a/src/common/xmpp/proxy_connectors.py b/src/common/xmpp/proxy_connectors.py deleted file mode 100644 index 2e020013e..000000000 --- a/src/common/xmpp/proxy_connectors.py +++ /dev/null @@ -1,238 +0,0 @@ -## proxy_connectors.py -## based on transports_nb.py -## -## Copyright (C) 2003-2004 Alexey "Snake" Nezhdanov -## modified by Dimitur Kirov <dkirov@gmail.com> -## modified by Tomas Karasek <tom.to.the.k@gmail.com> -## -## This program is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation; either version 2, or (at your option) -## any later version. -## -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. - -""" -Module containing classes for proxy connecting. So far its HTTP CONNECT and -SOCKS5 proxy - -Authentication to NTLM (Microsoft implementation) proxies can be next. -""" - -import struct, socket, base64 -import logging -log = logging.getLogger('gajim.c.x.proxy_connectors') - -class ProxyConnector: - """ - Interface for proxy-connecting object - when tunnneling XMPP over proxies, - some connecting process usually has to be done before opening stream. Proxy - connectors are used right after TCP connection is estabilished - """ - - def __init__(self, send_method, onreceive, old_on_receive, on_success, - on_failure, xmpp_server, proxy_creds=(None, None)): - """ - Creates proxy connector, starts connecting immediately and gives control - back to transport afterwards - - :param send_method: transport send method - :param onreceive: method to set on_receive callbacks - :param old_on_receive: on_receive callback that should be set when - proxy connection was successful - :param on_success: called after proxy connection was successfully opened - :param on_failure: called when errors occured while connecting - :param xmpp_server: tuple of (hostname, port) - :param proxy_creds: tuple of (proxy_user, proxy_credentials) - """ - self.send = send_method - self.onreceive = onreceive - self.old_on_receive = old_on_receive - self.on_success = on_success - self.on_failure = on_failure - self.xmpp_server = xmpp_server - self.proxy_user, self.proxy_pass = proxy_creds - self.old_on_receive = old_on_receive - - self.start_connecting() - - @classmethod - def get_instance(cls, *args, **kwargs): - """ - Factory Method for object creation - - Use this instead of directly initializing the class in order to make unit - testing much easier. - """ - return cls(*args, **kwargs) - - def start_connecting(self): - raise NotImplementedError - - def connecting_over(self): - self.onreceive(self.old_on_receive) - self.on_success() - -class HTTPCONNECTConnector(ProxyConnector): - def start_connecting(self): - """ - Connect to a proxy, supply login and password to it (if were specified - while creating instance). Instruct proxy to make connection to the target - server. - """ - log.info('Proxy server contacted, performing authentification') - connector = ['CONNECT %s:%s HTTP/1.1' % self.xmpp_server, - 'Proxy-Connection: Keep-Alive', - 'Pragma: no-cache', - 'Host: %s:%s' % self.xmpp_server, - 'User-Agent: Gajim'] - if self.proxy_user and self.proxy_pass: - credentials = '%s:%s' % (self.proxy_user, self.proxy_pass) - credentials = base64.encodestring(credentials).strip() - connector.append('Proxy-Authorization: Basic '+credentials) - connector.append('\r\n') - self.onreceive(self._on_headers_sent) - self.send('\r\n'.join(connector)) - - def _on_headers_sent(self, reply): - if reply is None: - return - self.reply = reply.replace('\r', '') - try: - proto, code, desc = reply.split('\n')[0].split(' ', 2) - except: - log.error("_on_headers_sent:", exc_info=True) - #traceback.print_exc() - self.on_failure('Invalid proxy reply') - return - if code <> '200': - log.error('Invalid proxy reply: %s %s %s' % (proto, code, desc)) - self.on_failure('Invalid proxy reply') - return - if len(reply) != 2: - pass - self.connecting_over() - - -class SOCKS5Connector(ProxyConnector): - """ - SOCKS5 proxy connection class. Allows to use SOCKS5 proxies with - (optionally) simple authentication (only USERNAME/PASSWORD auth) - """ - - def start_connecting(self): - log.info('Proxy server contacted, performing authentification') - if self.proxy_user and self.proxy_pass: - to_send = '\x05\x02\x00\x02' - else: - to_send = '\x05\x01\x00' - self.onreceive(self._on_greeting_sent) - self.send(to_send) - - def _on_greeting_sent(self, reply): - if reply is None: - return - if len(reply) != 2: - self.on_failure('Invalid proxy reply') - return - if reply[0] != '\x05': - log.info('Invalid proxy reply') - self.on_failure('Invalid proxy reply') - return - if reply[1] == '\x00': - return self._on_proxy_auth('\x01\x00') - elif reply[1] == '\x02': - to_send = '\x01' + chr(len(self.proxy_user)) + self.proxy_user +\ - chr(len(self.proxy_pass)) + self.proxy_pass - self.onreceive(self._on_proxy_auth) - self.send(to_send) - else: - if reply[1] == '\xff': - log.error('Authentification to proxy impossible: no acceptable ' - 'auth method') - self.on_failure('Authentification to proxy impossible: no ' - 'acceptable authentification method') - return - log.error('Invalid proxy reply') - self.on_failure('Invalid proxy reply') - return - - def _on_proxy_auth(self, reply): - if reply is None: - return - if len(reply) != 2: - log.error('Invalid proxy reply') - self.on_failure('Invalid proxy reply') - return - if reply[0] != '\x01': - log.error('Invalid proxy reply') - self.on_failure('Invalid proxy reply') - return - if reply[1] != '\x00': - log.error('Authentification to proxy failed') - self.on_failure('Authentification to proxy failed') - return - log.info('Authentification successfull. Jabber server contacted.') - # Request connection - req = "\x05\x01\x00" - # If the given destination address is an IP address, we'll - # use the IPv4 address request even if remote resolving was specified. - try: - self.ipaddr = socket.inet_aton(self.xmpp_server[0]) - req = req + "\x01" + self.ipaddr - except socket.error: - # Well it's not an IP number, so it's probably a DNS name. -# if self.__proxy[3]==True: - # Resolve remotely - self.ipaddr = None - req = req + "\x03" + chr(len(self.xmpp_server[0])) + self.xmpp_server[0] -# else: -# # Resolve locally -# self.ipaddr = socket.inet_aton(socket.gethostbyname(self.xmpp_server[0])) -# req = req + "\x01" + ipaddr - req = req + struct.pack(">H", self.xmpp_server[1]) - self.onreceive(self._on_req_sent) - self.send(req) - - def _on_req_sent(self, reply): - if reply is None: - return - if len(reply) < 10: - log.error('Invalid proxy reply') - self.on_failure('Invalid proxy reply') - return - if reply[0] != '\x05': - log.error('Invalid proxy reply') - self.on_failure('Invalid proxy reply') - return - if reply[1] != "\x00": - # Connection failed - if ord(reply[1])<9: - errors = ['general SOCKS server failure', - 'connection not allowed by ruleset', - 'Network unreachable', - 'Host unreachable', - 'Connection refused', - 'TTL expired', - 'Command not supported', - 'Address type not supported' - ] - txt = errors[ord(reply[1])-1] - else: - txt = 'Invalid proxy reply' - log.error(txt) - self.on_failure(txt) - return - # Get the bound address/port - elif reply[3] == "\x01": - begin, end = 3, 7 - elif reply[3] == "\x03": - begin, end = 4, 4 + reply[4] - else: - log.error('Invalid proxy reply') - self.on_failure('Invalid proxy reply') - return - self.connecting_over() diff --git a/src/common/xmpp/roster_nb.py b/src/common/xmpp/roster_nb.py deleted file mode 100644 index 9f520e3d3..000000000 --- a/src/common/xmpp/roster_nb.py +++ /dev/null @@ -1,368 +0,0 @@ -## roster_nb.py -## based on roster.py -## -## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov -## modified by Dimitur Kirov <dkirov@gmail.com> -## -## This program is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation; either version 2, or (at your option) -## any later version. -## -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. - -# $Id: roster.py,v 1.17 2005/05/02 08:38:49 snakeru Exp $ - - -""" -Simple roster implementation. Can be used though for different tasks like -mass-renaming of contacts. -""" - -from protocol import JID, Iq, Presence, Node, NodeProcessed, NS_MUC_USER, NS_ROSTER -from plugin import PlugIn - -import logging -log = logging.getLogger('gajim.c.x.roster_nb') - - -class NonBlockingRoster(PlugIn): - """ - Defines a plenty of methods that will allow you to manage roster. Also - automatically track presences from remote JIDs taking into account that - every JID can have multiple resources connected. Does not currently support - 'error' presences. You can also use mapping interface for access to the - internal representation of contacts in roster - """ - - def __init__(self, version=None): - """ - Init internal variables - """ - PlugIn.__init__(self) - self.version = version - self._data = {} - self._set=None - self._exported_methods=[self.getRoster] - self.received_from_server = False - - def Request(self, force=0): - """ - Request roster from server if it were not yet requested (or if the - 'force' argument is set) - """ - if self._set is None: - self._set = 0 - elif not force: - return - - iq = Iq('get', NS_ROSTER) - if self.version is not None: - iq.setTagAttr('query', 'ver', self.version) - id_ = self._owner.getAnID() - iq.setID(id_) - self._owner.send(iq) - log.info('Roster requested from server') - return id_ - - def RosterIqHandler(self, dis, stanza): - """ - Subscription tracker. Used internally for setting items state in internal - roster representation - """ - sender = stanza.getAttr('from') - if not sender is None and not sender.bareMatch( - self._owner.User + '@' + self._owner.Server): - return - query = stanza.getTag('query') - if query: - self.received_from_server = True - self.version = stanza.getTagAttr('query', 'ver') - if self.version is None: - self.version = '' - for item in query.getTags('item'): - jid=item.getAttr('jid') - if item.getAttr('subscription')=='remove': - if self._data.has_key(jid): del self._data[jid] - # Looks like we have a workaround - # raise NodeProcessed # a MUST - log.info('Setting roster item %s...' % jid) - if not self._data.has_key(jid): self._data[jid]={} - self._data[jid]['name']=item.getAttr('name') - self._data[jid]['ask']=item.getAttr('ask') - self._data[jid]['subscription']=item.getAttr('subscription') - self._data[jid]['groups']=[] - if not self._data[jid].has_key('resources'): self._data[jid]['resources']={} - for group in item.getTags('group'): - if group.getData() not in self._data[jid]['groups']: - self._data[jid]['groups'].append(group.getData()) - self._data[self._owner.User+'@'+self._owner.Server]={'resources': {}, 'name': None, 'ask': None, 'subscription': None, 'groups': None,} - self._set=1 - # Looks like we have a workaround - # raise NodeProcessed # a MUST. Otherwise you'll get back an <iq type='error'/> - - def PresenceHandler(self, dis, pres): - """ - Presence tracker. Used internally for setting items' resources state in - internal roster representation - """ - if pres.getTag('x', namespace=NS_MUC_USER): - return - jid=pres.getFrom() - if not jid: - # If no from attribue, it's from server - jid=self._owner.Server - jid=JID(jid) - if not self._data.has_key(jid.getStripped()): self._data[jid.getStripped()]={'name':None,'ask':None,'subscription':'none','groups':['Not in roster'],'resources':{}} - if type(self._data[jid.getStripped()]['resources'])!=type(dict()): - self._data[jid.getStripped()]['resources']={} - item=self._data[jid.getStripped()] - typ=pres.getType() - - if not typ: - log.info('Setting roster item %s for resource %s...'%(jid.getStripped(), jid.getResource())) - item['resources'][jid.getResource()]=res={'show':None,'status':None,'priority':'0','timestamp':None} - if pres.getTag('show'): res['show']=pres.getShow() - if pres.getTag('status'): res['status']=pres.getStatus() - if pres.getTag('priority'): res['priority']=pres.getPriority() - if not pres.getTimestamp(): pres.setTimestamp() - res['timestamp']=pres.getTimestamp() - elif typ=='unavailable' and item['resources'].has_key(jid.getResource()): del item['resources'][jid.getResource()] - # Need to handle type='error' also - - def _getItemData(self, jid, dataname): - """ - Return specific jid's representation in internal format. Used internally - """ - jid = jid[:(jid+'/').find('/')] - return self._data[jid][dataname] - - def _getResourceData(self, jid, dataname): - """ - Return specific jid's resource representation in internal format. Used - internally - """ - if jid.find('/') + 1: - jid, resource = jid.split('/', 1) - if self._data[jid]['resources'].has_key(resource): - return self._data[jid]['resources'][resource][dataname] - elif self._data[jid]['resources'].keys(): - lastpri = -129 - for r in self._data[jid]['resources'].keys(): - if int(self._data[jid]['resources'][r]['priority']) > lastpri: - resource, lastpri=r, int(self._data[jid]['resources'][r]['priority']) - return self._data[jid]['resources'][resource][dataname] - - def delItem(self, jid): - """ - Delete contact 'jid' from roster - """ - self._owner.send(Iq('set', NS_ROSTER, payload=[Node('item', {'jid': jid, 'subscription': 'remove'})])) - - def getAsk(self, jid): - """ - Return 'ask' value of contact 'jid' - """ - return self._getItemData(jid, 'ask') - - def getGroups(self, jid): - """ - Return groups list that contact 'jid' belongs to - """ - return self._getItemData(jid, 'groups') - - def getName(self, jid): - """ - Return name of contact 'jid' - """ - return self._getItemData(jid, 'name') - - def getPriority(self, jid): - """ - Return priority of contact 'jid'. 'jid' should be a full (not bare) JID - """ - return self._getResourceData(jid, 'priority') - - def getRawRoster(self): - """ - Return roster representation in internal format - """ - return self._data - - def getRawItem(self, jid): - """ - Return roster item 'jid' representation in internal format - """ - return self._data[jid[:(jid+'/').find('/')]] - - def getShow(self, jid): - """ - Return 'show' value of contact 'jid'. 'jid' should be a full (not bare) - JID - """ - return self._getResourceData(jid, 'show') - - def getStatus(self, jid): - """ - Return 'status' value of contact 'jid'. 'jid' should be a full (not bare) - JID - """ - return self._getResourceData(jid, 'status') - - def getSubscription(self, jid): - """ - Return 'subscription' value of contact 'jid' - """ - return self._getItemData(jid, 'subscription') - - def getResources(self, jid): - """ - Return list of connected resources of contact 'jid' - """ - return self._data[jid[:(jid+'/').find('/')]]['resources'].keys() - - def setItem(self, jid, name=None, groups=[]): - """ - Rename contact 'jid' and sets the groups list that it now belongs to - """ - iq = Iq('set', NS_ROSTER) - query = iq.getTag('query') - attrs = {'jid': jid} - if name: - attrs['name'] = name - item = query.setTag('item', attrs) - for group in groups: - item.addChild(node=Node('group', payload=[group])) - self._owner.send(iq) - - def setItemMulti(self, items): - """ - Rename multiple contacts and sets their group lists - """ - iq = Iq('set', NS_ROSTER) - query = iq.getTag('query') - for i in items: - attrs = {'jid': i['jid']} - if i['name']: - attrs['name'] = i['name'] - item = query.setTag('item', attrs) - for group in i['groups']: - item.addChild(node=Node('group', payload=[group])) - self._owner.send(iq) - - def getItems(self): - """ - Return list of all [bare] JIDs that the roster is currently tracks - """ - return self._data.keys() - - def keys(self): - """ - Same as getItems. Provided for the sake of dictionary interface - """ - return self._data.keys() - - def __getitem__(self, item): - """ - Get the contact in the internal format. Raises KeyError if JID 'item' is - not in roster - """ - return self._data[item] - - def getItem(self, item): - """ - Get the contact in the internal format (or None if JID 'item' is not in - roster) - """ - if self._data.has_key(item): - return self._data[item] - - def Subscribe(self, jid): - """ - Send subscription request to JID 'jid' - """ - self._owner.send(Presence(jid, 'subscribe')) - - def Unsubscribe(self, jid): - """ - Ask for removing our subscription for JID 'jid' - """ - self._owner.send(Presence(jid, 'unsubscribe')) - - def Authorize(self, jid): - """ - Authorize JID 'jid'. Works only if these JID requested auth previously - """ - self._owner.send(Presence(jid, 'subscribed')) - - def Unauthorize(self, jid): - """ - Unauthorise JID 'jid'. Use for declining authorisation request or for - removing existing authorization - """ - self._owner.send(Presence(jid, 'unsubscribed')) - - def getRaw(self): - """ - Return the internal data representation of the roster - """ - return self._data - - def setRaw(self, data): - """ - Return the internal data representation of the roster - """ - self._data = data - self._data[self._owner.User + '@' + self._owner.Server] = { - 'resources': {}, - 'name': None, - 'ask': None, - 'subscription': None, - 'groups': None - } - self._set = 1 - - def plugin(self, owner, request=1): - """ - Register presence and subscription trackers in the owner's dispatcher. - Also request roster from server if the 'request' argument is set. Used - internally - """ - self._owner.RegisterHandler('iq', self.RosterIqHandler, 'result', NS_ROSTER, makefirst = 1) - self._owner.RegisterHandler('iq', self.RosterIqHandler, 'set', NS_ROSTER) - self._owner.RegisterHandler('presence', self.PresenceHandler) - if request: - return self.Request() - - def _on_roster_set(self, data): - if data: - self._owner.Dispatcher.ProcessNonBlocking(data) - if not self._set: - return - if not hasattr(self, '_owner') or not self._owner: - # Connection has been closed by receiving a <stream:error> for ex, - return - self._owner.onreceive(None) - if self.on_ready: - self.on_ready(self) - self.on_ready = None - return True - - def getRoster(self, on_ready=None, force=False): - """ - Request roster from server if neccessary and returns self - """ - return_self = True - if not self._set: - self.on_ready = on_ready - self._owner.onreceive(self._on_roster_set) - return_self = False - elif on_ready: - on_ready(self) - return_self = False - if return_self or force: - return self - return None diff --git a/src/common/xmpp/simplexml.py b/src/common/xmpp/simplexml.py deleted file mode 100644 index 4a11cda7d..000000000 --- a/src/common/xmpp/simplexml.py +++ /dev/null @@ -1,692 +0,0 @@ -## simplexml.py based on Mattew Allum's xmlstream.py -## -## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov -## -## This program is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation; either version 2, or (at your option) -## any later version. -## -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. - -# $Id: simplexml.py,v 1.27 2005/04/30 07:20:27 snakeru Exp $ - -""" -Simplexml module provides xmpppy library with all needed tools to handle XML -nodes and XML streams. I'm personally using it in many other separate -projects. It is designed to be as standalone as possible -""" - -import xml.parsers.expat -import logging -log = logging.getLogger('gajim.c.x.simplexml') - -def XMLescape(txt): - """ - Return provided string with symbols & < > " replaced by their respective XML - entities - """ - # replace also FORM FEED and ESC, because they are not valid XML chars - return txt.replace("&", "&").replace("<", "<").replace(">", ">").replace('"', """).replace(u'\x0C', "").replace(u'\x1B', "") - -ENCODING='utf-8' - -def ustr(what): - """ - Converts object "what" to unicode string using it's own __str__ method if - accessible or unicode method otherwise - """ - if isinstance(what, unicode): - return what - try: - r = what.__str__() - except AttributeError: - r = str(what) - if not isinstance(r, unicode): - return unicode(r, ENCODING) - return r - -class Node(object): - """ - Node class describes syntax of separate XML Node. It have a constructor that - permits node creation from set of "namespace name", attributes and payload - of text strings and other nodes. It does not natively support building node - from text string and uses NodeBuilder class for that purpose. After - creation node can be mangled in many ways so it can be completely changed. - Also node can be serialised into string in one of two modes: default (where - the textual representation of node describes it exactly) and "fancy" - with - whitespace added to make indentation and thus make result more readable by - human. - - Node class have attribute FORCE_NODE_RECREATION that is defaults to False - thus enabling fast node replication from the some other node. The drawback - of the fast way is that new node shares some info with the "original" node - that is changing the one node may influence the other. Though it is rarely - needed (in xmpppy it is never needed at all since I'm usually never using - original node after replication (and using replication only to move upwards - on the classes tree). - """ - - FORCE_NODE_RECREATION = 0 - - def __init__(self, tag=None, attrs={}, payload=[], parent=None, nsp=None, - node_built=False, node=None): - """ - Takes "tag" argument as the name of node (prepended by namespace, if - needed and separated from it by a space), attrs dictionary as the set of - arguments, payload list as the set of textual strings and child nodes - that this node carries within itself and "parent" argument that is - another node that this one will be the child of. Also the __init__ can be - provided with "node" argument that is either a text string containing - exactly one node or another Node instance to begin with. If both "node" - and other arguments is provided then the node initially created as - replica of "node" provided and then modified to be compliant with other - arguments. - """ - if node: - if self.FORCE_NODE_RECREATION and isinstance(node, Node): - node = str(node) - if not isinstance(node, Node): - node = NodeBuilder(node, self) - node_built = True - else: - self.name, self.namespace, self.attrs, self.data, self.kids, self.parent, self.nsd = node.name, node.namespace, {}, [], [], node.parent, {} - for key in node.attrs.keys(): - self.attrs[key] = node.attrs[key] - for data in node.data: - self.data.append(data) - for kid in node.kids: - self.kids.append(kid) - for k, v in node.nsd.items(): - self.nsd[k] = v - else: - self.name, self.namespace, self.attrs, self.data, self.kids, self.parent, self.nsd = 'tag', '', {}, [], [], None, {} - if parent: - self.parent = parent - self.nsp_cache = {} - if nsp: - for k, v in nsp.items(): self.nsp_cache[k] = v - for attr, val in attrs.items(): - if attr == 'xmlns': - self.nsd[u''] = val - elif attr.startswith('xmlns:'): - self.nsd[attr[6:]] = val - self.attrs[attr]=attrs[attr] - if tag: - if node_built: - pfx, self.name = (['']+tag.split(':'))[-2:] - self.namespace = self.lookup_nsp(pfx) - else: - if ' ' in tag: - self.namespace, self.name = tag.split() - else: - self.name = tag - if isinstance(payload, basestring): payload=[payload] - for i in payload: - if isinstance(i, Node): - self.addChild(node=i) - else: - self.data.append(ustr(i)) - - def lookup_nsp(self, pfx=''): - ns = self.nsd.get(pfx, None) - if ns is None: - ns = self.nsp_cache.get(pfx, None) - if ns is None: - if self.parent: - ns = self.parent.lookup_nsp(pfx) - self.nsp_cache[pfx] = ns - else: - return 'http://www.gajim.org/xmlns/undeclared' - return ns - - def __str__(self, fancy=0): - """ - Method used to dump node into textual representation. If "fancy" argument - is set to True produces indented output for readability - """ - s = (fancy-1) * 2 * ' ' + "<" + self.name - if self.namespace: - if not self.parent or self.parent.namespace!=self.namespace: - if 'xmlns' not in self.attrs: - s = s + ' xmlns="%s"'%self.namespace - for key in self.attrs.keys(): - val = ustr(self.attrs[key]) - s = s + ' %s="%s"' % ( key, XMLescape(val) ) - s = s + ">" - cnt = 0 - if self.kids: - if fancy: s = s + "\n" - for a in self.kids: - if not fancy and (len(self.data)-1)>=cnt: s=s+XMLescape(self.data[cnt]) - elif (len(self.data)-1)>=cnt: s=s+XMLescape(self.data[cnt].strip()) - if isinstance(a, str) or isinstance(a, unicode): - s = s + a.__str__() - else: - s = s + a.__str__(fancy and fancy+1) - cnt=cnt+1 - if not fancy and (len(self.data)-1) >= cnt: s = s + XMLescape(self.data[cnt]) - elif (len(self.data)-1) >= cnt: s = s + XMLescape(self.data[cnt].strip()) - if not self.kids and s.endswith('>'): - s=s[:-1]+' />' - if fancy: s = s + "\n" - else: - if fancy and not self.data: s = s + (fancy-1) * 2 * ' ' - s = s + "</" + self.name + ">" - if fancy: s = s + "\n" - return s - - def addChild(self, name=None, attrs={}, payload=[], namespace=None, node=None): - """ - If "node" argument is provided, adds it as child node. Else creates new - node from the other arguments' values and adds it as well - """ - if 'xmlns' in attrs: - raise AttributeError("Use namespace=x instead of attrs={'xmlns':x}") - if node: - newnode=node - node.parent = self - else: newnode=Node(tag=name, parent=self, attrs=attrs, payload=payload) - if namespace: - newnode.setNamespace(namespace) - self.kids.append(newnode) - return newnode - - def addData(self, data): - """ - Add some CDATA to node - """ - self.data.append(ustr(data)) - - def clearData(self): - """ - Remove all CDATA from the node - """ - self.data = [] - - def delAttr(self, key): - """ - Delete an attribute "key" - """ - del self.attrs[key] - - def delChild(self, node, attrs={}): - """ - Delete the "node" from the node's childs list, if "node" is an instance. - Else delete the first node that have specified name and (optionally) - attributes - """ - if not isinstance(node, Node): - node = self.getTag(node, attrs) - self.kids.remove(node) - return node - - def getAttrs(self): - """ - Return all node's attributes as dictionary - """ - return self.attrs - - def getAttr(self, key): - """ - Return value of specified attribute - """ - return self.attrs.get(key) - - def getChildren(self): - """ - Return all node's child nodes as list - """ - return self.kids - - def getData(self): - """ - Return all node CDATA as string (concatenated) - """ - return ''.join(self.data) - - def getName(self): - """ - Return the name of node - """ - return self.name - - def getNamespace(self): - """ - Return the namespace of node - """ - return self.namespace - - def getParent(self): - """ - Returns the parent of node (if present) - """ - return self.parent - - def getPayload(self): - """ - Return the payload of node i.e. list of child nodes and CDATA entries. - F.e. for "<node>text1<nodea/><nodeb/> text2</node>" will be returned - list: ['text1', <nodea instance>, <nodeb instance>, ' text2'] - """ - ret = [] - for i in range(len(self.kids)+len(self.data)+1): - try: - if self.data[i]: - ret.append(self.data[i]) - except IndexError: - pass - try: - ret.append(self.kids[i]) - except IndexError: - pass - return ret - - def getTag(self, name, attrs={}, namespace=None): - """ - Filter all child nodes using specified arguments as filter. Return the - first found or None if not found - """ - return self.getTags(name, attrs, namespace, one=1) - - def getTagAttr(self, tag, attr): - """ - Return attribute value of the child with specified name (or None if no - such attribute) - """ - try: - return self.getTag(tag).attrs[attr] - except: - return None - - def getTagData(self, tag): - """ - Return cocatenated CDATA of the child with specified name - """ - try: - return self.getTag(tag).getData() - except Exception: - return None - - def getTags(self, name, attrs={}, namespace=None, one=0): - """ - Filter all child nodes using specified arguments as filter. Returns the - list of nodes found - """ - nodes = [] - for node in self.kids: - if namespace and namespace != node.getNamespace(): - continue - if node.getName() == name: - for key in attrs.keys(): - if key not in node.attrs or node.attrs[key]!=attrs[key]: - break - else: - nodes.append(node) - if one and nodes: - return nodes[0] - if not one: - return nodes - - def iterTags(self, name, attrs={}, namespace=None): - """ - Iterate over all children using specified arguments as filter - """ - for node in self.kids: - if namespace is not None and namespace != node.getNamespace(): - continue - if node.getName() == name: - for key in attrs.keys(): - if key not in node.attrs or \ - node.attrs[key]!=attrs[key]: - break - else: - yield node - - def setAttr(self, key, val): - """ - Set attribute "key" with the value "val" - """ - self.attrs[key] = val - - def setData(self, data): - """ - Set node's CDATA to provided string. Resets all previous CDATA! - """ - self.data = [ustr(data)] - - def setName(self, val): - """ - Change the node name - """ - self.name = val - - def setNamespace(self, namespace): - """ - Changes the node namespace - """ - self.namespace = namespace - - def setParent(self, node): - """ - Set node's parent to "node". WARNING: do not checks if the parent already - present and not removes the node from the list of childs of previous - parent - """ - self.parent = node - - def setPayload(self, payload, add=0): - """ - Set node payload according to the list specified. WARNING: completely - replaces all node's previous content. If you wish just to add child or - CDATA - use addData or addChild methods - """ - if isinstance(payload, basestring): - payload = [payload] - if add: - self.kids += payload - else: - self.kids = payload - - def setTag(self, name, attrs={}, namespace=None): - """ - Same as getTag but if the node with specified namespace/attributes not - found, creates such node and returns it - """ - node = self.getTags(name, attrs, namespace=namespace, one=1) - if node: - return node - else: - return self.addChild(name, attrs, namespace=namespace) - - def setTagAttr(self, tag, attr, val): - """ - Create new node (if not already present) with name "tag" and set it's - attribute "attr" to value "val" - """ - try: - self.getTag(tag).attrs[attr] = val - except Exception: - self.addChild(tag, attrs={attr: val}) - - def setTagData(self, tag, val, attrs={}): - """ - Creates new node (if not already present) with name "tag" and - (optionally) attributes "attrs" and sets it's CDATA to string "val" - """ - try: - self.getTag(tag, attrs).setData(ustr(val)) - except Exception: - self.addChild(tag, attrs, payload = [ustr(val)]) - - def has_attr(self, key): - """ - Check if node have attribute "key" - """ - return key in self.attrs - - def __getitem__(self, item): - """ - Return node's attribute "item" value - """ - return self.getAttr(item) - - def __setitem__(self, item, val): - """ - Set node's attribute "item" value - """ - return self.setAttr(item, val) - - def __delitem__(self, item): - """ - Delete node's attribute "item" - """ - return self.delAttr(item) - - def __contains__(self, item): - """ - Check if node has attribute "item" - """ - return self.has_attr(item) - - def __getattr__(self, attr): - """ - Reduce memory usage caused by T/NT classes - use memory only when needed - """ - if attr == 'T': - self.T = T(self) - return self.T - if attr == 'NT': - self.NT = NT(self) - return self.NT - raise AttributeError - -class T: - """ - Auxiliary class used to quick access to node's child nodes - """ - - def __init__(self, node): - self.__dict__['node'] = node - - def __getattr__(self, attr): - return self.node.setTag(attr) - - def __setattr__(self, attr, val): - if isinstance(val, Node): - Node.__init__(self.node.setTag(attr), node=val) - else: - return self.node.setTagData(attr, val) - - def __delattr__(self, attr): - return self.node.delChild(attr) - -class NT(T): - """ - Auxiliary class used to quick create node's child nodes - """ - - def __getattr__(self, attr): - return self.node.addChild(attr) - - def __setattr__(self, attr, val): - if isinstance(val, Node): - self.node.addChild(attr, node=val) - else: - return self.node.addChild(attr, payload=[val]) - -class NodeBuilder: - """ - Builds a Node class minidom from data parsed to it. This class used for two - purposes: - 1. Creation an XML Node from a textual representation. F.e. reading a - config file. See an XML2Node method. - 2. Handling an incoming XML stream. This is done by mangling the - __dispatch_depth parameter and redefining the dispatch method. - - You do not need to use this class directly if you do not designing your own - XML handler - """ - - def __init__(self, data=None, initial_node=None): - """ - Take two optional parameters: "data" and "initial_node" - - By default class initialised with empty Node class instance. Though, if - "initial_node" is provided it used as "starting point". You can think - about it as of "node upgrade". "data" (if provided) feeded to parser - immidiatedly after instance init. - """ - log.debug("Preparing to handle incoming XML stream.") - self._parser = xml.parsers.expat.ParserCreate() - self._parser.StartElementHandler = self.starttag - self._parser.EndElementHandler = self.endtag - self._parser.StartNamespaceDeclHandler = self.handle_namespace_start - self._parser.CharacterDataHandler = self.handle_cdata - self._parser.buffer_text = True - self.Parse = self._parser.Parse - - self.__depth = 0 - self.__last_depth = 0 - self.__max_depth = 0 - self._dispatch_depth = 1 - self._document_attrs = None - self._document_nsp = None - self._mini_dom=initial_node - self.last_is_data = 1 - self._ptr=None - self.data_buffer = None - self.streamError = '' - if data: - self._parser.Parse(data, 1) - - def check_data_buffer(self): - if self.data_buffer: - self._ptr.data.append(''.join(self.data_buffer)) - del self.data_buffer[:] - self.data_buffer = None - - def destroy(self): - """ - Method used to allow class instance to be garbage-collected - """ - self.check_data_buffer() - self._parser.StartElementHandler = None - self._parser.EndElementHandler = None - self._parser.CharacterDataHandler = None - self._parser.StartNamespaceDeclHandler = None - - def starttag(self, tag, attrs): - """ - XML Parser callback. Used internally - """ - self.check_data_buffer() - self._inc_depth() - log.info("STARTTAG.. DEPTH -> %i , tag -> %s, attrs -> %s" % (self.__depth, tag, `attrs`)) - if self.__depth == self._dispatch_depth: - if not self._mini_dom : - self._mini_dom = Node(tag=tag, attrs=attrs, nsp = self._document_nsp, node_built=True) - else: - Node.__init__(self._mini_dom, tag=tag, attrs=attrs, nsp = self._document_nsp, node_built=True) - self._ptr = self._mini_dom - elif self.__depth > self._dispatch_depth: - self._ptr.kids.append(Node(tag=tag, parent=self._ptr, attrs=attrs, node_built=True)) - self._ptr = self._ptr.kids[-1] - if self.__depth == 1: - self._document_attrs = {} - self._document_nsp = {} - nsp, name = (['']+tag.split(':'))[-2:] - for attr, val in attrs.items(): - if attr == 'xmlns': - self._document_nsp[u''] = val - elif attr.startswith('xmlns:'): - self._document_nsp[attr[6:]] = val - else: - self._document_attrs[attr] = val - ns = self._document_nsp.get(nsp, 'http://www.gajim.org/xmlns/undeclared-root') - try: - self.stream_header_received(ns, name, attrs) - except ValueError, e: - self._document_attrs = None - raise ValueError(str(e)) - if not self.last_is_data and self._ptr.parent: - self._ptr.parent.data.append('') - self.last_is_data = 0 - - def endtag(self, tag ): - """ - XML Parser callback. Used internally - """ - log.info("DEPTH -> %i , tag -> %s" % (self.__depth, tag)) - self.check_data_buffer() - if self.__depth == self._dispatch_depth: - if self._mini_dom.getName() == 'error': - children = self._mini_dom.getChildren() - if children: - self.streamError = children[0].getName() - else: - self.streamError = self._mini_dom.getData() - self.dispatch(self._mini_dom) - elif self.__depth > self._dispatch_depth: - self._ptr = self._ptr.parent - else: - log.info("Got higher than dispatch level. Stream terminated?") - self._dec_depth() - self.last_is_data = 0 - if self.__depth == 0: self.stream_footer_received() - - def handle_cdata(self, data): - if self.last_is_data: - if self.data_buffer: - self.data_buffer.append(data) - elif self._ptr: - self.data_buffer = [data] - self.last_is_data = 1 - - def handle_namespace_start(self, prefix, uri): - """ - XML Parser callback. Used internally - """ - self.check_data_buffer() - - def getDom(self): - """ - Return just built Node - """ - self.check_data_buffer() - return self._mini_dom - - def dispatch(self, stanza): - """ - Get called when the NodeBuilder reaches some level of depth on it's way - up with the built node as argument. Can be redefined to convert incoming - XML stanzas to program events - """ - pass - - def stream_header_received(self, ns, tag, attrs): - """ - Method called when stream just opened - """ - self.check_data_buffer() - - def stream_footer_received(self): - """ - Method called when stream just closed - """ - self.check_data_buffer() - - def has_received_endtag(self, level=0): - """ - Return True if at least one end tag was seen (at level) - """ - return self.__depth <= level and self.__max_depth > level - - def _inc_depth(self): - self.__last_depth = self.__depth - self.__depth += 1 - self.__max_depth = max(self.__depth, self.__max_depth) - - def _dec_depth(self): - self.__last_depth = self.__depth - self.__depth -= 1 - -def XML2Node(xml): - """ - Convert supplied textual string into XML node. Handy f.e. for reading - configuration file. Raises xml.parser.expat.parsererror if provided string - is not well-formed XML - """ - return NodeBuilder(xml).getDom() - -def BadXML2Node(xml): - """ - Convert supplied textual string into XML node. Survives if xml data is - cutted half way round. I.e. "<html>some text <br>some more text". Will raise - xml.parser.expat.parsererror on misplaced tags though. F.e. "<b>some text - <br>some more text</b>" will not work - """ - return NodeBuilder(xml).getDom() diff --git a/src/common/xmpp/smacks.py b/src/common/xmpp/smacks.py deleted file mode 100644 index d82785ac4..000000000 --- a/src/common/xmpp/smacks.py +++ /dev/null @@ -1,132 +0,0 @@ -from protocol import Acks -from protocol import NS_STREAM_MGMT -import logging -log = logging.getLogger('gajim.c.x.smacks') - -class Smacks(): - ''' - This is Smacks is the Stream Management class. It takes care of requesting - and sending acks. Also, it keeps track of the unhandled outgoing stanzas. - - The dispatcher has to be able to access this class to increment the - number of handled stanzas - ''' - - def __init__(self, con): - self.con = con # Connection object - self.out_h = 0 # Outgoing stanzas handled - self.in_h = 0 # Incoming stanzas handled - self.uqueue = [] # Unhandled stanzas queue - self.session_id = None - self.resumption = False # If server supports resume - # Max number of stanzas in queue before making a request - self.max_queue = 5 - self._owner = None - self.resuming = False - self.enabled = False # If SM is enabled - self.location = None - - def set_owner(self, owner): - self._owner = owner - - # Register handlers - owner.Dispatcher.RegisterNamespace(NS_STREAM_MGMT) - owner.Dispatcher.RegisterHandler('enabled', self._neg_response, - xmlns=NS_STREAM_MGMT) - owner.Dispatcher.RegisterHandler('r', self.send_ack, - xmlns=NS_STREAM_MGMT) - owner.Dispatcher.RegisterHandler('a', self.check_ack, - xmlns=NS_STREAM_MGMT) - owner.Dispatcher.RegisterHandler('resumed', self.check_ack, - xmlns=NS_STREAM_MGMT) - owner.Dispatcher.RegisterHandler('failed', self.error_handling, - xmlns=NS_STREAM_MGMT) - - def _neg_response(self, disp, stanza): - r = stanza.getAttr('resume') - if r == 'true' or r == 'True' or r == '1': - self.resumption = True - self.session_id = stanza.getAttr('id') - - if r == 'false' or r == 'False' or r == '0': - self.negociate(False) - - l = stanza.getAttr('location') - if l: - self.location = l - - def negociate(self, resume=True): - # Every time we attempt to negociate, we must erase all previous info - # about any previous session - self.uqueue = [] - self.in_h = 0 - self.out_h = 0 - self.session_id = None - self.enabled = True - - stanza = Acks() - stanza.buildEnable(resume) - self._owner.Connection.send(stanza, now=True) - - def resume_request(self): - if not self.session_id: - self.resuming = False - log.error('Attempted to resume without a valid session id ') - return - resume = Acks() - resume.buildResume(self.in_h, self.session_id) - self._owner.Connection.send(resume, False) - - def send_ack(self, disp, stanza): - ack = Acks() - ack.buildAnswer(self.in_h) - self._owner.Connection.send(ack, False) - - def request_ack(self): - r = Acks() - r.buildRequest() - self._owner.Connection.send(r, False) - - def check_ack(self, disp, stanza): - ''' - Checks if the number of stanzas sent are the same as the - number of stanzas received by the server. Pops stanzas that were - handled by the server from the queue. - ''' - h = int(stanza.getAttr('h')) - diff = self.out_h - h - - if len(self.uqueue) < diff or diff < 0: - log.error('Server and client number of stanzas handled mismatch ') - else: - while (len(self.uqueue) > diff): - self.uqueue.pop(0) - - if stanza.getName() == 'resumed': - self.enabled = True - self.resuming = True - self.con.set_oldst() - if self.uqueue != []: - for i in self.uqueue: - self._owner.Connection.send(i, False) - - def error_handling(self, disp, stanza): - # If the server doesn't recognize previd, forget about resuming - # Ask for service discovery, etc.. - if stanza.getTag('item-not-found'): - self.resuming = False - self.enabled = False - # we need to bind a resource - self._owner.NonBlockingBind.resuming = False - self._owner._on_auth_bind(None) - return - - # Doesn't support resumption - if stanza.getTag('feature-not-implemented'): - self.negociate(False) - return - - if stanza.getTag('unexpected-request'): - self.enabled = False - log.error('Gajim failed to negociate Stream Management') - return diff --git a/src/common/xmpp/stringprepare.py b/src/common/xmpp/stringprepare.py deleted file mode 100644 index 5e3d343b5..000000000 --- a/src/common/xmpp/stringprepare.py +++ /dev/null @@ -1,238 +0,0 @@ -# -*- coding:utf-8 -*- -## src/common/xmpp/stringprepare.py -## -## Copyright (C) 2001-2005 Twisted Matrix Laboratories -## Copyright (C) 2005-2012 Yann Leboulanger <asterix AT lagaule.org> -## Copyright (C) 2006 Stefan Bethge <stefan AT lanpartei.de> -## Copyright (C) 2007 Jean-Marie Traissard <jim AT lapin.org> -## -## This file is part of Gajim. -## -## Gajim is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published -## by the Free Software Foundation; version 3 only. -## -## Gajim is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with Gajim. If not, see <http://www.gnu.org/licenses/>. -## - -import stringprep -import unicodedata -from encodings import idna - -class ILookupTable: - """ - Interface for character lookup classes - """ - - def lookup(self, c): - """ - Return whether character is in this table - """ - pass - -class IMappingTable: - """ - Interface for character mapping classes - """ - - def map(self, c): - """ - Return mapping for character - """ - pass - -class LookupTableFromFunction: - - __implements__ = ILookupTable - - def __init__(self, in_table_function): - self.lookup = in_table_function - -class LookupTable: - - __implements__ = ILookupTable - - def __init__(self, table): - self._table = table - - def lookup(self, c): - return c in self._table - -class MappingTableFromFunction: - - __implements__ = IMappingTable - - def __init__(self, map_table_function): - self.map = map_table_function - -class EmptyMappingTable: - - __implements__ = IMappingTable - - def __init__(self, in_table_function): - self._in_table_function = in_table_function - - def map(self, c): - if self._in_table_function(c): - return None - else: - return c - -class Profile: - def __init__(self, mappings=[], normalize=True, prohibiteds=[], - check_unassigneds=True, check_bidi=True): - self.mappings = mappings - self.normalize = normalize - self.prohibiteds = prohibiteds - self.do_check_unassigneds = check_unassigneds - self.do_check_bidi = check_bidi - - def prepare(self, string): - result = self.map(string) - if self.normalize: - result = unicodedata.normalize("NFKC", result) - self.check_prohibiteds(result) - if self.do_check_unassigneds: - self.check_unassigneds(result) - if self.do_check_bidi: - self.check_bidirectionals(result) - return result - - def map(self, string): - result = [] - - for c in string: - result_c = c - - for mapping in self.mappings: - result_c = mapping.map(c) - if result_c != c: - break - - if result_c is not None: - result.append(result_c) - - return u"".join(result) - - def check_prohibiteds(self, string): - for c in string: - for table in self.prohibiteds: - if table.lookup(c): - raise UnicodeError, "Invalid character %s" % repr(c) - - def check_unassigneds(self, string): - for c in string: - if stringprep.in_table_a1(c): - raise UnicodeError, "Unassigned code point %s" % repr(c) - - def check_bidirectionals(self, string): - found_LCat = False - found_RandALCat = False - - for c in string: - if stringprep.in_table_d1(c): - found_RandALCat = True - if stringprep.in_table_d2(c): - found_LCat = True - - if found_LCat and found_RandALCat: - raise UnicodeError, "Violation of BIDI Requirement 2" - - if found_RandALCat and not (stringprep.in_table_d1(string[0]) and - stringprep.in_table_d1(string[-1])): - raise UnicodeError, "Violation of BIDI Requirement 3" - - -class NamePrep: - """ - Implements preparation of internationalized domain names - - This class implements preparing internationalized domain names using the - rules defined in RFC 3491, section 4 (Conversion operations). - - We do not perform step 4 since we deal with unicode representations of - domain names and do not convert from or to ASCII representations using - punycode encoding. When such a conversion is needed, the L{idna} standard - library provides the C{ToUnicode()} and C{ToASCII()} functions. Note that - L{idna} itself assumes UseSTD3ASCIIRules to be false. - - The following steps are performed by C{prepare()}: - - * Split the domain name in labels at the dots (RFC 3490, 3.1) - * Apply nameprep proper on each label (RFC 3491) - * Enforce the restrictions on ASCII characters in host names by - assuming STD3ASCIIRules to be true. (STD 3) - * Rejoin the labels using the label separator U+002E (full stop). - """ - - # Prohibited characters. - prohibiteds = [unichr(n) for n in range(0x00, 0x2c + 1) + - range(0x2e, 0x2f + 1) + - range(0x3a, 0x40 + 1) + - range(0x5b, 0x60 + 1) + - range(0x7b, 0x7f + 1) ] - - def prepare(self, string): - result = [] - - labels = idna.dots.split(string) - - if labels and len(labels[-1]) == 0: - trailing_dot = '.' - del labels[-1] - else: - trailing_dot = '' - - for label in labels: - result.append(self.nameprep(label)) - - return ".".join(result)+trailing_dot - - def check_prohibiteds(self, string): - for c in string: - if c in self.prohibiteds: - raise UnicodeError, "Invalid character %s" % repr(c) - - def nameprep(self, label): - label = idna.nameprep(label) - self.check_prohibiteds(label) - if len(label) == 0: - raise UnicodeError, "Invalid empty name" - if label[0] == '-': - raise UnicodeError, "Invalid leading hyphen-minus" - if label[-1] == '-': - raise UnicodeError, "Invalid trailing hyphen-minus" - return label - -C_11 = LookupTableFromFunction(stringprep.in_table_c11) -C_12 = LookupTableFromFunction(stringprep.in_table_c12) -C_21 = LookupTableFromFunction(stringprep.in_table_c21) -C_22 = LookupTableFromFunction(stringprep.in_table_c22) -C_3 = LookupTableFromFunction(stringprep.in_table_c3) -C_4 = LookupTableFromFunction(stringprep.in_table_c4) -C_5 = LookupTableFromFunction(stringprep.in_table_c5) -C_6 = LookupTableFromFunction(stringprep.in_table_c6) -C_7 = LookupTableFromFunction(stringprep.in_table_c7) -C_8 = LookupTableFromFunction(stringprep.in_table_c8) -C_9 = LookupTableFromFunction(stringprep.in_table_c9) - -B_1 = EmptyMappingTable(stringprep.in_table_b1) -B_2 = MappingTableFromFunction(stringprep.map_table_b2) - -nodeprep = Profile(mappings=[B_1, B_2], - prohibiteds=[C_11, C_12, C_21, C_22, - C_3, C_4, C_5, C_6, C_7, C_8, C_9, - LookupTable([u'"', u'&', u"'", u'/', - u':', u'<', u'>', u'@'])]) - -resourceprep = Profile(mappings=[B_1,], - prohibiteds=[C_12, C_21, C_22, - C_3, C_4, C_5, C_6, C_7, C_8, C_9]) - -nameprep = NamePrep() diff --git a/src/common/xmpp/tls_nb.py b/src/common/xmpp/tls_nb.py deleted file mode 100644 index 9425bcf17..000000000 --- a/src/common/xmpp/tls_nb.py +++ /dev/null @@ -1,461 +0,0 @@ -## tls_nb.py -## based on transports_nb.py -## -## Copyright (C) 2003-2004 Alexey "Snake" Nezhdanov -## modified by Dimitur Kirov <dkirov@gmail.com> -## modified by Tomas Karasek <tom.to.the.k@gmail.com> -## -## This program is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation; either version 2, or (at your option) -## any later version. -## -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. - -import socket -from plugin import PlugIn - -import sys -import os -import time - -import traceback - -import logging -log = logging.getLogger('gajim.c.x.tls_nb') - -USE_PYOPENSSL = False - -PYOPENSSL = 'PYOPENSSL' -PYSTDLIB = 'PYSTDLIB' - -try: - #raise ImportError("Manually disabled PyOpenSSL") - import OpenSSL.SSL - import OpenSSL.crypto - USE_PYOPENSSL = True - log.info("PyOpenSSL loaded") -except ImportError: - log.debug("Import of PyOpenSSL failed:", exc_info=True) - - # FIXME: Remove these prints before release, replace with a warning dialog. - print >> sys.stderr, "=" * 79 - print >> sys.stderr, "PyOpenSSL not found, falling back to Python builtin SSL objects (insecure)." - print >> sys.stderr, "=" * 79 - -def gattr(obj, attr, default=None): - try: - return getattr(obj, attr) - except AttributeError: - return default - - -class SSLWrapper: - """ - Abstract SSLWrapper base class - """ - - class Error(IOError): - """ - Generic SSL Error Wrapper - """ - - def __init__(self, sock=None, exc=None, errno=None, strerror=None, - peer=None): - self.parent = IOError - - errno = errno or gattr(exc, 'errno') or exc[0] - strerror = strerror or gattr(exc, 'strerror') or gattr(exc, 'args') - if not isinstance(strerror, basestring): - strerror = repr(strerror) - - self.sock = sock - self.exc = exc - self.peer = peer - self.exc_name = None - self.exc_args = None - self.exc_str = None - self.exc_repr = None - - if self.exc is not None: - self.exc_name = str(self.exc.__class__) - self.exc_args = gattr(self.exc, 'args') - self.exc_str = str(self.exc) - self.exc_repr = repr(self.exc) - if not errno: - try: - if isinstance(exc, OpenSSL.SSL.SysCallError): - if self.exc_args[0] > 0: - errno = self.exc_args[0] - strerror = self.exc_args[1] - except: pass - - self.parent.__init__(self, errno, strerror) - - if self.peer is None and sock is not None: - try: - ppeer = self.sock.getpeername() - if len(ppeer) == 2 and isinstance(ppeer[0], basestring) \ - and isinstance(ppeer[1], int): - self.peer = ppeer - except: - pass - - def __str__(self): - s = str(self.__class__) - if self.peer: - s += " for %s:%d" % self.peer - if self.errno is not None: - s += ": [Errno: %d]" % self.errno - if self.strerror: - s += " (%s)" % self.strerror - if self.exc_name: - s += ", Caused by %s" % self.exc_name - if self.exc_str: - if self.strerror: - s += "(%s)" % self.exc_str - else: s += "(%s)" % str(self.exc_args) - return s - - def __init__(self, sslobj, sock=None): - self.sslobj = sslobj - self.sock = sock - log.debug("%s.__init__ called with %s", self.__class__, sslobj) - - def recv(self, data, flags=None): - """ - Receive wrapper for SSL object - - We can return None out of this function to signal that no data is - available right now. Better than an exception, which differs - depending on which SSL lib we're using. Unfortunately returning '' - can indicate that the socket has been closed, so to be sure, we avoid - this by returning None. - """ - raise NotImplementedError - - def send(self, data, flags=None, now=False): - """ - Send wrapper for SSL object - """ - raise NotImplementedError - - -class PyOpenSSLWrapper(SSLWrapper): - """ - Wrapper class for PyOpenSSL's recv() and send() methods - """ - - def __init__(self, *args): - self.parent = SSLWrapper - self.parent.__init__(self, *args) - - def is_numtoolarge(self, e): - ''' Magic methods don't need documentation ''' - t = ('asn1 encoding routines', 'a2d_ASN1_OBJECT', 'first num too large') - return (isinstance(e.args, (list, tuple)) and len(e.args) == 1 and - isinstance(e.args[0], (list, tuple)) and len(e.args[0]) == 2 and - e.args[0][0] == e.args[0][1] == t) - - def recv(self, bufsize, flags=None): - retval = None - try: - if flags is None: - retval = self.sslobj.recv(bufsize) - else: - retval = self.sslobj.recv(bufsize, flags) - except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError), e: - log.debug("Recv: Want-error: " + repr(e)) - except OpenSSL.SSL.SysCallError, e: - log.debug("Recv: Got OpenSSL.SSL.SysCallError: " + repr(e), - exc_info=True) - raise SSLWrapper.Error(self.sock or self.sslobj, e) - except OpenSSL.SSL.ZeroReturnError, e: - # end-of-connection raises ZeroReturnError instead of having the - # connection's .recv() method return a zero-sized result. - raise SSLWrapper.Error(self.sock or self.sslobj, e, -1) - except OpenSSL.SSL.Error, e: - if self.is_numtoolarge(e): - # warn, but ignore this exception - log.warning("Recv: OpenSSL: asn1enc: first num too large (ignored)") - else: - log.debug("Recv: Caught OpenSSL.SSL.Error:", exc_info=True) - raise SSLWrapper.Error(self.sock or self.sslobj, e) - return retval - - def send(self, data, flags=None, now=False): - try: - if flags is None: - return self.sslobj.send(data) - else: - return self.sslobj.send(data, flags) - except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError), e: - #log.debug("Send: " + repr(e)) - time.sleep(0.1) # prevent 100% CPU usage - except OpenSSL.SSL.SysCallError, e: - log.error("Send: Got OpenSSL.SSL.SysCallError: " + repr(e), - exc_info=True) - raise SSLWrapper.Error(self.sock or self.sslobj, e) - except OpenSSL.SSL.Error, e: - if self.is_numtoolarge(e): - # warn, but ignore this exception - log.warning("Send: OpenSSL: asn1enc: first num too large (ignored)") - else: - log.error("Send: Caught OpenSSL.SSL.Error:", exc_info=True) - raise SSLWrapper.Error(self.sock or self.sslobj, e) - return 0 - - -class StdlibSSLWrapper(SSLWrapper): - """ - Wrapper class for Python socket.ssl read() and write() methods - """ - - def __init__(self, *args): - self.parent = SSLWrapper - self.parent.__init__(self, *args) - - def recv(self, bufsize, flags=None): - # we simply ignore flags since ssl object doesn't support it - try: - return self.sslobj.read(bufsize) - except socket.sslerror, e: - log.debug("Recv: Caught socket.sslerror: " + repr(e), exc_info=True) - if e.args[0] not in (socket.SSL_ERROR_WANT_READ, socket.SSL_ERROR_WANT_WRITE): - raise SSLWrapper.Error(self.sock or self.sslobj, e) - return None - - def send(self, data, flags=None, now=False): - # we simply ignore flags since ssl object doesn't support it - try: - return self.sslobj.write(data) - except socket.sslerror, e: - log.debug("Send: Caught socket.sslerror:", exc_info=True) - if e.args[0] not in (socket.SSL_ERROR_WANT_READ, socket.SSL_ERROR_WANT_WRITE): - raise SSLWrapper.Error(self.sock or self.sslobj, e) - return 0 - - -class NonBlockingTLS(PlugIn): - """ - TLS connection used to encrypts already estabilished tcp connection - - Can be plugged into NonBlockingTCP and will make use of StdlibSSLWrapper or - PyOpenSSLWrapper. - """ - - def __init__(self, cacerts, mycerts): - """ - :param cacerts: path to pem file with certificates of known XMPP servers - :param mycerts: path to pem file with certificates of user trusted servers - """ - PlugIn.__init__(self) - self.cacerts = cacerts - self.mycerts = mycerts - - # from ssl.h (partial extract) - ssl_h_bits = { "SSL_ST_CONNECT": 0x1000, "SSL_ST_ACCEPT": 0x2000, - "SSL_CB_LOOP": 0x01, "SSL_CB_EXIT": 0x02, - "SSL_CB_READ": 0x04, "SSL_CB_WRITE": 0x08, - "SSL_CB_ALERT": 0x4000, - "SSL_CB_HANDSHAKE_START": 0x10, "SSL_CB_HANDSHAKE_DONE": 0x20} - - def plugin(self, owner): - """ - Use to PlugIn TLS into transport and start establishing immediately. - Returns True if TLS/SSL was established correctly, otherwise False - """ - log.info('Starting TLS estabilishing') - try: - res = self._startSSL() - except Exception, e: - log.error("PlugIn: while trying _startSSL():", exc_info=True) - return False - return res - - def _dumpX509(self, cert, stream=sys.stderr): - print >> stream, "Digest (SHA-1):", cert.digest("sha1") - print >> stream, "Digest (MD5):", cert.digest("md5") - print >> stream, "Serial #:", cert.get_serial_number() - print >> stream, "Version:", cert.get_version() - print >> stream, "Expired:", ("Yes" if cert.has_expired() else "No") - print >> stream, "Subject:" - self._dumpX509Name(cert.get_subject(), stream) - print >> stream, "Issuer:" - self._dumpX509Name(cert.get_issuer(), stream) - self._dumpPKey(cert.get_pubkey(), stream) - - def _dumpX509Name(self, name, stream=sys.stderr): - print >> stream, "X509Name:", str(name) - - def _dumpPKey(self, pkey, stream=sys.stderr): - typedict = {OpenSSL.crypto.TYPE_RSA: "RSA", - OpenSSL.crypto.TYPE_DSA: "DSA"} - print >> stream, "PKey bits:", pkey.bits() - print >> stream, "PKey type: %s (%d)" % (typedict.get(pkey.type(), - "Unknown"), pkey.type()) - - def _startSSL(self): - """ - Immediatedly switch socket to TLS mode. Used internally - """ - log.debug("_startSSL called") - - if USE_PYOPENSSL: - result = self._startSSL_pyOpenSSL() - else: - result = self._startSSL_stdlib() - - if result: - log.debug('Synchronous handshake completed') - return True - else: - return False - - def _load_cert_file(self, cert_path, cert_store, logg=True): - if not os.path.isfile(cert_path): - return - try: - f = open(cert_path) - except IOError, e: - log.warning('Unable to open certificate file %s: %s' % \ - (cert_path, str(e))) - return - lines = f.readlines() - i = 0 - begin = -1 - for line in lines: - if 'BEGIN CERTIFICATE' in line: - begin = i - elif 'END CERTIFICATE' in line and begin > -1: - cert = ''.join(lines[begin:i+2]) - try: - x509cert = OpenSSL.crypto.load_certificate( - OpenSSL.crypto.FILETYPE_PEM, cert) - cert_store.add_cert(x509cert) - except OpenSSL.crypto.Error, exception_obj: - if logg: - log.warning('Unable to load a certificate from file %s: %s' %\ - (cert_path, exception_obj.args[0][0][2])) - except: - log.warning('Unknown error while loading certificate from file ' - '%s' % cert_path) - begin = -1 - i += 1 - - def _startSSL_pyOpenSSL(self): - log.debug("_startSSL_pyOpenSSL called") - tcpsock = self._owner - # NonBlockingHTTPBOSH instance has no attribute _owner - if hasattr(tcpsock, '_owner') and tcpsock._owner._caller.client_cert \ - and os.path.exists(tcpsock._owner._caller.client_cert): - conn = tcpsock._owner._caller - # FIXME make a checkbox for Client Cert / SSLv23 / TLSv1 - # If we are going to use a client cert/key pair for authentication, - # we choose TLSv1 method. - tcpsock._sslContext = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_METHOD) - log.debug('Using client cert and key from %s' % conn.client_cert) - try: - p12 = OpenSSL.crypto.load_pkcs12(open(conn.client_cert).read(), - conn.client_cert_passphrase) - except OpenSSL.crypto.Error, exception_obj: - log.warning('Unable to load client pkcs12 certificate from ' - 'file %s: %s ... Is it a valid PKCS12 cert?' % \ - (conn.client_cert, exception_obj.args)) - except: - log.warning('Unknown error while loading certificate from file ' - '%s' % conn.client_cert) - else: - log.info('PKCS12 Client cert loaded OK') - try: - tcpsock._sslContext.use_certificate(p12.get_certificate()) - tcpsock._sslContext.use_privatekey(p12.get_privatekey()) - log.info('p12 cert and key loaded') - except OpenSSL.crypto.Error, exception_obj: - log.warning('Unable to extract client certificate from ' - 'file %s' % conn.client_cert) - except Exception, msg: - log.warning('Unknown error extracting client certificate ' - 'from file %s: %s' % (conn.client_cert, msg)) - else: - log.info('client cert and key loaded OK') - else: - # See http://docs.python.org/dev/library/ssl.html - tcpsock._sslContext = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD) - flags = OpenSSL.SSL.OP_NO_SSLv2 - try: - flags |= OpenSSL.SSL.OP_NO_TICKET - except AttributeError, e: - # py-OpenSSL < 0.9 or old OpenSSL - flags |= 16384 - tcpsock._sslContext.set_options(flags) - - tcpsock.ssl_errnum = 0 - tcpsock._sslContext.set_verify(OpenSSL.SSL.VERIFY_PEER, - self._ssl_verify_callback) - try: - tcpsock._sslContext.load_verify_locations(self.cacerts) - except: - log.warning('Unable to load SSL certificates from file %s' % \ - os.path.abspath(self.cacerts)) - store = tcpsock._sslContext.get_cert_store() - self._load_cert_file(self.mycerts, store) - if os.path.isdir('/etc/ssl/certs'): - for f in os.listdir('/etc/ssl/certs'): - # We don't logg because there is a lot a duplicated certs in this - # folder - self._load_cert_file(os.path.join('/etc/ssl/certs', f), store, - logg=False) - - tcpsock._sslObj = OpenSSL.SSL.Connection(tcpsock._sslContext, - tcpsock._sock) - tcpsock._sslObj.set_connect_state() # set to client mode - wrapper = PyOpenSSLWrapper(tcpsock._sslObj) - tcpsock._recv = wrapper.recv - tcpsock._send = wrapper.send - - log.debug("Initiating handshake...") - try: - tcpsock._sslObj.do_handshake() - except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError), e: - pass - except: - log.error('Error while TLS handshake: ', exc_info=True) - return False - self._owner.ssl_lib = PYOPENSSL - return True - - def _startSSL_stdlib(self): - log.debug("_startSSL_stdlib called") - tcpsock=self._owner - try: - tcpsock._sock.setblocking(True) - tcpsock._sslObj = socket.ssl(tcpsock._sock, None, None) - tcpsock._sock.setblocking(False) - tcpsock._sslIssuer = tcpsock._sslObj.issuer() - tcpsock._sslServer = tcpsock._sslObj.server() - wrapper = StdlibSSLWrapper(tcpsock._sslObj, tcpsock._sock) - tcpsock._recv = wrapper.recv - tcpsock._send = wrapper.send - except: - log.error("Exception caught in _startSSL_stdlib:", exc_info=True) - return False - self._owner.ssl_lib = PYSTDLIB - return True - - def _ssl_verify_callback(self, sslconn, cert, errnum, depth, ok): - # Exceptions can't propagate up through this callback, so print them here. - try: - self._owner.ssl_fingerprint_sha1 = cert.digest('sha1') - self._owner.ssl_certificate = cert - self._owner.ssl_errnum = errnum - self._owner.ssl_cert_pem = OpenSSL.crypto.dump_certificate( - OpenSSL.crypto.FILETYPE_PEM, cert) - return True - except: - log.error("Exception caught in _ssl_info_callback:", exc_info=True) - # Make sure something is printed, even if log is disabled. - traceback.print_exc() diff --git a/src/common/xmpp/transports_nb.py b/src/common/xmpp/transports_nb.py deleted file mode 100644 index 9430ef654..000000000 --- a/src/common/xmpp/transports_nb.py +++ /dev/null @@ -1,786 +0,0 @@ -## transports_nb.py -## based on transports.py -## -## Copyright (C) 2003-2004 Alexey "Snake" Nezhdanov -## modified by Dimitur Kirov <dkirov@gmail.com> -## modified by Tomas Karasek <tom.to.the.k@gmail.com> -## -## This program is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation; either version 2, or (at your option) -## any later version. -## -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. - -""" -Transports are objects responsible for connecting to XMPP server and putting -data to wrapped sockets in in desired form (SSL, TLS, TCP, for HTTP proxy, -for SOCKS5 proxy...) - -Transports are not aware of XMPP stanzas and only responsible for low-level -connection handling. -""" - -from simplexml import ustr -from plugin import PlugIn -from idlequeue import IdleObject -import proxy_connectors -import tls_nb - -import socket -import errno -import time -import traceback -import base64 -import urlparse - -import logging -log = logging.getLogger('gajim.c.x.transports_nb') - -def urisplit(uri): - """ - Function for splitting URI string to tuple (protocol, host, port, path). - e.g. urisplit('http://httpcm.jabber.org:123/webclient') returns ('http', - 'httpcm.jabber.org', 123, '/webclient') return 443 as default port if proto - is https else 80 - """ - splitted = urlparse.urlsplit(uri) - proto, host, path = splitted.scheme, splitted.hostname, splitted.path - try: - port = splitted.port - except ValueError: - log.warn('port cannot be extracted from BOSH URL %s, using default port' \ - % uri) - port = '' - if not port: - if proto == 'https': - port = 443 - else: - port = 80 - return proto, host, port, path - -def get_proxy_data_from_dict(proxy): - tcp_host, tcp_port, proxy_user, proxy_pass = None, None, None, None - proxy_type = proxy['type'] - if proxy_type == 'bosh' and not proxy['bosh_useproxy']: - # with BOSH not over proxy we have to parse the hostname from BOSH URI - proto, tcp_host, tcp_port, path = urisplit(proxy['bosh_uri']) - else: - # with proxy!=bosh or with bosh over HTTP proxy we're connecting to proxy - # machine - tcp_host, tcp_port = proxy['host'], proxy['port'] - if proxy.get('useauth', False): - proxy_user, proxy_pass = proxy['user'], proxy['pass'] - return tcp_host, tcp_port, proxy_user, proxy_pass - -#: timeout to connect to the server socket, it doesn't include auth -CONNECT_TIMEOUT_SECONDS = 30 - -#: how long to wait for a disconnect to complete -DISCONNECT_TIMEOUT_SECONDS = 5 - -#: size of the buffer which reads data from server -# if lower, more stanzas will be fragmented and processed twice -RECV_BUFSIZE = 32768 # 2x maximum size of ssl packet, should be plenty -# FIXME: (#2634) gajim breaks with #RECV_BUFSIZE = 16 -# it's inefficient but should work. Problem is that connect machine makes wrong -# assumptions and that we only check for pending data in sockets but not in SSL -# buffer... - -DATA_RECEIVED = 'DATA RECEIVED' -DATA_SENT = 'DATA SENT' -DATA_ERROR = 'DATA ERROR' - -DISCONNECTED = 'DISCONNECTED' -DISCONNECTING = 'DISCONNECTING' -CONNECTING = 'CONNECTING' -PROXY_CONNECTING = 'PROXY_CONNECTING' -CONNECTED = 'CONNECTED' -STATES = (DISCONNECTED, CONNECTING, PROXY_CONNECTING, CONNECTED, DISCONNECTING) - -class NonBlockingTransport(PlugIn): - """ - Abstract class representing a transport - - Subclasses CAN have different constructor signature but connect method SHOULD - be the same. - """ - - def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls, - certs): - """ - Each trasport class can have different constructor but it has to have at - least all the arguments of NonBlockingTransport constructor - - :param raise_event: callback for monitoring of sent and received data - :param on_disconnect: callback called on disconnection during runtime - :param idlequeue: processing idlequeue - :param estabilish_tls: boolean whether to estabilish TLS connection after - TCP connection is done - :param certs: tuple of (cacerts, mycerts) see constructor of - tls_nb.NonBlockingTLS for more details - """ - PlugIn.__init__(self) - self.raise_event = raise_event - self.on_disconnect = on_disconnect - self.on_connect = None - self.on_connect_failure = None - self.idlequeue = idlequeue - self.on_receive = None - self.server = None - self.port = None - self.conn_5tuple = None - self.set_state(DISCONNECTED) - self.estabilish_tls = estabilish_tls - self.certs = certs - # type of used ssl lib (if any) will be assigned to this member var - self.ssl_lib = None - self._exported_methods=[self.onreceive, self.set_send_timeout, - self.set_send_timeout2, self.set_timeout, self.remove_timeout, - self.start_disconnect] - - # time to wait for SOME stanza to come and then send keepalive - self.sendtimeout = 0 - - # in case we want to something different than sending keepalives - self.on_timeout = None - self.on_timeout2 = None - - def plugin(self, owner): - owner.Connection = self - - def plugout(self): - self._owner.Connection = None - self._owner = None - self.disconnect(do_callback=False) - - def connect(self, conn_5tuple, on_connect, on_connect_failure): - """ - Creates and connects transport to server and port defined in conn_5tuple - which should be item from list returned from getaddrinfo - - :param conn_5tuple: 5-tuple returned from getaddrinfo - :param on_connect: callback called on successful connect to the server - :param on_connect_failure: callback called on failure when connecting - """ - self.on_connect = on_connect - self.on_connect_failure = on_connect_failure - self.server, self.port = conn_5tuple[4][:2] - self.conn_5tuple = conn_5tuple - - def set_state(self, newstate): - assert(newstate in STATES) - self.state = newstate - - def get_state(self): - return self.state - - def _on_connect(self): - """ - Preceeds call of on_connect callback - """ - # data is reference to socket wrapper instance. We don't need it in client - # because - self.set_state(CONNECTED) - self.on_connect() - - def _on_connect_failure(self, err_message): - """ - Preceeds call of on_connect_failure callback - """ - # In case of error while connecting we need to disconnect transport - # but we don't want to call DisconnectHandlers from client, - # thus the do_callback=False - self.disconnect(do_callback=False) - self.on_connect_failure(err_message=err_message) - - def send(self, raw_data, now=False): - if self.get_state() == DISCONNECTED: - log.error('Unable to send %s \n because state is %s.' % - (raw_data, self.get_state())) - - def disconnect(self, do_callback=True): - self.set_state(DISCONNECTED) - if do_callback: - # invoke callback given in __init__ - self.on_disconnect() - - def onreceive(self, recv_handler): - """ - Set the on_receive callback. - - onreceive(None) sets callback to Dispatcher.ProcessNonBlocking which is - the default one that will decide what to do with received stanza based on - its tag name and namespace. - - Do not confuse it with on_receive() method, which is the callback - itself. - """ - if not recv_handler: - if hasattr(self, '_owner') and hasattr(self._owner, 'Dispatcher'): - self.on_receive = self._owner.Dispatcher.ProcessNonBlocking - else: - log.warn('No Dispatcher plugged. Received data will not be processed') - self.on_receive = None - return - self.on_receive = recv_handler - - def _tcp_connecting_started(self): - self.set_state(CONNECTING) - - def read_timeout(self): - """ - Called when there's no response from server in defined timeout - """ - if self.on_timeout: - self.on_timeout() - self.renew_send_timeout() - - def read_timeout2(self): - """ - called when there's no response from server in defined timeout - """ - if self.on_timeout2: - self.on_timeout2() - self.renew_send_timeout2() - - def renew_send_timeout(self): - if self.on_timeout and self.sendtimeout > 0: - self.set_timeout(self.sendtimeout) - - def renew_send_timeout2(self): - if self.on_timeout2 and self.sendtimeout2 > 0: - self.set_timeout2(self.sendtimeout2) - - def set_timeout(self, timeout): - self.idlequeue.set_read_timeout(self.fd, timeout) - - def set_timeout2(self, timeout2): - self.idlequeue.set_read_timeout(self.fd, timeout2, self.read_timeout2) - - def get_fd(self): - pass - - def remove_timeout(self): - self.idlequeue.remove_timeout(self.fd) - - def set_send_timeout(self, timeout, on_timeout): - self.sendtimeout = timeout - if self.sendtimeout > 0: - self.on_timeout = on_timeout - else: - self.on_timeout = None - - def set_send_timeout2(self, timeout2, on_timeout2): - self.sendtimeout2 = timeout2 - if self.sendtimeout2 > 0: - self.on_timeout2 = on_timeout2 - else: - self.on_timeout2 = None - - # FIXME: where and why does this need to be called - def start_disconnect(self): - self.set_state(DISCONNECTING) - - -class NonBlockingTCP(NonBlockingTransport, IdleObject): - """ - Non-blocking TCP socket wrapper - - It is used for simple XMPP connection. Can be connected via proxy and can - estabilish TLS connection. - """ - def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls, - certs, proxy_dict=None): - """ - :param proxy_dict: dictionary with proxy data as loaded from config file - """ - NonBlockingTransport.__init__(self, raise_event, on_disconnect, idlequeue, - estabilish_tls, certs) - IdleObject.__init__(self) - - # queue with messages to be send - self.sendqueue = [] - - # bytes remained from the last send message - self.sendbuff = '' - - self.proxy_dict = proxy_dict - self.on_remote_disconnect = self.disconnect - - # FIXME: transport should not be aware xmpp - def start_disconnect(self): - NonBlockingTransport.start_disconnect(self) - self.send('</stream:stream>', now=True) - self.disconnect() - - def connect(self, conn_5tuple, on_connect, on_connect_failure): - NonBlockingTransport.connect(self, conn_5tuple, on_connect, - on_connect_failure) - log.info('NonBlockingTCP Connect :: About to connect to %s:%s' % - (self.server, self.port)) - - try: - self._sock = socket.socket(*conn_5tuple[:3]) - except socket.error, (errnum, errstr): - self._on_connect_failure('NonBlockingTCP Connect: Error while creating\ - socket: %s %s' % (errnum, errstr)) - return - - self._send = self._sock.send - self._recv = self._sock.recv - self.fd = self._sock.fileno() - - # we want to be notified when send is possible to connected socket because - # it means the TCP connection is estabilished - self._plug_idle(writable=True, readable=False) - self.peerhost = None - - # variable for errno symbol that will be found from exception raised - # from connect() - errnum = 0 - errstr = str() - - # set timeout for TCP connecting - if nonblocking connect() fails, pollend - # is called. If if succeeds pollout is called. - self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT_SECONDS) - - try: - self._sock.setblocking(False) - self._sock.connect((self.server, self.port)) - except Exception, exc: - errnum, errstr = exc.args - - if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK): - # connecting in progress - log.info('After NB connect() of %s. "%s" raised => CONNECTING' % - (id(self), errstr)) - self._tcp_connecting_started() - return - - # if there was some other exception, call failure callback and unplug - # transport which will also remove read_timeouts for descriptor - self._on_connect_failure('Exception while connecting to %s:%s - %s %s' % - (self.server, self.port, errnum, errstr)) - - def _connect_to_proxy(self): - self.set_state(PROXY_CONNECTING) - if self.proxy_dict['type'] == 'socks5': - proxyclass = proxy_connectors.SOCKS5Connector - elif self.proxy_dict['type'] == 'http' : - proxyclass = proxy_connectors.HTTPCONNECTConnector - proxyclass.get_instance( - send_method=self.send, - onreceive=self.onreceive, - old_on_receive=self.on_receive, - on_success=self._on_connect, - on_failure=self._on_connect_failure, - xmpp_server=self.proxy_dict['xmpp_server'], - proxy_creds=self.proxy_dict['credentials']) - - def _on_connect(self): - """ - Preceed invoking of on_connect callback. TCP connection is already - estabilished by this time - """ - if self.estabilish_tls: - self.tls_init( - on_succ = lambda: NonBlockingTransport._on_connect(self), - on_fail = lambda: self._on_connect_failure( - 'error while estabilishing TLS')) - else: - NonBlockingTransport._on_connect(self) - - def tls_init(self, on_succ, on_fail): - """ - Estabilishes TLS/SSL using this TCP connection by plugging a - NonBlockingTLS module - """ - cacerts, mycerts = self.certs - result = tls_nb.NonBlockingTLS.get_instance(cacerts, mycerts).PlugIn(self) - if result: - on_succ() - else: - on_fail() - - def pollin(self): - """ - Called by idlequeu when receive on plugged socket is possible - """ - log.info('pollin called, state == %s' % self.get_state()) - self._do_receive() - - def pollout(self): - """ - Called by idlequeu when send to plugged socket is possible - """ - log.info('pollout called, state == %s' % self.get_state()) - - if self.get_state() == CONNECTING: - log.info('%s socket wrapper connected' % id(self)) - self.idlequeue.remove_timeout(self.fd) - self._plug_idle(writable=False, readable=False) - self.peerhost = self._sock.getsockname() - if self.proxy_dict: - self._connect_to_proxy() - else: - self._on_connect() - else: - self._do_send() - - def pollend(self): - """ - Called by idlequeue on TCP connection errors - """ - log.info('pollend called, state == %s' % self.get_state()) - - if self.get_state() == CONNECTING: - self._on_connect_failure('Error during connect to %s:%s' % - (self.server, self.port)) - else: - self.disconnect() - - def disconnect(self, do_callback=True): - if self.get_state() == DISCONNECTED: - return - self.set_state(DISCONNECTED) - self.idlequeue.unplug_idle(self.fd) - if 'NonBlockingTLS' in self.__dict__: - self.NonBlockingTLS.PlugOut() - try: - self._sock.shutdown(socket.SHUT_RDWR) - self._sock.close() - except socket.error, (errnum, errstr): - log.info('Error while disconnecting socket: %s' % errstr) - self.fd = -1 - NonBlockingTransport.disconnect(self, do_callback) - - def read_timeout(self): - log.info('read_timeout called, state == %s' % self.get_state()) - if self.get_state() == CONNECTING: - # if read_timeout is called during connecting, connect() didn't end yet - # thus we have to call the tcp failure callback - self._on_connect_failure('Error during connect to %s:%s' % - (self.server, self.port)) - else: - NonBlockingTransport.read_timeout(self) - - def set_timeout(self, timeout): - if self.get_state() != DISCONNECTED and self.fd != -1: - NonBlockingTransport.set_timeout(self, timeout) - else: - log.warn('set_timeout: TIMEOUT NOT SET: state is %s, fd is %s' % - (self.get_state(), self.fd)) - - def remove_timeout(self): - if self.fd: - NonBlockingTransport.remove_timeout(self) - else: - log.warn('remove_timeout: no self.fd state is %s' % self.get_state()) - - def send(self, raw_data, now=False): - """ - Append raw_data to the queue of messages to be send. If supplied data is - unicode string, encode it to utf-8. - """ - NonBlockingTransport.send(self, raw_data, now) - - r = self.encode_stanza(raw_data) - - if now: - self.sendqueue.insert(0, r) - self._do_send() - else: - self.sendqueue.append(r) - - self._plug_idle(writable=True, readable=True) - - def encode_stanza(self, stanza): - """ - Encode str or unicode to utf-8 - """ - if isinstance(stanza, unicode): - stanza = stanza.encode('utf-8') - elif not isinstance(stanza, str): - stanza = ustr(stanza).encode('utf-8') - return stanza - - def _plug_idle(self, writable, readable): - """ - Plug file descriptor of socket to Idlequeue - - Plugged socket will be watched for "send possible" or/and "recv possible" - events. pollin() callback is invoked on "recv possible", pollout() on - "send_possible". - - Plugged socket will always be watched for "error" event - in that case, - pollend() is called. - """ - log.info('Plugging fd %d, W:%s, R:%s' % (self.fd, writable, readable)) - self.idlequeue.plug_idle(self, writable, readable) - - def _do_send(self): - """ - Called when send() to connected socket is possible. First message from - sendqueue will be sent - """ - if not self.sendbuff: - if not self.sendqueue: - log.warn('calling send on empty buffer and queue') - self._plug_idle(writable=False, readable=True) - return None - self.sendbuff = self.sendqueue.pop(0) - try: - send_count = self._send(self.sendbuff) - if send_count: - sent_data = self.sendbuff[:send_count] - self.sendbuff = self.sendbuff[send_count:] - self._plug_idle( - writable=((self.sendqueue!=[]) or (self.sendbuff!='')), - readable=True) - self.raise_event(DATA_SENT, sent_data) - - except Exception: - log.error('_do_send:', exc_info=True) - traceback.print_exc() - self.disconnect() - - def _do_receive(self): - """ - Reads all pending incoming data. Will call owner's disconnected() method - if appropriate - """ - received = None - errnum = 0 - errstr = 'No Error Set' - - try: - # get as many bites, as possible, but not more than RECV_BUFSIZE - received = self._recv(RECV_BUFSIZE) - except socket.error, (errnum, errstr): - log.info("_do_receive: got %s:" % received, exc_info=True) - except tls_nb.SSLWrapper.Error, e: - log.info("_do_receive, caught SSL error, got %s:" % received, - exc_info=True) - errnum, errstr = e.errno, e.strerror - - if received == '': - errstr = 'zero bytes on recv' - - if (self.ssl_lib is None and received == '') or \ - (self.ssl_lib == tls_nb.PYSTDLIB and errnum == 8 ) or \ - (self.ssl_lib == tls_nb.PYOPENSSL and errnum == -1 ): - # 8 in stdlib: errstr == EOF occured in violation of protocol - # -1 in pyopenssl: errstr == Unexpected EOF - log.info("Disconnected by remote server: #%s, %s" % (errnum, errstr)) - self.on_remote_disconnect() - return - - if errnum: - log.info("Connection to %s:%s lost: %s %s" % (self.server, self.port, - errnum, errstr), exc_info=True) - self.disconnect() - return - - # this branch is for case of non-fatal SSL errors - None is returned from - # recv() but no errnum is set - if received is None: - return - - # we have received some bytes, stop the timeout! - self.remove_timeout() - self.renew_send_timeout() - self.renew_send_timeout2() - # pass received data to owner - if self.on_receive: - self.raise_event(DATA_RECEIVED, received) - self._on_receive(received) - else: - # This should never happen, so we need the debug. - # (If there is no handler on receive specified, data is passed to - # Dispatcher.ProcessNonBlocking) - log.error('SOCKET %s Unhandled data received: %s' % (id(self), - received)) - self.disconnect() - - def _on_receive(self, data): - """ - Preceeds on_receive callback. It peels off and checks HTTP headers in - HTTP classes, in here it just calls the callback - """ - self.on_receive(data) - - -class NonBlockingHTTP(NonBlockingTCP): - """ - Socket wrapper that creates HTTP message out of sent data and peels-off HTTP - headers from incoming messages - """ - - def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls, - certs, on_http_request_possible, on_persistent_fallback, http_dict, - proxy_dict=None): - """ - :param on_http_request_possible: method to call when HTTP request to - socket owned by transport is possible. - :param on_persistent_fallback: callback called when server ends TCP - connection. It doesn't have to be fatal for HTTP session. - :param http_dict: dictionary with data for HTTP request and headers - """ - NonBlockingTCP.__init__(self, raise_event, on_disconnect, idlequeue, - estabilish_tls, certs, proxy_dict) - - self.http_protocol, self.http_host, self.http_port, self.http_path = \ - urisplit(http_dict['http_uri']) - self.http_protocol = self.http_protocol or 'http' - self.http_path = self.http_path or '/' - self.http_version = http_dict['http_version'] - self.http_persistent = http_dict['http_persistent'] - self.add_proxy_headers = http_dict['add_proxy_headers'] - - if 'proxy_user' in http_dict and 'proxy_pass' in http_dict: - self.proxy_user, self.proxy_pass = http_dict['proxy_user'], http_dict[ - 'proxy_pass'] - else: - self.proxy_user, self.proxy_pass = None, None - - # buffer for partial responses - self.recvbuff = '' - self.expected_length = 0 - self.pending_requests = 0 - self.on_http_request_possible = on_http_request_possible - self.last_recv_time = 0 - self.close_current_connection = False - self.on_remote_disconnect = lambda: on_persistent_fallback(self) - - def http_send(self, raw_data, now=False): - self.send(self.build_http_message(raw_data), now) - - def _on_receive(self, data): - """ - Preceeds passing received data to owner class. Gets rid of HTTP headers - and checks them. - """ - if self.get_state() == PROXY_CONNECTING: - NonBlockingTCP._on_receive(self, data) - return - - # append currently received data to HTTP msg in buffer - self.recvbuff = '%s%s' % (self.recvbuff or '', data) - statusline, headers, httpbody, buffer_rest = self.parse_http_message( - self.recvbuff) - - if not (statusline and headers and httpbody): - log.debug('Received incomplete HTTP response') - return - - if statusline[1] != '200': - log.error('HTTP Error: %s %s' % (statusline[1], statusline[2])) - self.disconnect() - return - self.expected_length = int(headers['Content-Length']) - if 'Connection' in headers and headers['Connection'].strip()=='close': - self.close_current_connection = True - - if self.expected_length > len(httpbody): - # If we haven't received the whole HTTP mess yet, let's end the thread. - # It will be finnished from one of following recvs on plugged socket. - log.info('not enough bytes in HTTP response - %d expected, got %d' % - (self.expected_length, len(httpbody))) - else: - # First part of buffer has been extraced and is going to be handled, - # remove it from buffer - self.recvbuff = buffer_rest - - # everything was received - self.expected_length = 0 - - if not self.http_persistent or self.close_current_connection: - # not-persistent connections disconnect after response - self.disconnect(do_callback=False) - self.close_current_connection = False - self.last_recv_time = time.time() - self.on_receive(data=httpbody, socket=self) - self.on_http_request_possible() - - def build_http_message(self, httpbody, method='POST'): - """ - Builds http message with given body. Values for headers and status line - fields are taken from class variables - """ - absolute_uri = '%s://%s:%s%s' % (self.http_protocol, self.http_host, - self.http_port, self.http_path) - headers = ['%s %s %s' % (method, absolute_uri, self.http_version), - 'Host: %s:%s' % (self.http_host, self.http_port), - 'User-Agent: Gajim', - 'Content-Type: text/xml; charset=utf-8', - 'Content-Length: %s' % len(str(httpbody))] - if self.add_proxy_headers: - headers.append('Proxy-Connection: keep-alive') - headers.append('Pragma: no-cache') - if self.proxy_user and self.proxy_pass: - credentials = '%s:%s' % (self.proxy_user, self.proxy_pass) - credentials = base64.encodestring(credentials).strip() - headers.append('Proxy-Authorization: Basic %s' % credentials) - else: - headers.append('Connection: Keep-Alive') - headers.append('\r\n') - headers = '\r\n'.join(headers) - return('%s%s' % (headers, httpbody)) - - def parse_http_message(self, message): - """ - Split http message into a tuple: - (statusline - list of e.g. ['HTTP/1.1', '200', 'OK'], - headers - dictionary of headers e.g. {'Content-Length': '604', - 'Content-Type': 'text/xml; charset=utf-8'}, - httpbody - string with http body) - http_rest - what is left in the message after a full HTTP header + body - """ - splitted = message.split('\r\n\r\n') - if len(splitted) < 2: - # no complete http message. Keep filling the buffer until we find one - buffer_rest = message - return ('', '', '', buffer_rest) - else: - (header, httpbody) = splitted[:2] - header = header.replace('\r', '') - header = header.lstrip('\n') - header = header.split('\n') - statusline = header[0].split(' ', 2) - header = header[1:] - headers = {} - for dummy in header: - row = dummy.split(' ', 1) - headers[row[0][:-1]] = row[1] - body_size = headers['Content-Length'] - rest_splitted = splitted[2:] - while (len(httpbody) < body_size) and rest_splitted: - # Complete httpbody until it has the announced size - httpbody = '\n\n'.join([httpbody, rest_splitted.pop(0)]) - buffer_rest = "\n\n".join(rest_splitted) - return (statusline, headers, httpbody, buffer_rest) - - -class NonBlockingHTTPBOSH(NonBlockingHTTP): - """ - Class for BOSH HTTP connections. Slightly redefines HTTP transport by - calling bosh bodytag generating callback before putting data on wire - """ - - def set_stanza_build_cb(self, build_cb): - self.build_cb = build_cb - - def _do_send(self): - if self.state == PROXY_CONNECTING: - NonBlockingTCP._do_send(self) - return - if not self.sendbuff: - stanza = self.build_cb(socket=self) - stanza = self.encode_stanza(stanza) - stanza = self.build_http_message(httpbody=stanza) - self.sendbuff = stanza - NonBlockingTCP._do_send(self) |