def get_error_tree(iq, type, code, tag): result = Element('iq', {'type': 'error', 'id': iq.get('id')}) result.append(iq) err_tree = Element('error', {'type': type, 'code': code,}) SubElement(err_tree, tag, {'xmlns': 'urn:ietf:params:xml:ns:xmpp-stanzas'}) result.append(err_tree) return result
def createRosterQuery(cjid, subName, name=None, groups=None, itemArgs=None): """Creates and returns a <query> item for sending in an <iq> in a roster push. cjid -- jid as a str for the contact in a roster item. subName -- name of the subscription as a str. name -- name for the contact. Can be None. groups -- list of group names as strings. """ itemArgs = itemArgs or {} query = Element('query', {'xmlns': 'jabber:iq:roster'}) d = { 'jid': cjid, 'subscription': subName, } if name: d['name'] = name d.update(itemArgs) item = SubElement(query, 'item', d) for groupName in groups: if groupName: # don't want empty groups group = Element('group') group.text = groupName item.append(group) return query
def createRosterQuery(cjid, subName, name=None, groups=None, itemArgs=None): """Creates and returns a <query> item for sending in an <iq> in a roster push. cjid -- jid as a str for the contact in a roster item. subName -- name of the subscription as a str. name -- name for the contact. Can be None. groups -- list of group names as strings. """ itemArgs = itemArgs or {} query = Element('query', {'xmlns' : 'jabber:iq:roster'}) d = { 'jid' : cjid, 'subscription' : subName, } if name: d['name'] = name d.update(itemArgs) item = SubElement(query, 'item', d) for groupName in groups: if groupName: # don't want empty groups group = Element('group') group.text = groupName item.append(group) return query
def handle(self, tree, msg, lastRetVal=None): if len(tree) > 0: # get the original iq msg origIQ = tree else: logging.warning("[%s] Original <iq> missing:\n%s", self.__class__, tostring(tree)) return id = origIQ.get('id') if id: res = Element('iq', { 'type' : 'error', 'id' : id }) res.append(origIQ) err = Element('error', {'type' : 'cancel'}) SubElement(err, 'service-unavailable', {'xmlns' : 'urn:ietf:params:xml:ns:xmpp-stanzas'}) res.append(err) return chainOutput(lastRetVal, res) else: logging.warning("[%s] No id in <iq>:\n%s", self.__class__, tostring(origIQ)) return lastRetVal
def testNamespacedXPath(self): """Should be able to refer to elements using namespaces in XPath""" self.p.feed(streamStart) self.p.feed('<stream:features></stream:features>') stream = Element('dummy') stream.insert(0, self.p.tree) self.assert_(stream.find('{http://etherx.jabber.org/streams}features') is not None)
def getAsTree(self): """Returns the roster Element tree starting from <query>. Call loadRoster() before this. """ query = Element('query', {'xmlns' : 'jabber:iq:roster'}) for item in self.items: query.append(self.items[item].getAsTree()) return query
def makeServiceUnavailableError(): fromJID = cjid.__str__() reply = Element("message", {"type": "error", "to": fromJID}) reply.append(copy(tree)) error = Element("error", {"type": "cancel"}) SubElement(error, "service-unavailable", {"xmlns": "urn:ietf:params:xml:ns:xmpp-stanzas"}) routeData = {"to": fromJID, "data": reply} msg.setNextHandler("route-client") return chainOutput(lastRetVal, routeData)
def getAsTree(self): """Returns the roster Element tree starting from <query>. Call loadRoster() before this. """ query = Element('query', {'xmlns': 'jabber:iq:roster'}) for item in self.items: query.append(self.items[item].getAsTree()) return query
def makeServiceUnavailableError(): fromJID = cjid.__str__() reply = Element('message', {'type': 'error', 'to': fromJID}) reply.append(copy(tree)) error = Element('error', {'type': 'cancel'}) SubElement(error, 'service-unavailable', {'xmlns': 'urn:ietf:params:xml:ns:xmpp-stanzas'}) routeData = {'to': fromJID, 'data': reply} msg.setNextHandler('route-client') return chainOutput(lastRetVal, routeData)
def testNamespacedXPath(self): """Should be able to refer to elements using namespaces in XPath""" self.p.feed(streamStart) self.p.feed('<stream:features></stream:features>') stream = Element('dummy') stream.insert(0, self.p.tree) self.assert_( stream.find('{http://etherx.jabber.org/streams}features') is not None)
def handle(self, tree, msg, lastRetVal=None): if isinstance(lastRetVal, SASLError): el = Element('failure', {'xmlns' : 'urn:ietf:params:xml:ns:xmpp-sasl'}) el.append(lastRetVal.errorElement()) return chainOutput(lastRetVal, el) else: logging.warning("[%s] SASLErrorHandler was passed a non-SASL " +\ "exception. Exception: %s", self.__class__, lastRetVal) raise Exception, "can't handle a non-SASL error"
def handle(self, tree, msg, lastRetVal=None): if isinstance(lastRetVal, SASLError): el = Element('failure', {'xmlns': 'urn:ietf:params:xml:ns:xmpp-sasl'}) el.append(lastRetVal.errorElement()) return chainOutput(lastRetVal, el) else: logging.warning("[%s] SASLErrorHandler was passed a non-SASL " +\ "exception. Exception: %s", self.__class__, lastRetVal) raise Exception, "can't handle a non-SASL error"
def getAsTree(self): """Return the roster item as an Element tree starting from <item>""" item = Element('item', { 'jid' : self.jid, 'subscription' : Subscription.getPrimaryNameFromState(self.subscription) }) if self.name: item.set('name', self.name) for group in self.groups: SubElement(item, 'group').text = group return item
def handle_close(self): # we want to be able to do stuff in handlers when the connection is # closed, so we dispatch into the 'stream-end' phase. # we set the tree to a dummy element so that the handlers could modify # it. if self.data['stream']['closing']: # already closing return wrapper = Element('wrapper') tree = Element('tag') wrapper.append(tree) Dispatcher().dispatch(wrapper, self, 'stream-end')
def handle(self, tree, msg, lastRetVal=None): iq = tree id = iq.get('id') if id: bind = iq[0] if len(bind) > 0: resource = bind[0].text else: # generate an id resource = generateId()[:6] # TODO: check that we don't already have such a resource jid = msg.conn.data['user']['jid'] bindResource(msg, resource) res = Element('iq', {'type' : 'result', 'id' : id}) bind = Element('bind', {'xmlns' : 'urn:ietf:params:xml:ns:xmpp-bind'}) jidEl = Element('jid') jidEl.text = '%s/%s' % (jid, resource) bind.append(jidEl) res.append(bind) return chainOutput(lastRetVal, res) else: logging.warning("[%s] No id in <iq>:\n%s", self.__class__, tostring(iq)) return lastRetVal
def handle(self, tree, msg, lastRetVal=None): iq = tree id = iq.get("id") if id: bind = iq[0] if len(bind) > 0: resource = bind[0].text else: # generate an id resource = generateId()[:6] # TODO: check that we don't already have such a resource jid = msg.conn.data["user"]["jid"] bindResource(msg, resource) res = Element("iq", {"type": "result", "id": id}) bind = Element("bind", {"xmlns": "urn:ietf:params:xml:ns:xmpp-bind"}) jidEl = Element("jid") jidEl.text = "%s/%s" % (jid, resource) bind.append(jidEl) res.append(bind) return chainOutput(lastRetVal, res) else: logging.warning("[%s] No id in <iq>:\n%s", self.__class__, tostring(iq)) return lastRetVal
def getAsTree(self): """Return the roster item as an Element tree starting from <item>""" item = Element( 'item', { 'jid': self.jid, 'subscription': Subscription.getPrimaryNameFromState(self.subscription) }) if self.name: item.set('name', self.name) for group in self.groups: SubElement(item, 'group').text = group return item
def makeSuccess(id): """Creates the auth successful iq stanza""" d = {'type': 'result'} if id: d['id'] = id iq = Element('iq', d) return iq
def handle(self, tree, msg, lastRetVal=None): res = Element('stream:features') mechs = SubElement(res, 'mechanisms', {'xmlns' : 'urn:ietf:params:xml:ns:xmpp-sasl'}) SubElement(mechs, 'mechanism').text = 'PLAIN' return chainOutput(lastRetVal, res)
def handle(self, tree, msg, lastRetVal=None): res = Element('stream:features') SubElement(res, 'bind', {'xmlns': 'urn:ietf:params:xml:ns:xmpp-bind'}) SubElement(res, 'session', {'xmlns': 'urn:ietf:params:xml:ns:xmpp-session'}) return chainOutput(lastRetVal, res)
def makeServiceUnavailableError(): fromJID = cjid.__str__() reply = Element('message', { 'type' : 'error', 'to' : fromJID }) reply.append(copy(tree)) error = Element('error', {'type' : 'cancel'}) SubElement(error, 'service-unavailable', { 'xmlns' : 'urn:ietf:params:xml:ns:xmpp-stanzas' }) routeData = { 'to' : fromJID, 'data' : reply } msg.setNextHandler('route-client') return chainOutput(lastRetVal, routeData)
def handle(self, tree, msg, lastRetVal=None): if len(tree) > 0: # get the original iq msg origIQ = tree else: logging.warning("[%s] Original <iq> missing:\n%s", self.__class__, tostring(tree)) return id = origIQ.get('id') if id: res = Element('iq', {'type': 'error', 'id': id}) res.append(origIQ) err = Element('error', {'type': 'cancel'}) SubElement(err, 'service-unavailable', {'xmlns': 'urn:ietf:params:xml:ns:xmpp-stanzas'}) res.append(err) return chainOutput(lastRetVal, res) else: logging.warning("[%s] No id in <iq>:\n%s", self.__class__, tostring(origIQ)) return lastRetVal
def checkPolicyViolation(msg): """Checks if a SASL auth is being or has been attempted""" data = msg.conn.data['sasl'] if data['in-progress'] or data['complete'] or data['mechObj']: # policy violation! se = Element('stream:error') SubElement(se, 'policy-violation', {'xmlns': 'urn:ietf:params:xml:ns:xmpp-streams'}) return se
def act(): # TODO: verify that it's coming from a known user jid = msg.conn.data["user"]["jid"] resource = msg.conn.data["user"]["resource"] id = tree.get("id") if id is None: logging.warning("[%s] No id in roster get query. Tree: %s", self.__class__, tree) # TODO: throw exception here return roster = Roster(jid) roster.loadRoster() res = Element("iq", {"to": "/".join([jid, resource]), "type": "result", "id": id}) res.append(roster.getAsTree()) return chainOutput(lastRetVal, res)
def makeNotAcceptable(id): """Creates the not-acceptable iq error stanza""" d = {'type': 'error'} if id: d['id'] = id iq = Element('iq', d) error = SubElement(iq, 'error', {'code': '406', 'type': 'modify'}) SubElement(error, 'not-acceptable', {'xmlns': 'urn:ietf:params:xml:ns:xmpp-stanzas'}) return iq
def makeConflict(id): """Creates the conflict iq error stanza""" d = {'type': 'error'} if id: d['id'] = id iq = Element('iq', d) error = SubElement(iq, 'error', {'code': '409', 'type': 'cancel'}) SubElement(error, 'conflict', {'xmlns': 'urn:ietf:params:xml:ns:xmpp-stanzas'}) return iq
def handle(self, tree, msg, lastRetVal=None): res = Element( 'iq', { 'from': msg.conn.server.hostname, 'type': 'result', 'id': tree.get('id') }) msg.conn.data['user']['in-session'] = True return chainOutput(lastRetVal, res)
def makeNotAuthorized(id): """Creates the not-authorized iq error stanza""" d = {'type': 'error'} if id: d['id'] = id iq = Element('iq', d) error = SubElement(iq, 'error', {'code': '401', 'type': 'cancel'}) SubElement(error, 'not-authorized', {'xmlns': 'urn:ietf:params:xml:ns:xmpp-stanzas'}) return iq
def handle(self, tree, msg, lastRetVal=None): res = Element('stream:features') mechs = SubElement(res, 'mechanisms', {'xmlns': 'urn:ietf:params:xml:ns:xmpp-sasl'}) SubElement(mechs, 'mechanism').text = 'DIGEST-MD5' SubElement(mechs, 'mechanism').text = 'PLAIN' # we also support the old style jabber:iq:auth SubElement(res, 'auth', {'xmlns': 'http://jabber.org/features/iq-auth'}) return chainOutput(lastRetVal, res)
def act(): # TODO: verify that it's coming from a known user jid = msg.conn.data['user']['jid'] resource = msg.conn.data['user']['resource'] id = tree.get('id') if id is None: logging.warning('[%s] No id in roster get query. Tree: %s', self.__class__, tree) # TODO: throw exception here return roster = Roster(jid) roster.loadRoster() res = Element('iq', { 'to': '/'.join([jid, resource]), 'type': 'result', 'id': id }) res.append(roster.getAsTree()) return chainOutput(lastRetVal, res)
def act(): # TODO: verify that it's coming from a known user jid = msg.conn.data['user']['jid'] resource = msg.conn.data['user']['resource'] id = tree.get('id') if id is None: logging.warning('[%s] No id in roster get query. Tree: %s', self.__class__, tree) # TODO: throw exception here return roster = Roster(jid) roster.loadRoster() res = Element('iq', { 'to' : '/'.join([jid, resource]), 'type' : 'result', 'id' : id }) res.append(roster.getAsTree()) return chainOutput(lastRetVal, res)
def handle(self, tree, msg, lastRetVal=None): if len(tree) > 0: # get the original iq msg origIQ = tree else: logging.warning("[%s] Original <iq> missing:\n%s", self.__class__, tostring(tree)) return id = origIQ.get("id") if id: res = Element("iq", {"type": "error", "id": id}) res.append(origIQ) err = Element("error", {"type": "cancel"}) SubElement(err, "service-unavailable", {"xmlns": "urn:ietf:params:xml:ns:xmpp-stanzas"}) res.append(err) return chainOutput(lastRetVal, res) else: logging.warning("[%s] No id in <iq>:\n%s", self.__class__, tostring(origIQ)) return lastRetVal
def handle(self, b64text): """Verify the username/password in response""" authtext = '' if b64text: try: authtext = fromBase64(b64text) except: raise SASLIncorrectEncodingError auth = authtext.split('\x00') if len(auth) != 3: raise SASLIncorrectEncodingError con = DBautocommit() c = con.cursor() c.execute("SELECT * FROM jids WHERE \ jid = ? AND password = ?" , (auth[1] + \ '@' + self.msg.conn.server.hostname, auth[2])) res = c.fetchall() if len(res) == 0: c.close() con.close() raise SASLAuthError c.close() con.close() self.msg.conn.data['sasl']['complete'] = True self.msg.conn.data['sasl']['in-progress'] = False self.msg.conn.data['user']['jid'] = '%s@%s' % ( auth[1], self.msg.conn.server.hostname) # record the JID for local delivery self.msg.conn.server.conns[self.msg.conn.id] = (JID( self.msg.conn.data['user']['jid']), self.msg.conn) self.msg.conn.parser.resetParser() return Element('success', {'xmlns': 'urn:ietf:params:xml:ns:xmpp-sasl'})
def __init__(self, sock, addr, server): ServerConnection.__init__(self, sock, addr, server) self.id = 'locsin%s' % id(self) self.data['server']['direction'] = 'from' self.data['server']['hostname'] = 'localhost' # send the <stream> to prime the parser for the ns it'll deal with, # but tell it not to process the xml during priming, since we don't # care for auth/encr on a loopback connection self.parser.disable() data = "<?xml version='1.0' ?>" +\ "<stream:stream xmlns='jabber:server' " +\ "xmlns:stream='http://etherx.jabber.org/streams' " +\ "version='1.0'>" self.parser.feed(data) self.parser.depth = 1 self.parser.stream = Element('{http://etherx.jabber.org/streams}stream', {'version' : '1.0'}) self.parser.ns = 'jabber:server' self.parser.enable() logging.info("New LocalServerInConnection created with %s", addr)
def handle(self, tree, msg, lastRetVal=None): iq = tree id = iq.get('id') if id: bind = iq[0] if len(bind) > 0: resource = bind[0].text else: # generate an id resource = generateId()[:6] # TODO: check that we don't already have such a resource jid = msg.conn.data['user']['jid'] bindResource(msg, resource) res = Element('iq', {'type': 'result', 'id': id}) bind = Element('bind', {'xmlns': 'urn:ietf:params:xml:ns:xmpp-bind'}) jidEl = Element('jid') jidEl.text = '%s/%s' % (jid, resource) bind.append(jidEl) res.append(bind) return chainOutput(lastRetVal, res) else: logging.warning("[%s] No id in <iq>:\n%s", self.__class__, tostring(iq)) return lastRetVal
def handle(self, tree, msg, lastRetVal=None): # generate error response tree def get_error_tree(iq, type, code, tag): result = Element('iq', {'type': 'error', 'id': iq.get('id')}) result.append(iq) err_tree = Element('error', {'type': type, 'code': code,}) SubElement(err_tree, tag, {'xmlns': 'urn:ietf:params:xml:ns:xmpp-stanzas'}) result.append(err_tree) return result if len(tree) > 0: # get the original iq msg origIQ = tree else: logging.warning("[%s] Original <iq> missing:\n%s", self.__class__, tostring(tree)) return lastRetVal id = origIQ.get('id') if id: try: username_tree = origIQ[0][0] password_tree = origIQ[0][1] if username_tree.tag == '{jabber:iq:register}username' and password_tree.tag == '{jabber:iq:register}password': username = username_tree.text password = password_tree.text # ruling out illegal username if not re.match("^[a-zA-Z0-9_.-]+$", username): raise Exception('Username not accepted') # write to database try: con = DB() with closing(con.cursor()) as cursor: cursor.execute("INSERT INTO jids (jid, password) VALUES ('%s@%s', '%s')" % (username, msg.conn.server.hostname, password)) con.commit() res = Element('iq', {'type': 'result', 'id': id}) query = deepcopy(origIQ[0]) query.insert(0, Element('registered')) res.append(query) return chainOutput(lastRetVal, res) # conflict response except sqlite.IntegrityError as e: if e.message.find('column jid is not unique') >= 0: logging.warning("[%s] Username conflict in <iq>:\n%s", self.__class__, str(e), tostring(origIQ)) res = get_error_tree(origIQ, 'cancel', '409', 'conflict') return chainOutput(lastRetVal, res) else: raise e else: raise Exception('IQ missing registration fields') # error response except Exception as e: error_string = str(e) logging.warning("[%s] Register failed '%s' in <iq>:\n%s", self.__class__, str(e) if error_string else 'Unknown error', tostring(origIQ)) res = get_error_tree(origIQ, 'modify', '406', 'not-acceptable') return chainOutput(lastRetVal, res) else: logging.warning("[%s] No id in <iq>:\n%s", self.__class__, tostring(origIQ)) return lastRetVal
def act(): # we have to be passed a tree to work # or a tuple with routingData and a tree if not isinstance(lastRetVal, list): logging.warning('[%s] lastRetVal is not a list', self.__class__) return if isinstance(lastRetVal[-1], Element): if lastRetVal[-1].tag.find('query') == -1: logging.warning('[%s] Got a non-query Element last return value' +\ '. Last return value: %s', self.__class__, lastRetVal) elif isinstance(lastRetVal[-1], tuple): if not isinstance(lastRetVal[-1][0], dict) \ or not isinstance(lastRetVal[-1][1], Element): logging.warning('[%s] Got a non-query Element last return value' +\ '. Last return value: %s', self.__class__, lastRetVal) return else: logging.warning('[%s] Roster push needs either a <query> Element ' +\ 'as the last item in lastRetVal or a tuple ' + \ 'with (routeData, query Element)', self.__class__) return # this is the roster <query> that we'll send # it could be a tuple if we got routing data as well query = lastRetVal.pop(-1) routeData = None # did we get routing data (from S2S) if isinstance(query, tuple): routeData = query[0] query = query[1] if routeData: jid = routeData['jid'] resources = routeData['resources'] else: jid = msg.conn.data['user']['jid'] resource = msg.conn.data['user']['resource'] resources = msg.conn.server.data['resources'][jid] for res, con in resources.items(): # don't send the roster to clients that didn't request it if con.data['user']['requestedRoster']: iq = Element('iq', { 'to' : '%s/%s' % (jid, res), 'type' : 'set', 'id' : generateId()[:10] }) iq.append(query) # TODO: remove this. debug. logging.debug("Sending " + tostring(iq)) con.send(tostring(iq)) if tree.tag == '{jabber:client}iq' and tree.get('id'): # send an ack to client if this is in reply to a roster get/set id = tree.get('id') d = { 'to' : '%s/%s' % (jid, resource), 'type' : 'result', 'id' : id } iq = Element('iq', d) return chainOutput(lastRetVal, iq)
"""XMPP SASL and iq auth stuff""" # Some parts are borrowed from twisted. See TWISTED-LICENSE for details on its # license. import pjs.auth_mechanisms as mechs import pjs.threadpool as threadpool import logging from pjs.handlers.base import Handler, ThreadedHandler, poll, chainOutput from pjs.auth_mechanisms import SASLError, IQAuthError from pjs.handlers.iq import bindResource from pjs.utils import FunctionCall from pjs.elementtree.ElementTree import Element, SubElement iqAuthEl = Element('iq', {'type': 'result'}) iqAuthQueryEl = SubElement(iqAuthEl, 'query', {'xmlns': 'jabber:iq:auth'}) SubElement(iqAuthQueryEl, 'username') SubElement(iqAuthQueryEl, 'digest') SubElement(iqAuthQueryEl, 'resource') def checkPolicyViolation(msg): """Checks if a SASL auth is being or has been attempted""" data = msg.conn.data['sasl'] if data['in-progress'] or data['complete'] or data['mechObj']: # policy violation! se = Element('stream:error') SubElement(se, 'policy-violation', {'xmlns': 'urn:ietf:params:xml:ns:xmpp-streams'}) return se
def act(): d = msg.conn.data retVal = lastRetVal jid = d['user']['jid'] resource = d['user']['resource'] roster = Roster(jid) presTree = deepcopy(tree) presTree.set('from', '%s/%s' % (jid, resource)) probes = [] init_rosters = [] offline_msgs = [] if tree.get('to') is None and not d['user']['active']: # initial presence # TODO: we don't need to do it every time. we can cache the # data after the first resource is active and just resend # that to all new resources d['user']['active'] = True # get jids of the contacts whose status we're interested in cjids = roster.getPresenceSubscriptions() probeTree = Element('presence', { 'type': 'probe', 'from' : '%s/%s' \ % (jid, resource) }) # TODO: replace this with a more efficient router handler for cjid in cjids: probeTree.set('to', cjid) probeRouteData = { 'to' : cjid, 'data' : deepcopy(probeTree) } probes.append(probeRouteData) # they're sent first. see below # send initial roster list to this user rosterTree = Element('presence', { 'type': 'unavailable', 'to' : '%s/%s' \ % (jid, resource) }) for cjid in cjids: rosterTree.set('from', cjid) rosterRouterData = { 'to' : '%s/%s' % (jid, resource), 'data' : deepcopy(rosterTree) } init_rosters.append(rosterRouterData) # send offline message to this user try: con = DB() result = [] to_jid = JID(jid) with closing(con.cursor()) as cursor: cursor.execute("SELECT fromid, time, content FROM offline WHERE toid = %d ORDER BY time DESC" % (to_jid.getNumId())) con.commit() result = cursor.fetchall() with closing(con.cursor()) as cursor: cursor.execute("DELETE FROM offline WHERE toid = %d" % (to_jid.getNumId())) con.commit() for fromid, time, content in result: fromJID = JID(fromid, True).getBare() toJID = '%s/%s' % (jid, resource) reply = Element('message', { 'to': toJID, 'from': fromJID, 'type': 'chat' }) body = Element('body') body.text = content reply.append(body) delay = Element('delay', { 'xmlns': 'urn:xmpp:delay', 'from': fromJID, 'stamp': time.strftime("%Y-%m-%dT%H:%M:%SZ") }) reply.append(delay) routeData = { 'to' : toJID, 'data': reply } offline_msgs.append(routeData) logging.debug("[%s] Sending %d offline messages to %s", self.__class__, len(offline_msgs), to_jid.getBare()) except Exception as e: logging.warning("[%s] Failed to read offline messages: %s", self.__class__, str(e)) # broadcast to other resources of this user retVal = self.broadcastToOtherResources(presTree, msg, retVal, jid, resource) elif tree.get('to') is not None: # TODO: directed presence return elif tree.get('type') == 'unavailable': # broadcast to other resources of this user d['user']['active'] = False retVal = self.broadcastToOtherResources(presTree, msg, retVal, jid, resource) # record this stanza as the last presence sent from this client lastPresence = deepcopy(tree) lastPresence.set('from', '%s/%s' % (jid, resource)) d['user']['lastPresence'] = lastPresence # lookup contacts interested in presence cjids = roster.getPresenceSubscribers() # TODO: replace this with another router handler that would send # it out to all cjids in a batch instead of queuing a handler # for each for cjid in cjids: presTree.set('to', cjid) presRouteData = { 'to' : cjid, 'data' : deepcopy(presTree) } retVal = chainOutput(retVal, presRouteData) msg.setNextHandler('route-server') # send the probes first for probe in probes: msg.setNextHandler('route-server') retVal = chainOutput(retVal, probe) # send initial rosters for init_roster in init_rosters: msg.setNextHandler('route-client') retVal = chainOutput(retVal, init_roster) # send offline messages for offline_msg in offline_msgs: msg.setNextHandler('route-client') retVal = chainOutput(retVal, offline_msg) return retVal
def errorElement(self): return Element('mechanism-too-weak')
def handle(self, data=None): """Performs DIGEST-MD5 auth based on current state. data -- either None for initial challenge, base64-encoded text when the client responds to challenge 1, or the tree when the client responds to challenge 2. """ # TODO: authz # TODO: subsequent auth qop = 'qop="auth"' charset = 'charset=utf-8' algo = 'algorithm=md5-sess' if self.state == SASLDigestMD5.INIT: # initial challenge self.nonce = generateId() self.state = SASLDigestMD5.SENT_CHALLENGE1 nonce = 'nonce="%s"' % self.nonce realm = 'realm="%s"' % self.realm res = Element('challenge', {'xmlns' : 'urn:ietf:params:xml:ns:xmpp-sasl'}) res.text = base64.b64encode(','.join([realm, qop, nonce, charset, algo])) return res elif self.state == SASLDigestMD5.SENT_CHALLENGE1 and data: # response to client's reponse (ie. challenge 2) try: text = fromBase64(data) except: raise SASLIncorrectEncodingError pairs = self._parse(text) try: username = pairs['username'] nonce = pairs['nonce'] realm = pairs['realm'] cnonce = pairs['cnonce'] nc = pairs['nc'] qop = pairs['qop'] response = pairs['response'] digest_uri = pairs['digest-uri'] except KeyError: self._handleFailure() raise SASLAuthError self.username = username # authz is ignored for now if nonce != self.nonce or realm != self.realm \ or int(nc, 16) != 1 or qop[0] != 'auth' or not response\ or not digest_uri: self._handleFailure() raise SASLAuthError # fetch the password now con = DBautocommit() c = con.cursor() c.execute("SELECT password FROM jids WHERE \ jid = ?", (username + '@%s' % self.msg.conn.server.hostname,)) for row in c: password = row['password'] break else: self._handleFailure() c.close() con.close() raise SASLAuthError c.close() con.close() # compute the digest as per RFC 2831 a1 = "%s:%s:%s" % (H("%s:%s:%s" % (username, realm, password)), nonce, cnonce) a2 = ":%s" % digest_uri a2client = "AUTHENTICATE:%s" % digest_uri digest = HEX(KD(HEX(H(a1)), "%s:%s:%s:%s:%s" % (nonce, nc, cnonce, "auth", HEX(H(a2client))))) if digest == response: rspauth = HEX(KD(HEX(H(a1)), "%s:%s:%s:%s:%s" % (nonce, nc, cnonce, "auth", HEX(H(a2))))) self.state = SASLDigestMD5.SENT_CHALLENGE2 res = Element('challenge', {'xmlns' : 'urn:ietf:params:xml:ns:xmpp-sasl'}) res.text = base64.b64encode(u"rspauth=%s" % rspauth) return res else: self._handleFailure() raise SASLAuthError elif self.state == SASLDigestMD5.SENT_CHALLENGE2 and isinstance(data, Element): # expect to get <response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/> respInd = data.tag.find('{urn:ietf:params:xml:ns:xmpp-sasl}response') d = self.msg.conn.data if respInd != -1 and len(data) == 0: self.state = SASLDigestMD5.INIT d['sasl']['complete'] = True d['sasl']['in-progress'] = False d['user']['jid'] = '%s@%s' % (self.username, self.msg.conn.server.hostname) # record the JID for local delivery self.msg.conn.server.conns[self.msg.conn.id] = (JID(d['user']['jid']), self.msg.conn) self.msg.conn.parser.resetParser() res = Element('success', {'xmlns' : 'urn:ietf:params:xml:ns:xmpp-sasl'}) return res else: self._handleFailure() raise SASLAuthError else: self._handleFailure() raise SASLAuthError
def errorElement(self): return Element('invalid-mechanism')
def act(): # we have to be passed a tree to work # or a tuple with routingData and a tree if not isinstance(lastRetVal, list): logging.warning("[%s] lastRetVal is not a list", self.__class__) return if isinstance(lastRetVal[-1], Element): if lastRetVal[-1].tag.find("query") == -1: logging.warning( "[%s] Got a non-query Element last return value" + ". Last return value: %s", self.__class__, lastRetVal, ) elif isinstance(lastRetVal[-1], tuple): if not isinstance(lastRetVal[-1][0], dict) or not isinstance(lastRetVal[-1][1], Element): logging.warning( "[%s] Got a non-query Element last return value" + ". Last return value: %s", self.__class__, lastRetVal, ) return else: logging.warning( "[%s] Roster push needs either a <query> Element " + "as the last item in lastRetVal or a tuple " + "with (routeData, query Element)", self.__class__, ) return # this is the roster <query> that we'll send # it could be a tuple if we got routing data as well query = lastRetVal.pop(-1) routeData = None # did we get routing data (from S2S) if isinstance(query, tuple): routeData = query[0] query = query[1] if routeData: jid = routeData["jid"] resources = routeData["resources"] else: jid = msg.conn.data["user"]["jid"] resource = msg.conn.data["user"]["resource"] resources = msg.conn.server.data["resources"][jid] for res, con in resources.items(): # don't send the roster to clients that didn't request it if con.data["user"]["requestedRoster"]: iq = Element("iq", {"to": "%s/%s" % (jid, res), "type": "set", "id": generateId()[:10]}) iq.append(query) # TODO: remove this. debug. logging.debug("Sending " + tostring(iq)) con.send(tostring(iq)) if tree.tag == "{jabber:client}iq" and tree.get("id"): # send an ack to client if this is in reply to a roster get/set id = tree.get("id") d = {"to": "%s/%s" % (jid, resource), "type": "result", "id": id} iq = Element("iq", d) return chainOutput(lastRetVal, iq)
def errorElement(self): return Element('incorrect-encoding')
def errorElement(self): return Element('invalid-authzid')
def act(): d = msg.conn.data retVal = lastRetVal jid = d['user']['jid'] resource = d['user']['resource'] roster = Roster(jid) presTree = deepcopy(tree) presTree.set('from', '%s/%s' % (jid, resource)) probes = [] if tree.get('to') is None and not d['user']['active']: # initial presence # TODO: we don't need to do it every time. we can cache the # data after the first resource is active and just resend # that to all new resources d['user']['active'] = True # get jids of the contacts whose status we're interested in cjids = roster.getPresenceSubscriptions() probeTree = Element('presence', { 'type': 'probe', 'from' : '%s/%s' \ % (jid, resource) }) # TODO: replace this with a more efficient router handler for cjid in cjids: probeTree.set('to', cjid) probeRouteData = { 'to' : cjid, 'data' : deepcopy(probeTree) } probes.append(probeRouteData) # they're sent first. see below # broadcast to other resources of this user retVal = self.broadcastToOtherResources(presTree, msg, retVal, jid, resource) elif tree.get('to') is not None: # TODO: directed presence return elif tree.get('type') == 'unavailable': # broadcast to other resources of this user d['user']['active'] = False retVal = self.broadcastToOtherResources(presTree, msg, retVal, jid, resource) # record this stanza as the last presence sent from this client lastPresence = deepcopy(tree) lastPresence.set('from', '%s/%s' % (jid, resource)) d['user']['lastPresence'] = lastPresence # lookup contacts interested in presence cjids = roster.getPresenceSubscribers() # TODO: replace this with another router handler that would send # it out to all cjids in a batch instead of queuing a handler # for each for cjid in cjids: presTree.set('to', cjid) presRouteData = { 'to' : cjid, 'data' : deepcopy(presTree) } retVal = chainOutput(retVal, presRouteData) msg.setNextHandler('route-server') # send the probes first for probe in probes: msg.setNextHandler('route-server') retVal = chainOutput(retVal, probe) return retVal
def errorElement(self): return Element('not-authorized')
def handle(self, data=None): """Performs DIGEST-MD5 auth based on current state. data -- either None for initial challenge, base64-encoded text when the client responds to challenge 1, or the tree when the client responds to challenge 2. """ # TODO: authz # TODO: subsequent auth qop = 'qop="auth"' charset = 'charset=utf-8' algo = 'algorithm=md5-sess' if self.state == SASLDigestMD5.INIT: # initial challenge self.nonce = generateId() self.state = SASLDigestMD5.SENT_CHALLENGE1 nonce = 'nonce="%s"' % self.nonce realm = 'realm="%s"' % self.realm res = Element('challenge', {'xmlns': 'urn:ietf:params:xml:ns:xmpp-sasl'}) res.text = base64.b64encode(','.join( [realm, qop, nonce, charset, algo])) return res elif self.state == SASLDigestMD5.SENT_CHALLENGE1 and data: # response to client's reponse (ie. challenge 2) try: text = fromBase64(data) except: raise SASLIncorrectEncodingError pairs = self._parse(text) try: username = pairs['username'] nonce = pairs['nonce'] realm = pairs['realm'] cnonce = pairs['cnonce'] nc = pairs['nc'] qop = pairs['qop'] response = pairs['response'] digest_uri = pairs['digest-uri'] except KeyError: self._handleFailure() raise SASLAuthError self.username = username # authz is ignored for now if nonce != self.nonce or realm != self.realm \ or int(nc, 16) != 1 or qop[0] != 'auth' or not response\ or not digest_uri: self._handleFailure() raise SASLAuthError # fetch the password now con = DBautocommit() c = con.cursor() c.execute( "SELECT password FROM jids WHERE \ jid = ?", (username + '@%s' % self.msg.conn.server.hostname, )) for row in c: password = row['password'] break else: self._handleFailure() c.close() con.close() raise SASLAuthError c.close() con.close() # compute the digest as per RFC 2831 a1 = "%s:%s:%s" % (H("%s:%s:%s" % (username, realm, password)), nonce, cnonce) a2 = ":%s" % digest_uri a2client = "AUTHENTICATE:%s" % digest_uri digest = HEX( KD( HEX(H(a1)), "%s:%s:%s:%s:%s" % (nonce, nc, cnonce, "auth", HEX(H(a2client))))) if digest == response: rspauth = HEX( KD( HEX(H(a1)), "%s:%s:%s:%s:%s" % (nonce, nc, cnonce, "auth", HEX(H(a2))))) self.state = SASLDigestMD5.SENT_CHALLENGE2 res = Element('challenge', {'xmlns': 'urn:ietf:params:xml:ns:xmpp-sasl'}) res.text = base64.b64encode(u"rspauth=%s" % rspauth) return res else: self._handleFailure() raise SASLAuthError elif self.state == SASLDigestMD5.SENT_CHALLENGE2 and isinstance( data, Element): # expect to get <response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/> respInd = data.tag.find( '{urn:ietf:params:xml:ns:xmpp-sasl}response') d = self.msg.conn.data if respInd != -1 and len(data) == 0: self.state = SASLDigestMD5.INIT d['sasl']['complete'] = True d['sasl']['in-progress'] = False d['user']['jid'] = '%s@%s' % (self.username, self.msg.conn.server.hostname) # record the JID for local delivery self.msg.conn.server.conns[self.msg.conn.id] = (JID( d['user']['jid']), self.msg.conn) self.msg.conn.parser.resetParser() res = Element('success', {'xmlns': 'urn:ietf:params:xml:ns:xmpp-sasl'}) return res else: self._handleFailure() raise SASLAuthError else: self._handleFailure() raise SASLAuthError
def act(): d = msg.conn.data retVal = lastRetVal jid = d['user']['jid'] resource = d['user']['resource'] roster = Roster(jid) presTree = deepcopy(tree) presTree.set('from', '%s/%s' % (jid, resource)) probes = [] if tree.get('to') is None and not d['user']['active']: # initial presence # TODO: we don't need to do it every time. we can cache the # data after the first resource is active and just resend # that to all new resources d['user']['active'] = True # get jids of the contacts whose status we're interested in cjids = roster.getPresenceSubscriptions() probeTree = Element('presence', { 'type': 'probe', 'from' : '%s/%s' \ % (jid, resource) }) # TODO: replace this with a more efficient router handler for cjid in cjids: probeTree.set('to', cjid) probeRouteData = {'to': cjid, 'data': deepcopy(probeTree)} probes.append(probeRouteData) # they're sent first. see below # broadcast to other resources of this user retVal = self.broadcastToOtherResources( presTree, msg, retVal, jid, resource) elif tree.get('to') is not None: # TODO: directed presence return elif tree.get('type') == 'unavailable': # broadcast to other resources of this user d['user']['active'] = False retVal = self.broadcastToOtherResources( presTree, msg, retVal, jid, resource) # record this stanza as the last presence sent from this client lastPresence = deepcopy(tree) lastPresence.set('from', '%s/%s' % (jid, resource)) d['user']['lastPresence'] = lastPresence # lookup contacts interested in presence cjids = roster.getPresenceSubscribers() # TODO: replace this with another router handler that would send # it out to all cjids in a batch instead of queuing a handler # for each for cjid in cjids: presTree.set('to', cjid) presRouteData = {'to': cjid, 'data': deepcopy(presTree)} retVal = chainOutput(retVal, presRouteData) msg.setNextHandler('route-server') # send the probes first for probe in probes: msg.setNextHandler('route-server') retVal = chainOutput(retVal, probe) return retVal
def errorElement(self): return Element('temporary-auth-failure')