client.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. # -*- coding: utf-8 -*-
  2. """
  3. sleekxmpp.util.sasl.client
  4. ~~~~~~~~~~~~~~~~~~~~~~~~~~
  5. This module was originally based on Dave Cridland's Suelta library.
  6. Part of SleekXMPP: The Sleek XMPP Library
  7. :copryight: (c) 2004-2013 David Alan Cridland
  8. :copyright: (c) 2013 Nathanael C. Fritz, Lance J.T. Stout
  9. :license: MIT, see LICENSE for more details
  10. """
  11. import logging
  12. import stringprep
  13. from sleekxmpp.util import hashes, bytes, stringprep_profiles
  14. log = logging.getLogger(__name__)
  15. #: Global registry mapping mechanism names to implementation classes.
  16. MECHANISMS = {}
  17. #: Global registry mapping mechanism names to security scores.
  18. MECH_SEC_SCORES = {}
  19. #: The SASLprep profile of stringprep used to validate simple username
  20. #: and password credentials.
  21. saslprep = stringprep_profiles.create(
  22. nfkc=True,
  23. bidi=True,
  24. mappings=[
  25. stringprep_profiles.b1_mapping,
  26. stringprep_profiles.c12_mapping],
  27. prohibited=[
  28. stringprep.in_table_c12,
  29. stringprep.in_table_c21,
  30. stringprep.in_table_c22,
  31. stringprep.in_table_c3,
  32. stringprep.in_table_c4,
  33. stringprep.in_table_c5,
  34. stringprep.in_table_c6,
  35. stringprep.in_table_c7,
  36. stringprep.in_table_c8,
  37. stringprep.in_table_c9],
  38. unassigned=[stringprep.in_table_a1])
  39. def sasl_mech(score):
  40. sec_score = score
  41. def register(mech):
  42. n = 0
  43. mech.score = sec_score
  44. if mech.use_hashes:
  45. for hashing_alg in hashes():
  46. n += 1
  47. score = mech.score + n
  48. name = '%s-%s' % (mech.name, hashing_alg)
  49. MECHANISMS[name] = mech
  50. MECH_SEC_SCORES[name] = score
  51. if mech.channel_binding:
  52. name += '-PLUS'
  53. score += 10
  54. MECHANISMS[name] = mech
  55. MECH_SEC_SCORES[name] = score
  56. else:
  57. MECHANISMS[mech.name] = mech
  58. MECH_SEC_SCORES[mech.name] = mech.score
  59. if mech.channel_binding:
  60. MECHANISMS[mech.name + '-PLUS'] = mech
  61. MECH_SEC_SCORES[name] = mech.score + 10
  62. return mech
  63. return register
  64. class SASLNoAppropriateMechanism(Exception):
  65. def __init__(self, value=''):
  66. self.message = value
  67. class SASLCancelled(Exception):
  68. def __init__(self, value=''):
  69. self.message = value
  70. class SASLFailed(Exception):
  71. def __init__(self, value=''):
  72. self.message = value
  73. class SASLMutualAuthFailed(SASLFailed):
  74. def __init__(self, value=''):
  75. self.message = value
  76. class Mech(object):
  77. name = 'GENERIC'
  78. score = -1
  79. use_hashes = False
  80. channel_binding = False
  81. required_credentials = set()
  82. optional_credentials = set()
  83. security = set()
  84. def __init__(self, name, credentials, security_settings):
  85. self.credentials = credentials
  86. self.security_settings = security_settings
  87. self.values = {}
  88. self.base_name = self.name
  89. self.name = name
  90. self.setup(name)
  91. def setup(self, name):
  92. pass
  93. def process(self, challenge=b''):
  94. return b''
  95. def choose(mech_list, credentials, security_settings, limit=None, min_mech=None):
  96. available_mechs = set(MECHANISMS.keys())
  97. if limit is None:
  98. limit = set(mech_list)
  99. if not isinstance(limit, set):
  100. limit = set(limit)
  101. if not isinstance(mech_list, set):
  102. mech_list = set(mech_list)
  103. mech_list = mech_list.intersection(limit)
  104. available_mechs = available_mechs.intersection(mech_list)
  105. best_score = MECH_SEC_SCORES.get(min_mech, -1)
  106. best_mech = None
  107. for name in available_mechs:
  108. if name in MECH_SEC_SCORES:
  109. if MECH_SEC_SCORES[name] > best_score:
  110. best_score = MECH_SEC_SCORES[name]
  111. best_mech = name
  112. if best_mech is None:
  113. raise SASLNoAppropriateMechanism()
  114. mech_class = MECHANISMS[best_mech]
  115. try:
  116. creds = credentials(mech_class.required_credentials,
  117. mech_class.optional_credentials)
  118. for req in mech_class.required_credentials:
  119. if req not in creds:
  120. raise SASLCancelled('Missing credential: %s' % req)
  121. for opt in mech_class.optional_credentials:
  122. if opt not in creds:
  123. creds[opt] = b''
  124. for cred in creds:
  125. if cred in ('username', 'password', 'authzid'):
  126. creds[cred] = bytes(saslprep(creds[cred]))
  127. else:
  128. creds[cred] = bytes(creds[cred])
  129. security_opts = security_settings(mech_class.security)
  130. return mech_class(best_mech, creds, security_opts)
  131. except SASLCancelled as e:
  132. log.info('SASL: %s: %s', best_mech, e.message)
  133. mech_list.remove(best_mech)
  134. return choose(mech_list, credentials, security_settings,
  135. limit=limit,
  136. min_mech=min_mech)