diff options
Diffstat (limited to 'xmpp/auth.py')
-rw-r--r-- | xmpp/auth.py | 413 |
1 files changed, 413 insertions, 0 deletions
diff --git a/xmpp/auth.py b/xmpp/auth.py new file mode 100644 index 0000000..95842f4 --- /dev/null +++ b/xmpp/auth.py @@ -0,0 +1,413 @@ +## auth.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: auth.py, v1.42 2013/10/21 alkorgun Exp $ + +""" +Provides library with all Non-SASL and SASL authentication mechanisms. +Can be used both for client and transport authentication. +""" + +import dispatcher +import sha + +from base64 import encodestring, decodestring +from hashlib import md5 as __md5 +from plugin import PlugIn +from protocol import * +from random import random as _random +from re import findall as re_findall + +def HH(some): + return __md5(some).hexdigest() + +def H(some): + return __md5(some).digest() + +def C(some): + return ":".join(some) + +class NonSASL(PlugIn): + """ + Implements old Non-SASL (JEP-0078) authentication used in jabberd1.4 and transport authentication. + """ + def __init__(self, user, password, resource): + """ + Caches username, password and resource for auth. + """ + PlugIn.__init__(self) + self.DBG_LINE = "gen_auth" + self.user = user + self.password = password + self.resource = resource + + 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. + """ + if not self.resource: + return self.authComponent(owner) + self.DEBUG("Querying server about possible auth methods", "start") + resp = owner.Dispatcher.SendAndWaitForResponse(Iq("get", NS_AUTH, payload=[Node("username", payload=[self.user])])) + if not isResultNode(resp): + self.DEBUG("No result node arrived! Aborting...", "error") + return 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"): + self.DEBUG("Performing digest authentication", "ok") + query.setTagData("digest", sha.new(owner.Dispatcher.Stream._document_attrs["id"] + self.password).hexdigest()) + if query.getTag("password"): + query.delChild("password") + method = "digest" + elif query.getTag("token"): + token = query.getTagData("token") + seq = query.getTagData("sequence") + self.DEBUG("Performing zero-k authentication", "ok") + hash = sha.new(sha.new(self.password).hexdigest() + token).hexdigest() + for foo in xrange(int(seq)): + hash = sha.new(hash).hexdigest() + query.setTagData("hash", hash) + method = "0k" + else: + self.DEBUG("Sequre methods unsupported, performing plain text authentication", "warn") + query.setTagData("password", self.password) + method = "plain" + resp = owner.Dispatcher.SendAndWaitForResponse(iq) + if isResultNode(resp): + self.DEBUG("Sucessfully authenticated with remove host.", "ok") + owner.User = self.user + owner.Resource = self.resource + owner._registered_name = owner.User + "@" + owner.Server + "/" + owner.Resource + return method + self.DEBUG("Authentication failed!", "error") + + def authComponent(self, owner): + """ + Authenticate component. Send handshake stanza and wait for result. Returns "ok" on success. + """ + self.handshake = 0 + owner.send(Node(NS_COMPONENT_ACCEPT + " handshake", payload=[sha.new(owner.Dispatcher.Stream._document_attrs["id"] + self.password).hexdigest()])) + owner.RegisterHandler("handshake", self.handshakeHandler, xmlns=NS_COMPONENT_ACCEPT) + while not self.handshake: + self.DEBUG("waiting on handshake", "notify") + owner.Process(1) + owner._registered_name = self.user + if self.handshake + 1: + return "ok" + + def handshakeHandler(self, disp, stanza): + """ + Handler for registering in dispatcher for accepting transport authentication. + """ + if stanza.getName() == "handshake": + self.handshake = 1 + else: + self.handshake = -1 + +class SASL(PlugIn): + """ + Implements SASL authentication. + """ + def __init__(self, username, password): + PlugIn.__init__(self) + self.username = username + self.password = password + + def plugin(self, owner): + if not self._owner.Dispatcher.Stream._document_attrs.has_key("version"): + self.startsasl = "not-supported" + 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 auth(self): + """ + Start authentication. Result can be obtained via "SASL.startsasl" attribute + and will beeither "success" or "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 plugout(self): + """ + Remove SASL handlers from owner's dispatcher. Used internally. + """ + if self._owner.__dict__.has_key("features"): + self._owner.UnregisterHandler("features", self.FeaturesHandler, xmlns=NS_STREAMS) + if self._owner.__dict__.has_key("challenge"): + self._owner.UnregisterHandler("challenge", self.SASLHandler, xmlns=NS_SASL) + if self._owner.__dict__.has_key("failure"): + self._owner.UnregisterHandler("failure", self.SASLHandler, xmlns=NS_SASL) + if self._owner.__dict__.has_key("success"): + self._owner.UnregisterHandler("success", self.SASLHandler, xmlns=NS_SASL) + + 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" + self.DEBUG("SASL not supported by server", "error") + return None + mecs = [] + for mec in feats.getTag("mechanisms", namespace=NS_SASL).getTags("mechanism"): + 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) + if "ANONYMOUS" in mecs and self.username == None: + node = Node("auth", attrs={"xmlns": NS_SASL, "mechanism": "ANONYMOUS"}) + elif "DIGEST-MD5" in mecs: + node = Node("auth", attrs={"xmlns": NS_SASL, "mechanism": "DIGEST-MD5"}) + elif "PLAIN" in mecs: + sasl_data = "%s\x00%s\x00%s" % ("@".join((self.username, self._owner.Server)), self.username, self.password) + node = Node("auth", attrs={"xmlns": NS_SASL, "mechanism": "PLAIN"}, payload=[encodestring(sasl_data).replace("\r", "").replace("\n", "")]) + else: + self.startsasl = "failure" + self.DEBUG("I can only use DIGEST-MD5 and PLAIN mecanisms.", "error") + return + self.startsasl = "in-process" + self._owner.send(node.__str__()) + raise NodeProcessed() + + def SASLHandler(self, conn, challenge): + """ + Perform next SASL auth step. Used internally. + """ + if challenge.getNamespace() != NS_SASL: + return None + if challenge.getName() == "failure": + self.startsasl = "failure" + try: + reason = challenge.getChildren()[0] + except: + reason = challenge + self.DEBUG("Failed SASL authentification: %s" % reason, "error") + raise NodeProcessed() + elif challenge.getName() == "success": + self.startsasl = "success" + self.DEBUG("Successfully authenticated with remote server.", "ok") + handlers = self._owner.Dispatcher.dumpHandlers() + self._owner.Dispatcher.PlugOut() + dispatcher.Dispatcher().PlugIn(self._owner) + self._owner.Dispatcher.restoreHandlers(handlers) + self._owner.User = self.username + raise NodeProcessed() + incoming_data = challenge.getData() + chal = {} + data = decodestring(incoming_data) + self.DEBUG("Got challenge:" + data, "ok") + for pair in re_findall('(\w+\s*=\s*(?:(?:"[^"]+")|(?:[^,]+)))', data): + key, value = [x.strip() for x in pair.split("=", 1)] + if value[:1] == '"' and value[-1:] == '"': + value = value[1:-1] + chal[key] = value + if chal.has_key("qop") and "auth" in [x.strip() for x in chal["qop"].split(",")]: + resp = {} + resp["username"] = self.username + resp["realm"] = self._owner.Server + resp["nonce"] = chal["nonce"] + cnonce = "" + for i in range(7): + cnonce += hex(int(_random() * 65536 * 4096))[2:] + resp["cnonce"] = cnonce + resp["nc"] = ("00000001") + resp["qop"] = "auth" + resp["digest-uri"] = "xmpp/" + self._owner.Server + A1 = C([H(C([resp["username"], resp["realm"], self.password])), resp["nonce"], resp["cnonce"]]) + A2 = C(["AUTHENTICATE", resp["digest-uri"]]) + response = HH(C([HH(A1), resp["nonce"], resp["nc"], resp["cnonce"], resp["qop"], HH(A2)])) + resp["response"] = response + resp["charset"] = "utf-8" + sasl_data = "" + for key in ["charset", "username", "realm", "nonce", "nc", "cnonce", "digest-uri", "response", "qop"]: + if key in ["nc", "qop", "response", "charset"]: + sasl_data += "%s=%s," % (key, resp[key]) + else: + sasl_data += "%s=\"%s\"," % (key, resp[key]) + node = Node("response", attrs={"xmlns": NS_SASL}, payload=[encodestring(sasl_data[:-1]).replace("\r", "").replace("\n", "")]) + self._owner.send(node.__str__()) + elif chal.has_key("rspauth"): + self._owner.send(Node("response", attrs={"xmlns": NS_SASL}).__str__()) + else: + self.startsasl = "failure" + self.DEBUG("Failed SASL authentification: unknown challenge", "error") + raise NodeProcessed() + +class Bind(PlugIn): + """ + Bind some JID to the current connection to allow router know of our location. + """ + def __init__(self): + PlugIn.__init__(self) + self.DBG_LINE = "bind" + self.bound = None + + 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 plugout(self): + """ + Remove Bind handler from owner's dispatcher. Used internally. + """ + self._owner.UnregisterHandler("features", self.FeaturesHandler, xmlns=NS_STREAMS) + + def FeaturesHandler(self, conn, feats): + """ + Determine if server supports resource binding and set some internal attributes accordingly. + """ + if not feats.getTag("bind", namespace=NS_BIND): + self.bound = "failure" + self.DEBUG("Server does not requested binding.", "error") + return Nonr + if feats.getTag("session", namespace=NS_SESSION): + self.session = 1 + else: + self.session = -1 + self.bound = [] + + def Bind(self, resource=None): + """ + Perform binding. Use provided resource name or random (if not provided). + """ + while self.bound is None and self._owner.Process(1): + pass + if resource: + resource = [Node("resource", payload=[resource])] + else: + resource = [] + resp = self._owner.SendAndWaitForResponse(Protocol("iq", typ="set", payload=[Node("bind", attrs={"xmlns": NS_BIND}, payload=resource)])) + if isResultNode(resp): + self.bound.append(resp.getTag("bind").getTagData("jid")) + self.DEBUG("Successfully bound %s." % self.bound[-1], "ok") + jid = JID(resp.getTag("bind").getTagData("jid")) + self._owner.User = jid.getNode() + self._owner.Resource = jid.getResource() + resp = self._owner.SendAndWaitForResponse(Protocol("iq", typ="set", payload=[Node("session", attrs={"xmlns": NS_SESSION})])) + if isResultNode(resp): + self.DEBUG("Successfully opened session.", "ok") + self.session = 1 + return "ok" + else: + self.DEBUG("Session open failed.", "error") + self.session = 0 + elif resp: + self.DEBUG("Binding failed: %s." % resp.getTag("error"), "error") + else: + self.DEBUG("Binding failed: timeout expired.", "error") + return "" + +class ComponentBind(PlugIn): + """ + ComponentBind some JID to the current connection to allow router know of our location. + """ + def __init__(self, sasl): + PlugIn.__init__(self) + self.DBG_LINE = "bind" + self.bound = None + self.needsUnregister = None + self.sasl = sasl + + def plugin(self, owner): + """ + Start resource binding, if allowed at this time. Used internally. + """ + if not self.sasl: + self.bound = [] + return None + 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) + self.needsUnregister = 1 + + def plugout(self): + """ + Remove ComponentBind handler from owner's dispatcher. Used internally. + """ + if self.needsUnregister: + self._owner.UnregisterHandler("features", self.FeaturesHandler, xmlns=NS_STREAMS) + + def FeaturesHandler(self, conn, feats): + """ + Determine if server supports resource binding and set some internal attributes accordingly. + """ + if not feats.getTag("bind", namespace=NS_BIND): + self.bound = "failure" + self.DEBUG("Server does not requested binding.", "error") + return None + if feats.getTag("session", namespace=NS_SESSION): + self.session = 1 + else: + self.session = -1 + self.bound = [] + + def Bind(self, domain=None): + """ + Perform binding. Use provided domain name (if not provided). + """ + while self.bound is None and self._owner.Process(1): + pass + if self.sasl: + xmlns = NS_COMPONENT_1 + else: + xmlns = None + self.bindresponse = None + ttl = dispatcher.DefaultTimeout + self._owner.RegisterHandler("bind", self.BindHandler, xmlns=xmlns) + self._owner.send(Protocol("bind", attrs={"name": domain}, xmlns=NS_COMPONENT_1)) + while self.bindresponse is None and self._owner.Process(1) and ttl > 0: + ttl -= 1 + self._owner.UnregisterHandler("bind", self.BindHandler, xmlns=xmlns) + resp = self.bindresponse + if resp and resp.getAttr("error"): + self.DEBUG("Binding failed: %s." % resp.getAttr("error"), "error") + elif resp: + self.DEBUG("Successfully bound.", "ok") + return "ok" + else: + self.DEBUG("Binding failed: timeout expired.", "error") + return "" + + def BindHandler(self, conn, bind): + self.bindresponse = bind + pass |