Advertisement
Guest User

Untitled

a guest
Aug 7th, 2017
140
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 5.02 KB | None | 0 0
  1. class ClientXMPP(sleekxmpp.ClientXMPP):
  2.     # an hacked version of the base class which supports
  3.     # digest-md5 authentication (for facebook)
  4.  
  5.     def __init__(self, *args, **kwargs):
  6.         sleekxmpp.ClientXMPP.__init__(self, *args, **kwargs)
  7.         sasl_ns = 'urn:ietf:params:xml:ns:xmpp-sasl'
  8.         self.add_handler("<challenge xmlns='%s' />"  % sasl_ns,
  9.                                  self._handle_sasl_digest_md5_auth,
  10.                                  instream=True)
  11.  
  12.     def _handle_sasl_auth(self, xml):
  13.         if '{urn:ietf:params:xml:ns:xmpp-tls}starttls' in self.features:
  14.             return False
  15.  
  16.         logger.debug("Starting SASL Auth")
  17.         sasl_ns = 'urn:ietf:params:xml:ns:xmpp-sasl'
  18.         self.add_handler("<success xmlns='%s' />" % sasl_ns,
  19.                          self._handle_auth_success,
  20.                          name='SASL Sucess',
  21.                          instream=True)
  22.         self.add_handler("<failure xmlns='%s' />" % sasl_ns,
  23.                          self._handle_auth_fail,
  24.                          name='SASL Failure',
  25.                          instream=True)
  26.  
  27.         sasl_mechs = xml.findall('{%s}mechanism' % sasl_ns)
  28.         if sasl_mechs:
  29.             for sasl_mech in sasl_mechs:
  30.                 self.features.append("sasl:%s" % sasl_mech.text)
  31.             if 'sasl:PLAIN' in self.features and self.boundjid.user:
  32.                 user = bytes(self.boundjid.user)
  33.                 password = bytes(self.password)
  34.                 auth = base64.b64encode('\x00' + user + \
  35.                                         '\x00' + password).decode('utf-8')
  36.  
  37.                 self.send("<auth xmlns='%s' mechanism='PLAIN'>%s</auth>" \
  38.                           % (sasl_ns, auth))
  39.             elif 'sasl:ANONYMOUS' in self.features and not self.boundjid.user:
  40.                 self.send("<auth xmlns='%s' mechanism='%s' />" % \
  41.                           (sasl_ns, 'ANONYMOUS'))
  42.             elif 'sasl:DIGEST-MD5' in self.features:
  43.                 self.send("<auth xmlns='%s' mechanism='DIGEST-MD5'/>" % sasl_ns)
  44.             else:
  45.                 logger.error("no appropriate login method.")
  46.                 self.disconnect()
  47.         return True
  48.  
  49.     def _handle_sasl_digest_md5_auth(self, xml):
  50.         sasl_ns = 'urn:ietf:params:xml:ns:xmpp-sasl'
  51.         self.add_handler("<success xmlns='%s' />" % sasl_ns,
  52.                          self._handle_auth_success,
  53.                          name='SASL Sucess',
  54.                          instream=True)
  55.         self.add_handler("<failure xmlns='%s' />" % sasl_ns,
  56.                          self._handle_auth_fail,
  57.                          name='SASL Failure',
  58.                          instream=True)
  59.  
  60.         challenge = [item.split('=', 1) for item in base64.b64decode(xml.text).replace("\"", "").split(',', 6) ]
  61.         challenge = dict(challenge)
  62.         logger.debug("MD5 auth challenge: %s", challenge)
  63.  
  64.         # succesfully authenticated, send response
  65.         if challenge.get('rspauth'):
  66.             self.send("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>")
  67.             return
  68.         # TODO: use realm if supplied by server, use default qop unless
  69.         # supplied by server; realm, nonce, qop should all be present.
  70.         if not challenge.get('qop') or not challenge.get('nonce'):
  71.             logger.error("error during digest-md5 authentication. "
  72.                          "challenge missing critical information. "
  73.                          "(challenge: %s" % base64.b64decode(xml.text))
  74.             self._handle_auth_fail(xml)
  75.             return
  76.  
  77.         # TODO: charset can be either UTF-8 or if not present use ISO
  78.         # 8859-1 defaulting for UTF-8 for now.
  79.         # compute the cnonce - a unique hex string only used in this request
  80.         cnonce = ""
  81.         for i in range(7):
  82.             cnonce += hex(int(random.random() * 65536 * 4096))[2:]
  83.         cnonce = base64.encodestring(cnonce)[0:-1]
  84.         a1 = b"%s:%s:%s" %(md5("%s:%s:%s" % (self.boundjid.user,
  85.                                              self.boundjid.host,
  86.                                              self.password)),
  87.                            challenge["nonce"].encode("UTF-8"),
  88.                            cnonce.encode("UTF-8"))
  89.         a2 = "AUTHENTICATE:xmpp/%s" % self.boundjid.host
  90.         resp_hash = md5digest("%s:%s:00000001:%s:auth:%s" \
  91.                                                      %(md5digest(a1),
  92.                                                        challenge["nonce"],
  93.                                                        cnonce, md5digest(a2)))
  94.         response = 'charset=utf-8,username="%s",realm="%s",nonce="%s",nc=00000001,cnonce="%s",digest-uri="%s",response=%s,qop=%s,' \
  95.             % (self.boundjid.user,
  96.                self.boundjid.host,
  97.                challenge["nonce"],
  98.                cnonce,
  99.                "xmpp/%s" % self.boundjid.host, resp_hash, challenge["qop"])
  100.         self.send("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>%s</response>" \
  101.                     % base64.encodestring(response)[:-1])
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement