diff options
author | Florent Le Coz <louiz@louiz.org> | 2014-07-17 14:19:04 +0200 |
---|---|---|
committer | Florent Le Coz <louiz@louiz.org> | 2014-07-17 14:19:04 +0200 |
commit | 5ab77c745270d7d5c016c1dc7ef2a82533a4b16e (patch) | |
tree | 259377cc666f8b9c7954fc4e7b8f7a912bcfe101 /sleekxmpp/util | |
parent | e5582694c07236e6830c20361840360a1dde37f3 (diff) | |
download | slixmpp-5ab77c745270d7d5c016c1dc7ef2a82533a4b16e.tar.gz slixmpp-5ab77c745270d7d5c016c1dc7ef2a82533a4b16e.tar.bz2 slixmpp-5ab77c745270d7d5c016c1dc7ef2a82533a4b16e.tar.xz slixmpp-5ab77c745270d7d5c016c1dc7ef2a82533a4b16e.zip |
Rename to slixmpp
Diffstat (limited to 'sleekxmpp/util')
-rw-r--r-- | sleekxmpp/util/__init__.py | 43 | ||||
-rw-r--r-- | sleekxmpp/util/misc_ops.py | 165 | ||||
-rw-r--r-- | sleekxmpp/util/sasl/__init__.py | 17 | ||||
-rw-r--r-- | sleekxmpp/util/sasl/client.py | 174 | ||||
-rw-r--r-- | sleekxmpp/util/sasl/mechanisms.py | 551 | ||||
-rw-r--r-- | sleekxmpp/util/stringprep_profiles.py | 151 |
6 files changed, 0 insertions, 1101 deletions
diff --git a/sleekxmpp/util/__init__.py b/sleekxmpp/util/__init__.py deleted file mode 100644 index 05286d33..00000000 --- a/sleekxmpp/util/__init__.py +++ /dev/null @@ -1,43 +0,0 @@ -# -*- coding: utf-8 -*- -""" - sleekxmpp.util - ~~~~~~~~~~~~~~ - - Part of SleekXMPP: The Sleek XMPP Library - - :copyright: (c) 2012 Nathanael C. Fritz, Lance J.T. Stout - :license: MIT, see LICENSE for more details -""" - - -from sleekxmpp.util.misc_ops import bytes, unicode, hashes, hash, \ - num_to_bytes, bytes_to_num, quote, \ - XOR, safedict - - -# ===================================================================== -# Standardize import of Queue class: - -import sys - -def _gevent_threads_enabled(): - if not 'gevent' in sys.modules: - return False - try: - from gevent import thread as green_thread - thread = __import__('thread') - return thread.LockType is green_thread.LockType - except ImportError: - return False - -if _gevent_threads_enabled(): - import gevent.queue as queue - Queue = queue.JoinableQueue -else: - try: - import queue - except ImportError: - import Queue as queue - Queue = queue.Queue - -QueueEmpty = queue.Empty diff --git a/sleekxmpp/util/misc_ops.py b/sleekxmpp/util/misc_ops.py deleted file mode 100644 index 18c919a8..00000000 --- a/sleekxmpp/util/misc_ops.py +++ /dev/null @@ -1,165 +0,0 @@ -import sys -import hashlib - - -def unicode(text): - if sys.version_info < (3, 0): - if isinstance(text, str): - text = text.decode('utf-8') - import __builtin__ - return __builtin__.unicode(text) - elif not isinstance(text, str): - return text.decode('utf-8') - else: - return text - - -def bytes(text): - """ - Convert Unicode text to UTF-8 encoded bytes. - - Since Python 2.6+ and Python 3+ have similar but incompatible - signatures, this function unifies the two to keep code sane. - - :param text: Unicode text to convert to bytes - :rtype: bytes (Python3), str (Python2.6+) - """ - if text is None: - return b'' - - if sys.version_info < (3, 0): - import __builtin__ - return __builtin__.bytes(text) - else: - import builtins - if isinstance(text, builtins.bytes): - # We already have bytes, so do nothing - return text - if isinstance(text, list): - # Convert a list of integers to bytes - return builtins.bytes(text) - else: - # Convert UTF-8 text to bytes - return builtins.bytes(text, encoding='utf-8') - - -def quote(text): - """ - Enclose in quotes and escape internal slashes and double quotes. - - :param text: A Unicode or byte string. - """ - text = bytes(text) - return b'"' + text.replace(b'\\', b'\\\\').replace(b'"', b'\\"') + b'"' - - -def num_to_bytes(num): - """ - Convert an integer into a four byte sequence. - - :param integer num: An integer to convert to its byte representation. - """ - bval = b'' - bval += bytes(chr(0xFF & (num >> 24))) - bval += bytes(chr(0xFF & (num >> 16))) - bval += bytes(chr(0xFF & (num >> 8))) - bval += bytes(chr(0xFF & (num >> 0))) - return bval - - -def bytes_to_num(bval): - """ - Convert a four byte sequence to an integer. - - :param bytes bval: A four byte sequence to turn into an integer. - """ - num = 0 - num += ord(bval[0] << 24) - num += ord(bval[1] << 16) - num += ord(bval[2] << 8) - num += ord(bval[3]) - return num - - -def XOR(x, y): - """ - Return the results of an XOR operation on two equal length byte strings. - - :param bytes x: A byte string - :param bytes y: A byte string - :rtype: bytes - """ - result = b'' - for a, b in zip(x, y): - if sys.version_info < (3, 0): - result += chr((ord(a) ^ ord(b))) - else: - result += bytes([a ^ b]) - return result - - -def hash(name): - """ - Return a hash function implementing the given algorithm. - - :param name: The name of the hashing algorithm to use. - :type name: string - - :rtype: function - """ - name = name.lower() - if name.startswith('sha-'): - name = 'sha' + name[4:] - if name in dir(hashlib): - return getattr(hashlib, name) - return None - - -def hashes(): - """ - Return a list of available hashing algorithms. - - :rtype: list of strings - """ - t = [] - if 'md5' in dir(hashlib): - t = ['MD5'] - if 'md2' in dir(hashlib): - t += ['MD2'] - hashes = ['SHA-' + h[3:] for h in dir(hashlib) if h.startswith('sha')] - return t + hashes - - -def setdefaultencoding(encoding): - """ - Set the current default string encoding used by the Unicode implementation. - - Actually calls sys.setdefaultencoding under the hood - see the docs for that - for more details. This method exists only as a way to call find/call it - even after it has been 'deleted' when the site module is executed. - - :param string encoding: An encoding name, compatible with sys.setdefaultencoding - """ - func = getattr(sys, 'setdefaultencoding', None) - if func is None: - import gc - import types - for obj in gc.get_objects(): - if (isinstance(obj, types.BuiltinFunctionType) - and obj.__name__ == 'setdefaultencoding'): - func = obj - break - if func is None: - raise RuntimeError("Could not find setdefaultencoding") - sys.setdefaultencoding = func - return func(encoding) - - -def safedict(data): - if sys.version_info < (2, 7): - safe = {} - for key in data: - safe[key.encode('utf8')] = data[key] - return safe - else: - return data diff --git a/sleekxmpp/util/sasl/__init__.py b/sleekxmpp/util/sasl/__init__.py deleted file mode 100644 index 2d344e9b..00000000 --- a/sleekxmpp/util/sasl/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# -*- coding: utf-8 -*- -""" - sleekxmpp.util.sasl - ~~~~~~~~~~~~~~~~~~~ - - This module was originally based on Dave Cridland's Suelta library. - - Part of SleekXMPP: The Sleek XMPP Library - - :copryight: (c) 2004-2013 David Alan Cridland - :copyright: (c) 2013 Nathanael C. Fritz, Lance J.T. Stout - - :license: MIT, see LICENSE for more details -""" - -from sleekxmpp.util.sasl.client import * -from sleekxmpp.util.sasl.mechanisms import * diff --git a/sleekxmpp/util/sasl/client.py b/sleekxmpp/util/sasl/client.py deleted file mode 100644 index fd685547..00000000 --- a/sleekxmpp/util/sasl/client.py +++ /dev/null @@ -1,174 +0,0 @@ -# -*- coding: utf-8 -*- -""" - sleekxmpp.util.sasl.client - ~~~~~~~~~~~~~~~~~~~~~~~~~~ - - This module was originally based on Dave Cridland's Suelta library. - - Part of SleekXMPP: The Sleek XMPP Library - - :copryight: (c) 2004-2013 David Alan Cridland - :copyright: (c) 2013 Nathanael C. Fritz, Lance J.T. Stout - - :license: MIT, see LICENSE for more details -""" - -import logging -import stringprep - -from sleekxmpp.util import hashes, bytes, stringprep_profiles - - -log = logging.getLogger(__name__) - - -#: Global registry mapping mechanism names to implementation classes. -MECHANISMS = {} - - -#: Global registry mapping mechanism names to security scores. -MECH_SEC_SCORES = {} - - -#: The SASLprep profile of stringprep used to validate simple username -#: and password credentials. -saslprep = stringprep_profiles.create( - nfkc=True, - bidi=True, - mappings=[ - stringprep_profiles.b1_mapping, - stringprep_profiles.c12_mapping], - prohibited=[ - stringprep.in_table_c12, - stringprep.in_table_c21, - stringprep.in_table_c22, - stringprep.in_table_c3, - stringprep.in_table_c4, - stringprep.in_table_c5, - stringprep.in_table_c6, - stringprep.in_table_c7, - stringprep.in_table_c8, - stringprep.in_table_c9], - unassigned=[stringprep.in_table_a1]) - - -def sasl_mech(score): - sec_score = score - def register(mech): - n = 0 - mech.score = sec_score - if mech.use_hashes: - for hashing_alg in hashes(): - n += 1 - score = mech.score + n - name = '%s-%s' % (mech.name, hashing_alg) - MECHANISMS[name] = mech - MECH_SEC_SCORES[name] = score - - if mech.channel_binding: - name += '-PLUS' - score += 10 - MECHANISMS[name] = mech - MECH_SEC_SCORES[name] = score - else: - MECHANISMS[mech.name] = mech - MECH_SEC_SCORES[mech.name] = mech.score - if mech.channel_binding: - MECHANISMS[mech.name + '-PLUS'] = mech - MECH_SEC_SCORES[name] = mech.score + 10 - return mech - return register - - -class SASLNoAppropriateMechanism(Exception): - def __init__(self, value=''): - self.message = value - - -class SASLCancelled(Exception): - def __init__(self, value=''): - self.message = value - - -class SASLFailed(Exception): - def __init__(self, value=''): - self.message = value - - -class SASLMutualAuthFailed(SASLFailed): - def __init__(self, value=''): - self.message = value - - -class Mech(object): - - name = 'GENERIC' - score = -1 - use_hashes = False - channel_binding = False - required_credentials = set() - optional_credentials = set() - security = set() - - def __init__(self, name, credentials, security_settings): - self.credentials = credentials - self.security_settings = security_settings - self.values = {} - self.base_name = self.name - self.name = name - self.setup(name) - - def setup(self, name): - pass - - def process(self, challenge=b''): - return b'' - - -def choose(mech_list, credentials, security_settings, limit=None, min_mech=None): - available_mechs = set(MECHANISMS.keys()) - if limit is None: - limit = set(mech_list) - if not isinstance(limit, set): - limit = set(limit) - if not isinstance(mech_list, set): - mech_list = set(mech_list) - - mech_list = mech_list.intersection(limit) - available_mechs = available_mechs.intersection(mech_list) - - best_score = MECH_SEC_SCORES.get(min_mech, -1) - best_mech = None - for name in available_mechs: - if name in MECH_SEC_SCORES: - if MECH_SEC_SCORES[name] > best_score: - best_score = MECH_SEC_SCORES[name] - best_mech = name - if best_mech is None: - raise SASLNoAppropriateMechanism() - - mech_class = MECHANISMS[best_mech] - - try: - creds = credentials(mech_class.required_credentials, - mech_class.optional_credentials) - for req in mech_class.required_credentials: - if req not in creds: - raise SASLCancelled('Missing credential: %s' % req) - for opt in mech_class.optional_credentials: - if opt not in creds: - creds[opt] = b'' - for cred in creds: - if cred in ('username', 'password', 'authzid'): - creds[cred] = bytes(saslprep(creds[cred])) - else: - creds[cred] = bytes(creds[cred]) - security_opts = security_settings(mech_class.security) - - return mech_class(best_mech, creds, security_opts) - except SASLCancelled as e: - log.info('SASL: %s: %s', best_mech, e.message) - mech_list.remove(best_mech) - return choose(mech_list, credentials, security_settings, - limit=limit, - min_mech=min_mech) diff --git a/sleekxmpp/util/sasl/mechanisms.py b/sleekxmpp/util/sasl/mechanisms.py deleted file mode 100644 index d341ed3e..00000000 --- a/sleekxmpp/util/sasl/mechanisms.py +++ /dev/null @@ -1,551 +0,0 @@ -# -*- coding: utf-8 -*- -""" - sleekxmpp.util.sasl.mechanisms - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - A collection of supported SASL mechanisms. - - This module was originally based on Dave Cridland's Suelta library. - - Part of SleekXMPP: The Sleek XMPP Library - - :copryight: (c) 2004-2013 David Alan Cridland - :copyright: (c) 2013 Nathanael C. Fritz, Lance J.T. Stout - - :license: MIT, see LICENSE for more details -""" - -import sys -import hmac -import random - -from base64 import b64encode, b64decode - -from sleekxmpp.util import bytes, hash, XOR, quote, num_to_bytes -from sleekxmpp.util.sasl.client import sasl_mech, Mech, \ - SASLCancelled, SASLFailed, \ - SASLMutualAuthFailed - - -@sasl_mech(0) -class ANONYMOUS(Mech): - - name = 'ANONYMOUS' - - def process(self, challenge=b''): - return b'Anonymous, Suelta' - - -@sasl_mech(1) -class LOGIN(Mech): - - name = 'LOGIN' - required_credentials = set(['username', 'password']) - - def setup(self, name): - self.step = 0 - - def process(self, challenge=b''): - if not challenge: - return b'' - - if self.step == 0: - self.step = 1 - return self.credentials['username'] - else: - return self.credentials['password'] - - -@sasl_mech(2) -class PLAIN(Mech): - - name = 'PLAIN' - required_credentials = set(['username', 'password']) - optional_credentials = set(['authzid']) - security = set(['encrypted', 'encrypted_plain', 'unencrypted_plain']) - - def setup(self, name): - if not self.security_settings['encrypted']: - if not self.security_settings['unencrypted_plain']: - raise SASLCancelled('PLAIN without encryption') - else: - if not self.security_settings['encrypted_plain']: - raise SASLCancelled('PLAIN with encryption') - - def process(self, challenge=b''): - authzid = self.credentials['authzid'] - authcid = self.credentials['username'] - password = self.credentials['password'] - return authzid + b'\x00' + authcid + b'\x00' + password - - -@sasl_mech(100) -class EXTERNAL(Mech): - - name = 'EXTERNAL' - optional_credentials = set(['authzid']) - - def process(self, challenge=b''): - return self.credentials['authzid'] - - -@sasl_mech(31) -class X_FACEBOOK_PLATFORM(Mech): - - name = 'X-FACEBOOK-PLATFORM' - required_credentials = set(['api_key', 'access_token']) - - def process(self, challenge=b''): - if challenge: - values = {} - for kv in challenge.split(b'&'): - key, value = kv.split(b'=') - values[key] = value - - resp_data = { - b'method': values[b'method'], - b'v': b'1.0', - b'call_id': b'1.0', - b'nonce': values[b'nonce'], - b'access_token': self.credentials['access_token'], - b'api_key': self.credentials['api_key'] - } - - resp = '&'.join(['%s=%s' % (k.decode("utf-8"), v.decode("utf-8")) for k, v in resp_data.items()]) - return bytes(resp) - return b'' - - -@sasl_mech(10) -class X_MESSENGER_OAUTH2(Mech): - - name = 'X-MESSENGER-OAUTH2' - required_credentials = set(['access_token']) - - def process(self, challenge=b''): - return self.credentials['access_token'] - - -@sasl_mech(10) -class X_OAUTH2(Mech): - - name = 'X-OAUTH2' - required_credentials = set(['username', 'access_token']) - - def process(self, challenge=b''): - return b'\x00' + self.credentials['username'] + \ - b'\x00' + self.credentials['access_token'] - - -@sasl_mech(3) -class X_GOOGLE_TOKEN(Mech): - - name = 'X-GOOGLE-TOKEN' - required_credentials = set(['email', 'access_token']) - - def process(self, challenge=b''): - email = self.credentials['email'] - token = self.credentials['access_token'] - return b'\x00' + email + b'\x00' + token - - -@sasl_mech(20) -class CRAM(Mech): - - name = 'CRAM' - use_hashes = True - required_credentials = set(['username', 'password']) - security = set(['encrypted', 'unencrypted_cram']) - - def setup(self, name): - self.hash_name = name[5:] - self.hash = hash(self.hash_name) - if self.hash is None: - raise SASLCancelled('Unknown hash: %s' % self.hash_name) - if not self.security_settings['encrypted']: - if not self.security_settings['unencrypted_cram']: - raise SASLCancelled('Unecrypted CRAM-%s' % self.hash_name) - - def process(self, challenge=b''): - if not challenge: - return None - - username = self.credentials['username'] - password = self.credentials['password'] - - mac = hmac.HMAC(key=password, digestmod=self.hash) - mac.update(challenge) - - return username + b' ' + bytes(mac.hexdigest()) - - -@sasl_mech(60) -class SCRAM(Mech): - - name = 'SCRAM' - use_hashes = True - channel_binding = True - required_credentials = set(['username', 'password']) - optional_credentials = set(['authzid', 'channel_binding']) - security = set(['encrypted', 'unencrypted_scram']) - - def setup(self, name): - self.use_channel_binding = False - if name[-5:] == '-PLUS': - name = name[:-5] - self.use_channel_binding = True - - self.hash_name = name[6:] - self.hash = hash(self.hash_name) - - if self.hash is None: - raise SASLCancelled('Unknown hash: %s' % self.hash_name) - if not self.security_settings['encrypted']: - if not self.security_settings['unencrypted_scram']: - raise SASLCancelled('Unencrypted SCRAM') - - self.step = 0 - self._mutual_auth = False - - def HMAC(self, key, msg): - return hmac.HMAC(key=key, msg=msg, digestmod=self.hash).digest() - - def Hi(self, text, salt, iterations): - text = bytes(text) - ui1 = self.HMAC(text, salt + b'\0\0\0\01') - ui = ui1 - for i in range(iterations - 1): - ui1 = self.HMAC(text, ui1) - ui = XOR(ui, ui1) - return ui - - def H(self, text): - return self.hash(text).digest() - - def saslname(self, value): - escaped = b'' - for char in bytes(value): - if char == b',': - escaped += b'=2C' - elif char == b'=': - escaped += b'=3D' - else: - if isinstance(char, int): - char = chr(char) - escaped += bytes(char) - return escaped - - def parse(self, challenge): - items = {} - for key, value in [item.split(b'=', 1) for item in challenge.split(b',')]: - items[key] = value - return items - - def process(self, challenge=b''): - steps = [self.process_1, self.process_2, self.process_3] - return steps[self.step](challenge) - - def process_1(self, challenge): - self.step = 1 - data = {} - - self.cnonce = bytes(('%s' % random.random())[2:]) - - gs2_cbind_flag = b'n' - if self.credentials['channel_binding']: - if self.use_channel_binding: - gs2_cbind_flag = b'p=tls-unique' - else: - gs2_cbind_flag = b'y' - - authzid = b'' - if self.credentials['authzid']: - authzid = b'a=' + self.saslname(self.credentials['authzid']) - - self.gs2_header = gs2_cbind_flag + b',' + authzid + b',' - - nonce = b'r=' + self.cnonce - username = b'n=' + self.saslname(self.credentials['username']) - - self.client_first_message_bare = username + b',' + nonce - self.client_first_message = self.gs2_header + \ - self.client_first_message_bare - - return self.client_first_message - - def process_2(self, challenge): - self.step = 2 - - data = self.parse(challenge) - if b'm' in data: - raise SASLCancelled('Received reserved attribute.') - - salt = b64decode(data[b's']) - iteration_count = int(data[b'i']) - nonce = data[b'r'] - - if nonce[:len(self.cnonce)] != self.cnonce: - raise SASLCancelled('Invalid nonce') - - cbind_data = b'' - if self.use_channel_binding: - cbind_data = self.credentials['channel_binding'] - cbind_input = self.gs2_header + cbind_data - channel_binding = b'c=' + b64encode(cbind_input).replace(b'\n', b'') - - client_final_message_without_proof = channel_binding + b',' + \ - b'r=' + nonce - - salted_password = self.Hi(self.credentials['password'], - salt, - iteration_count) - client_key = self.HMAC(salted_password, b'Client Key') - stored_key = self.H(client_key) - auth_message = self.client_first_message_bare + b',' + \ - challenge + b',' + \ - client_final_message_without_proof - client_signature = self.HMAC(stored_key, auth_message) - client_proof = XOR(client_key, client_signature) - server_key = self.HMAC(salted_password, b'Server Key') - - self.server_signature = self.HMAC(server_key, auth_message) - - client_final_message = client_final_message_without_proof + \ - b',p=' + b64encode(client_proof) - - return client_final_message - - def process_3(self, challenge): - data = self.parse(challenge) - verifier = data.get(b'v', None) - error = data.get(b'e', 'Unknown error') - - if not verifier: - raise SASLFailed(error) - - if b64decode(verifier) != self.server_signature: - raise SASLMutualAuthFailed() - - self._mutual_auth = True - - return b'' - - -@sasl_mech(30) -class DIGEST(Mech): - - name = 'DIGEST' - use_hashes = True - required_credentials = set(['username', 'password', 'realm', 'service', 'host']) - optional_credentials = set(['authzid', 'service-name']) - security = set(['encrypted', 'unencrypted_digest']) - - def setup(self, name): - self.hash_name = name[7:] - self.hash = hash(self.hash_name) - if self.hash is None: - raise SASLCancelled('Unknown hash: %s' % self.hash_name) - if not self.security_settings['encrypted']: - if not self.security_settings['unencrypted_digest']: - raise SASLCancelled('Unencrypted DIGEST') - - self.qops = [b'auth'] - self.qop = b'auth' - self.maxbuf = b'65536' - self.nonce = b'' - self.cnonce = b'' - self.nonce_count = 1 - - def parse(self, challenge=b''): - data = {} - var_name = b'' - var_value = b'' - - # States: var, new_var, end, quote, escaped_quote - state = 'var' - - - for char in challenge: - if sys.version_info >= (3, 0): - char = bytes([char]) - - if state == 'var': - if char.isspace(): - continue - if char == b'=': - state = 'value' - else: - var_name += char - elif state == 'value': - if char == b'"': - state = 'quote' - elif char == b',': - if var_name: - data[var_name.decode('utf-8')] = var_value - var_name = b'' - var_value = b'' - state = 'var' - else: - var_value += char - elif state == 'escaped': - var_value += char - elif state == 'quote': - if char == b'\\': - state = 'escaped' - elif char == b'"': - state = 'end' - else: - var_value += char - else: - if char == b',': - if var_name: - data[var_name.decode('utf-8')] = var_value - var_name = b'' - var_value = b'' - state = 'var' - else: - var_value += char - - if var_name: - data[var_name.decode('utf-8')] = var_value - var_name = b'' - var_value = b'' - state = 'var' - return data - - def MAC(self, key, seq, msg): - mac = hmac.HMAC(key=key, digestmod=self.hash) - seqnum = num_to_bytes(seq) - mac.update(seqnum) - mac.update(msg) - return mac.digest()[:10] + b'\x00\x01' + seqnum - - def A1(self): - username = self.credentials['username'] - password = self.credentials['password'] - authzid = self.credentials['authzid'] - realm = self.credentials['realm'] - - a1 = self.hash() - a1.update(username + b':' + realm + b':' + password) - a1 = a1.digest() - a1 += b':' + self.nonce + b':' + self.cnonce - if authzid: - a1 += b':' + authzid - - return bytes(a1) - - def A2(self, prefix=b''): - a2 = prefix + b':' + self.digest_uri() - if self.qop in (b'auth-int', b'auth-conf'): - a2 += b':00000000000000000000000000000000' - return bytes(a2) - - def response(self, prefix=b''): - nc = bytes('%08x' % self.nonce_count) - - a1 = bytes(self.hash(self.A1()).hexdigest().lower()) - a2 = bytes(self.hash(self.A2(prefix)).hexdigest().lower()) - s = self.nonce + b':' + nc + b':' + self.cnonce + \ - b':' + self.qop + b':' + a2 - - return bytes(self.hash(a1 + b':' + s).hexdigest().lower()) - - def digest_uri(self): - serv_type = self.credentials['service'] - serv_name = self.credentials['service-name'] - host = self.credentials['host'] - - uri = serv_type + b'/' + host - if serv_name and host != serv_name: - uri += b'/' + serv_name - return uri - - def respond(self): - data = { - 'username': quote(self.credentials['username']), - 'authzid': quote(self.credentials['authzid']), - 'realm': quote(self.credentials['realm']), - 'nonce': quote(self.nonce), - 'cnonce': quote(self.cnonce), - 'nc': bytes('%08x' % self.nonce_count), - 'qop': self.qop, - 'digest-uri': quote(self.digest_uri()), - 'response': self.response(b'AUTHENTICATE'), - 'maxbuf': self.maxbuf, - 'charset': 'utf-8' - } - resp = b'' - for key, value in data.items(): - if value and value != b'""': - resp += b',' + bytes(key) + b'=' + bytes(value) - return resp[1:] - - def process(self, challenge=b''): - if not challenge: - if self.cnonce and self.nonce and self.nonce_count and self.qop: - self.nonce_count += 1 - return self.respond() - return None - - data = self.parse(challenge) - if 'rspauth' in data: - if data['rspauth'] != self.response(): - raise SASLMutualAuthFailed() - else: - self.nonce_count = 1 - self.cnonce = bytes('%s' % random.random())[2:] - self.qops = data.get('qop', [b'auth']) - self.qop = b'auth' - if 'nonce' in data: - self.nonce = data['nonce'] - if 'realm' in data and not self.credentials['realm']: - self.credentials['realm'] = data['realm'] - - return self.respond() - - -try: - import kerberos -except ImportError: - pass -else: - @sasl_mech(75) - class GSSAPI(Mech): - - name = 'GSSAPI' - required_credentials = set(['username', 'service-name']) - optional_credentials = set(['authzid']) - - def setup(self, name): - authzid = self.credentials['authzid'] - if not authzid: - authzid = 'xmpp@%s' % self.credentials['service-name'] - - _, self.gss = kerberos.authGSSClientInit(authzid) - self.step = 0 - - def process(self, challenge=b''): - b64_challenge = b64encode(challenge) - try: - if self.step == 0: - result = kerberos.authGSSClientStep(self.gss, b64_challenge) - if result != kerberos.AUTH_GSS_CONTINUE: - self.step = 1 - elif not challenge: - kerberos.authGSSClientClean(self.gss) - return b'' - elif self.step == 1: - username = self.credentials['username'] - - kerberos.authGSSClientUnwrap(self.gss, b64_challenge) - resp = kerberos.authGSSClientResponse(self.gss) - kerberos.authGSSClientWrap(self.gss, resp, username) - - resp = kerberos.authGSSClientResponse(self.gss) - except kerberos.GSSError as e: - raise SASLCancelled('Kerberos error: %s' % e) - if not resp: - return b'' - else: - return b64decode(resp) diff --git a/sleekxmpp/util/stringprep_profiles.py b/sleekxmpp/util/stringprep_profiles.py deleted file mode 100644 index 84326bc3..00000000 --- a/sleekxmpp/util/stringprep_profiles.py +++ /dev/null @@ -1,151 +0,0 @@ -# -*- coding: utf-8 -*- -""" - sleekxmpp.util.stringprep_profiles - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - This module makes it easier to define profiles of stringprep, - such as nodeprep and resourceprep for JID validation, and - SASLprep for SASL. - - Part of SleekXMPP: The Sleek XMPP Library - - :copyright: (c) 2012 Nathanael C. Fritz, Lance J.T. Stout - :license: MIT, see LICENSE for more details -""" - - -from __future__ import unicode_literals - -import stringprep -from unicodedata import ucd_3_2_0 as unicodedata - -from sleekxmpp.util import unicode - - -class StringPrepError(UnicodeError): - pass - - -def b1_mapping(char): - """Map characters that are commonly mapped to nothing.""" - return '' if stringprep.in_table_b1(char) else None - - -def c12_mapping(char): - """Map non-ASCII whitespace to spaces.""" - return ' ' if stringprep.in_table_c12(char) else None - - -def map_input(data, tables=None): - """ - Each character in the input stream MUST be checked against - a mapping table. - """ - result = [] - for char in data: - replacement = None - - for mapping in tables: - replacement = mapping(char) - if replacement is not None: - break - - if replacement is None: - replacement = char - result.append(replacement) - return ''.join(result) - - -def normalize(data, nfkc=True): - """ - A profile can specify one of two options for Unicode normalization: - - no normalization - - Unicode normalization with form KC - """ - if nfkc: - data = unicodedata.normalize('NFKC', data) - return data - - -def prohibit_output(data, tables=None): - """ - Before the text can be emitted, it MUST be checked for prohibited - code points. - """ - for char in data: - for check in tables: - if check(char): - raise StringPrepError("Prohibited code point: %s" % char) - - -def check_bidi(data): - """ - 1) The characters in section 5.8 MUST be prohibited. - - 2) If a string contains any RandALCat character, the string MUST NOT - contain any LCat character. - - 3) If a string contains any RandALCat character, a RandALCat - character MUST be the first character of the string, and a - RandALCat character MUST be the last character of the string. - """ - if not data: - return data - - has_lcat = False - has_randal = False - - for c in data: - if stringprep.in_table_c8(c): - raise StringPrepError("BIDI violation: seciton 6 (1)") - if stringprep.in_table_d1(c): - has_randal = True - elif stringprep.in_table_d2(c): - has_lcat = True - - if has_randal and has_lcat: - raise StringPrepError("BIDI violation: section 6 (2)") - - first_randal = stringprep.in_table_d1(data[0]) - last_randal = stringprep.in_table_d1(data[-1]) - if has_randal and not (first_randal and last_randal): - raise StringPrepError("BIDI violation: section 6 (3)") - - -def create(nfkc=True, bidi=True, mappings=None, - prohibited=None, unassigned=None): - """Create a profile of stringprep. - - :param bool nfkc: - If `True`, perform NFKC Unicode normalization. Defaults to `True`. - :param bool bidi: - If `True`, perform bidirectional text checks. Defaults to `True`. - :param list mappings: - Optional list of functions for mapping characters to - suitable replacements. - :param list prohibited: - Optional list of functions which check for the presence of - prohibited characters. - :param list unassigned: - Optional list of functions for detecting the use of unassigned - code points. - - :raises: StringPrepError - :return: Unicode string of the resulting text passing the - profile's requirements. - """ - def profile(data, query=False): - try: - data = unicode(data) - except UnicodeError: - raise StringPrepError - - data = map_input(data, mappings) - data = normalize(data, nfkc) - prohibit_output(data, prohibited) - if bidi: - check_bidi(data) - if query and unassigned: - check_unassigned(data, unassigned) - return data - return profile |