def act(): mech = msg.conn.data['sasl']['mechObj'] if not mech: # TODO: close connection logging.warning("[%s] Mech object doesn't exist in connection data for %s", self.__class__, msg.conn.addr) logging.debug("[%s] %s", self.__class__, msg.conn.data) return text = tree.text if text: return chainOutput(lastRetVal, mech.handle(text.strip())) else: return chainOutput(lastRetVal, mech.handle(tree))
def act(): mech = msg.conn.data['sasl']['mechObj'] if not mech: # TODO: close connection logging.warning( "[%s] Mech object doesn't exist in connection data for %s", self.__class__, msg.conn.addr) logging.debug("[%s] %s", self.__class__, msg.conn.data) return text = tree.text if text: return chainOutput(lastRetVal, mech.handle(text.strip())) else: return chainOutput(lastRetVal, mech.handle(tree))
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): 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, 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 broadcastToOtherResources(self, tree, msg, lastRetVal, jid=None, resource=None): """Takes in the stanza to broadcast to other resources of this user and sets the next handler to route-server. Returns the lastRetVal with chained route-server handlers. tree -- tree to modify with the 'to' address and send out """ jid = jid or msg.conn.data['user']['jid'] resource = resource or msg.conn.data['user']['resource'] retVal = lastRetVal resources = msg.conn.server.data['resources'][jid] for r in resources: if r != resource: otherRes = jid + '/' + r tree.set('to', otherRes) presRouteData = {'to': otherRes, 'data': tree} retVal = chainOutput(lastRetVal, presRouteData) msg.setNextHandler('route-server') return retVal
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): # find connections of the user's JID try: jid = JID(tree.get('to')) except: logging.debug("[%s] Couldn't create a JID from %s", self.__class__, tree.get('to')) return jids = msg.conn.server.launcher.getC2SServer().data['resources'] resources = jids.get(jid.getBare()) if not resources: return lastPresences = [] for res, con in resources.items(): lp = con.data['user']['lastPresence'] if lp is not None: lpcopy = deepcopy(lp) # keep the last presence intact # modify the copy to include the 'to' attr for s2s routing lpcopy.set('to', tree.get('from')) lastPresences.append(lpcopy) if lastPresences: d = { 'to': tree.get('from'), 'data': lastPresences, } msg.setNextHandler('route-server') return chainOutput(lastRetVal, d)
def handle(self, tree, msg, lastRetVal=None): # TODO: continue with features, auth, etc. # for now, just assume it's localhost # forward any queued messages for this connection out = prepareDataForSending(msg.conn.outQueue) return chainOutput(lastRetVal, out)
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 broadcastToOtherResources(self, tree, msg, lastRetVal, jid=None, resource=None): """Takes in the stanza to broadcast to other resources of this user and sets the next handler to route-server. Returns the lastRetVal with chained route-server handlers. tree -- tree to modify with the 'to' address and send out """ jid = jid or msg.conn.data['user']['jid'] resource = resource or msg.conn.data['user']['resource'] retVal = lastRetVal resources = msg.conn.server.data['resources'][jid] for r in resources: if r != resource: otherRes = jid + '/' + r tree.set('to', otherRes) presRouteData = { 'to' : otherRes, 'data' : tree } retVal = chainOutput(lastRetVal, presRouteData) msg.setNextHandler('route-server') return retVal
def handle(self, tree, msg, lastRetVal=None): id = tree.get('id') if not id: logging.debug("[%s] No id specified in iq-auth get request", self.__class__) # check for policy violation violation = checkPolicyViolation(msg) if violation is not None: msg.setLastHandler('close-stream') return chainOutput(lastRetVal, violation) else: msg.conn.data['iqauth']['in-progress'] = True if id: iqAuthEl.set('id', id) return chainOutput(lastRetVal, iqAuthEl)
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): 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, tree, msg, lastRetVal=None): # find connections of the user's JID try: jid = JID(tree.get('to')) except: logging.debug("[%s] Couldn't create a JID from %s", self.__class__, tree.get('to')) return jids = msg.conn.server.launcher.getC2SServer().data['resources'] resources = jids.get(jid.getBare()) if not resources: return lastPresences = [] for res, con in resources.items(): lp = con.data['user']['lastPresence'] if lp is not None: lpcopy = deepcopy(lp) # keep the last presence intact # modify the copy to include the 'to' attr for s2s routing lpcopy.set('to', tree.get('from')) lastPresences.append(lpcopy) if lastPresences: d = { 'to' : tree.get('from'), 'data' : lastPresences, } msg.setNextHandler('route-server') return chainOutput(lastRetVal, d)
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 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 act(): data = msg.conn.data data['sasl']['in-progress'] = True mech = tree.get('mechanism') if mech == 'PLAIN': data['sasl']['mech'] = 'PLAIN' authtext64 = tree.text plain = mechs.SASLPlain(msg) data['sasl']['mechObj'] = plain return chainOutput(lastRetVal, plain.handle(authtext64)) elif mech == 'DIGEST-MD5': data['sasl']['mech'] = 'DIGEST-MD5' digest = mechs.SASLDigestMD5(msg) data['sasl']['mechObj'] = digest return chainOutput(lastRetVal, digest.handle()) else: logging.warning("[%s] Mechanism %s not implemented", self.__class__, mech)
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 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 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 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): 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 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): # we need to rewrite the "to" attribute of the <presence> # stanza for each resource of the user we send it to def rewriteTo(data, conn): jid = conn.data['user']['jid'] res = conn.data['user']['resource'] data.set('to', '%s/%s' % (jid, res)) return data logging.debug("[%s] Routing %s", self.__class__, tostring(tree)) d = {'to': tree.get('to'), 'data': tree, 'preprocessFunc': rewriteTo} msg.setNextHandler('route-client') return chainOutput(lastRetVal, d)
def act(): data = msg.conn.data # check for policy violation violation = checkPolicyViolation(msg) if violation is not None: msg.setLastHandler('close-stream') return chainOutput(lastRetVal, violation) id = tree.get('id') if not id: logging.debug("[%s] No id specified in iq-auth set request", self.__class__) data['iqauth']['in-progress'] = True username = tree[0].find('{jabber:iq:auth}username') if username is not None: username = username.text resource = tree[0].find('{jabber:iq:auth}resource') if resource is not None: resource = resource.text if username is None or resource is None: iq = makeNotAcceptable(id) return chainOutput(lastRetVal, iq) digest = tree[0].find('{jabber:iq:auth}digest') if digest is not None: digest = digest.text password = tree[0].find('{jabber:iq:auth}password') if password is not None: password = password.text if digest: data['iqauth']['mech'] = 'digest' auth = mechs.IQAuthDigest(msg) try: auth.handle(username, digest) except IQAuthError: return chainOutput(lastRetVal, makeNotAuthorized(id)) elif password: data['iqauth']['mech'] = 'plain' plain = mechs.IQAuthPlain(msg) try: plain.handle(username, password) except IQAuthError: return chainOutput(lastRetVal, makeNotAuthorized(id)) else: iq = makeNotAcceptable(id) return chainOutput(lastRetVal, iq) # do the resource binding # TODO: check that we don't already have such a resource bindResource(msg, resource) data['iqauth']['complete'] = True return chainOutput(lastRetVal, makeSuccess(id))
def handle(self, tree, msg, lastRetVal=None): # we need to rewrite the "to" attribute of the <presence> # stanza for each resource of the user we send it to def rewriteTo(data, conn): jid = conn.data['user']['jid'] res = conn.data['user']['resource'] data.set('to', '%s/%s' % (jid, res)) return data logging.debug("[%s] Routing %s", self.__class__, tostring(tree)) d = { 'to' : tree.get('to'), 'data' : tree, 'preprocessFunc' : rewriteTo } msg.setNextHandler('route-client') return chainOutput(lastRetVal, d)
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 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): # check that we have the to and from fields in the message and # just forward toJID = tree.get("to") jid = msg.conn.data["user"]["jid"] resource = msg.conn.data["user"]["resource"] try: toJID = JID(toJID) except: logging.debug("[%s] 'to' attribute in message not a real JID", self.__class__) return stampedTree = copy(tree) stampedTree.set("from", "%s/%s" % (jid, resource)) routeData = {"to": toJID.__str__(), "data": stampedTree} msg.setNextHandler("route-server") return chainOutput(lastRetVal, routeData)
def handle(self, tree, msg, lastRetVal=None): # check that we have the to and from fields in the message and # just forward toJID = tree.get('to') jid = msg.conn.data['user']['jid'] resource = msg.conn.data['user']['resource'] try: toJID = JID(toJID) except: logging.debug("[%s] 'to' attribute in message not a real JID", self.__class__) return stampedTree = copy(tree) stampedTree.set('from', '%s/%s' % (jid, resource)) routeData = {'to': toJID.__str__(), 'data': stampedTree} msg.setNextHandler('route-server') return chainOutput(lastRetVal, routeData)
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 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): # check that we have the to and from fields in the message and # just forward toJID = tree.get('to') jid = msg.conn.data['user']['jid'] resource = msg.conn.data['user']['resource'] try: toJID = JID(toJID) except: logging.debug("[%s] 'to' attribute in message not a real JID", self.__class__) return stampedTree = copy(tree) stampedTree.set('from', '%s/%s' % (jid, resource)) routeData = { 'to' : toJID.__str__(), 'data' : stampedTree } msg.setNextHandler('route-server') return chainOutput(lastRetVal, routeData)
def act(): # TODO: verify that it's coming from a known user jid = msg.conn.data['user']['jid'] 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 # RFC 3921 says in section 7.4 "an item", so we only handle the # first <item> item = tree[0][0] # iq -> query -> item cjid = item.get('jid') name = item.get('name') if cjid is None: logging.warning("[%s] Client trying to add a roster item " + \ "without a jid. Tree: %s", self.__class__, tree) # TODO: throw exception here return roster = Roster(jid) xpath = './{jabber:iq:roster}query/{jabber:iq:roster}item[@subscription="remove"]' if tree.find(xpath) is not None: # we're removing the roster item. See 3921 8.6 out = "<presence from='%s' to='%s' type='unsubscribe'/>" \ % (jid, cjid) out += "<presence from='%s' to='%s' type='unsubscribed'/>" \ % (jid, cjid) # create unavailable presence stanzas for all resources of the user resources = msg.conn.server.launcher.getC2SServer( ).data['resources'] jidForResources = resources.has_key(jid) and resources[jid] if jidForResources: for i in jidForResources: out += "<presence from='%s/%s'" % (jid, i) out += " to='%s' type='unavailable'/>" % cjid # prepare routing data d = {'to': cjid, 'data': out} query = deepcopy(tree[0]) retVal = chainOutput(lastRetVal, query) if roster.removeContact(cjid) is False: # We don't even have this contact in the roster anymore. # The contact is probably local (like ourselves). # This happens with some clients (like pidgin/gaim) who # cache the roster and don't delete some items even when # they're not present in the roster the server sends out # anymore. If we send the presence here it # will probably arrive after roster-push (due to s2s) # and will confuse the clients into thinking they still # have that contact in their roster. This creates an # undeletable contact. We can't do much about this. # If/when the s2s component can do a shortcut delivery of # stanzas to local users, while in the same phase, this # problem should go away, as it will allow the roster-push # to arrive after presences every time. pass # route the presence first, then do a roster push msg.setNextHandler('roster-push') msg.setNextHandler('route-server') return chainOutput(retVal, d) # we're updating/adding the roster item groups = [ i.text for i in list(item.findall('{jabber:iq:roster}group')) ] cid = roster.updateContact(cjid, groups, name) # get the subscription status before roster push sub = roster.getSubPrimaryName(cid) # prepare the result for roster push query = Roster.createRosterQuery(cjid, sub, name, groups) msg.setNextHandler('roster-push') return chainOutput(lastRetVal, query)
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)
class S2SMessageHandler(ThreadedHandler): """Handles <message>s coming in from remote servers""" def __init__(self): # this is true when the threaded handler returns self.done = False # used to pass the output to the next handler self.retVal = None def handle(self, tree, msg, lastRetVal=None): self.done = False self.retVal = lastRetVal tpool = msg.conn.server.threadpool def act(): cjid = tree.get('from') if not cjid: logging.debug("[%s] No 'from' attribute in <message> " + \ "stanza from server. Dropping: %s", self.__class__, tostring(tree)) return try: cjid = JID(cjid) except Exception, e: logging.debug("[%s] 'from' attribute in <message> not a " +\ "real JID: %s. Dropping: %s", self.__class__, cjid, tostring(tree)) return to = tree.get('to') if not to: logging.debug( "[%s] No 'to' attribute in <message> stanza from server", self.__class__) return try: to = JID(to) except Exception, e: logging.debug("[%s] 'to' attribute in <message> not a " +\ "real JID: %s. Dropping: %s", self.__class__, to, tostring(tree)) return if to.domain != msg.conn.server.hostname: logging.debug("[%s] <message> stanza recipient not handled " +\ "by this server: %s", self.__class__, tostring(msg)) return 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) if to.exists(): # user exists in the DB. check if they're online, # then forward to server conns = msg.conn.server.launcher.getC2SServer( ).data['resources'] toJID = to.getBare() # we may need to strip the resource if it's not available # and send to the bare JID modifiedTo = to.__str__() if conns.has_key(toJID) and conns[toJID]: # the user has one or more resources available if to.resource: # if sending to a specific resource, # check if it's available if not conns[toJID].has_key(to.resource): # resource is unavailable, so send to bare JID modifiedTo = toJID else: # user is unavailable, so send an error, unless # this message is an error already if tree.get('type') == 'error': return return makeServiceUnavailableError() routeData = {'to': modifiedTo, 'data': tree} msg.setNextHandler('route-client') return chainOutput(lastRetVal, routeData) else: # user does not exist # reply with <service-unavailable>. if tree.get('type') == 'error': # unless this message was an error itself return return makeServiceUnavailableError()
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 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 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 act(): # TODO: verify that it's coming from a known user jid = msg.conn.data['user']['jid'] 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 # RFC 3921 says in section 7.4 "an item", so we only handle the # first <item> item = tree[0][0] # iq -> query -> item cjid = item.get('jid') name = item.get('name') if cjid is None: logging.warning("[%s] Client trying to add a roster item " + \ "without a jid. Tree: %s", self.__class__, tree) # TODO: throw exception here return roster = Roster(jid) xpath = './{jabber:iq:roster}query/{jabber:iq:roster}item[@subscription="remove"]' if tree.find(xpath) is not None: # we're removing the roster item. See 3921 8.6 out = "<presence from='%s' to='%s' type='unsubscribe'/>" \ % (jid, cjid) out += "<presence from='%s' to='%s' type='unsubscribed'/>" \ % (jid, cjid) # create unavailable presence stanzas for all resources of the user resources = msg.conn.server.launcher.getC2SServer().data['resources'] jidForResources = resources.has_key(jid) and resources[jid] if jidForResources: for i in jidForResources: out += "<presence from='%s/%s'" % (jid, i) out += " to='%s' type='unavailable'/>" % cjid # prepare routing data d = { 'to' : cjid, 'data' : out } query = deepcopy(tree[0]) retVal = chainOutput(lastRetVal, query) if roster.removeContact(cjid) is False: # We don't even have this contact in the roster anymore. # The contact is probably local (like ourselves). # This happens with some clients (like pidgin/gaim) who # cache the roster and don't delete some items even when # they're not present in the roster the server sends out # anymore. If we send the presence here it # will probably arrive after roster-push (due to s2s) # and will confuse the clients into thinking they still # have that contact in their roster. This creates an # undeletable contact. We can't do much about this. # If/when the s2s component can do a shortcut delivery of # stanzas to local users, while in the same phase, this # problem should go away, as it will allow the roster-push # to arrive after presences every time. pass # route the presence first, then do a roster push msg.setNextHandler('roster-push') msg.setNextHandler('route-server') return chainOutput(retVal, d) # we're updating/adding the roster item groups = [i.text for i in list(item.findall('{jabber:iq:roster}group'))] cid = roster.updateContact(cjid, groups, name) # get the subscription status before roster push sub = roster.getSubPrimaryName(cid) # prepare the result for roster push query = Roster.createRosterQuery(cjid, sub, name, groups) msg.setNextHandler('roster-push') return chainOutput(lastRetVal, query)
def act(): # TODO: verify that it's coming from a known user jid = msg.conn.data['user']['jid'] cjid = JID(tree.get('to')) type = tree.get('type') if not cjid: logging.warning('[%s] No contact jid specified in subscription ' +\ 'query. Tree: %s', self.__class__, tree) # TODO: throw exception here return roster = Roster(jid) # get the RosterItem cinfo = roster.getContactInfo(cjid.getBare()) retVal = lastRetVal # C2S SUBSCRIBE if type == 'subscribe': # we must always route the subscribe presence so as to allow # the other servers to resynchronize their sub lists. # RFC 3921 9.2 if not cinfo: # contact doesn't exist, but according to RFC 3921 # section 8.2 bullet 4 we MUST create a new roster entry # for it with empty name and groups. roster.updateContact(cjid.getBare()) # now refetch the contact info cinfo = roster.getContactInfo(cjid.getBare()) cid = cinfo.id name = cinfo.name subscription = cinfo.subscription groups = cinfo.groups # update the subscription state if subscription == Subscription.NONE: roster.setSubscription(cid, Subscription.NONE_PENDING_OUT) subscription = Subscription.NONE_PENDING_OUT elif subscription == Subscription.NONE_PENDING_IN: roster.setSubscription(cid, Subscription.NONE_PENDING_IN_OUT) subscription = Subscription.NONE_PENDING_IN_OUT elif subscription == Subscription.FROM: roster.setSubscription(cid, Subscription.FROM_PENDING_OUT) subscription = Subscription.FROM_PENDING_OUT # send a roster push with ask query = Roster.createRosterQuery(cjid.getBare(), Subscription.getPrimaryNameFromState(subscription), name, groups, {'ask' : 'subscribe'}) # stamp presence with 'from' JID treeCopy = deepcopy(tree) treeCopy.set('from', jid) # prepare the presence data for routing d = { 'to' : cjid, 'data' : treeCopy, } retVal = chainOutput(retVal, d) # sequence of events in reverse order # push the roster first, in case we have to create a new # s2s connection msg.setNextHandler('route-server') msg.setNextHandler('roster-push') return chainOutput(retVal, query) # C2S SUBSCRIBED elif type == 'subscribed': if not cinfo: logging.warning("[%s] 'subscribed' presence received for " +\ "non-existent contact %s", self.__class__, cjid) else: subscription = cinfo.subscription if cinfo.subscription in (Subscription.NONE_PENDING_IN, Subscription.NONE_PENDING_IN_OUT, Subscription.TO_PENDING_IN): # update state and deliver if cinfo.subscription == Subscription.NONE_PENDING_IN: roster.setSubscription(cinfo.id, Subscription.FROM) subscription = Subscription.FROM elif cinfo.subscription == Subscription.NONE_PENDING_IN_OUT: roster.setSubscription(cinfo.id, Subscription.FROM_PENDING_OUT) subscription = Subscription.FROM_PENDING_OUT elif cinfo.subscription == Subscription.TO_PENDING_IN: roster.setSubscription(cinfo.id, Subscription.BOTH) subscription = Subscription.BOTH # roster stanza query = Roster.createRosterQuery(cjid.getBare(), Subscription.getPrimaryNameFromState(subscription), cinfo.name, cinfo.groups) # stamp presence with 'from' treeCopy = deepcopy(tree) treeCopy.set('from', jid) toRoute = tostring(treeCopy) # create available presence stanzas for all resources of the user resources = msg.conn.server.launcher.getC2SServer().data['resources'] jidForResources = resources.has_key(jid) and resources[jid] if jidForResources: out = u'' for i in jidForResources: out += "<presence from='%s/%s'" % (jid, i) out += " to='%s'/>" % cjid.getBare() # and queue for routing toRoute += out # prepare the presence data for routing d = { 'to' : cjid, 'data' : toRoute, } retVal = chainOutput(retVal, d) # next handlers in reverse order msg.setNextHandler('route-server') msg.setNextHandler('roster-push') return chainOutput(retVal, query) # C2S UNSUBSCRIBE elif type == 'unsubscribe': # we must always route the unsubscribe presence so as to allow # the other servers to resynchronize their sub lists. # RFC 3921 9.2 if not cinfo: # we don't have this contact in our roster, but route the # presence anyway # stamp presence with 'from' treeCopy = deepcopy(tree) treeCopy.set('from', jid) # prepare the presence data for routing d = { 'to' : cjid, 'data' : treeCopy, } msg.setNextHandler('route-server') return chainOutput(retVal, d) else: subscription = cinfo.subscription if subscription == Subscription.BOTH: # mutual roster.setSubscription(cinfo.id, Subscription.FROM) subscription = Subscription.FROM elif subscription in (Subscription.NONE_PENDING_OUT, # one way Subscription.NONE_PENDING_IN_OUT, Subscription.TO, Subscription.TO_PENDING_IN): if subscription == Subscription.NONE_PENDING_OUT \ or subscription == Subscription.TO: roster.setSubscription(cinfo.id, Subscription.NONE) subscription = Subscription.NONE elif subscription == Subscription.NONE_PENDING_IN_OUT \ or subscription == Subscription.TO_PENDING_IN: roster.setSubscription(cinfo.id, Subscription.NONE_PENDING_IN) subscription = Subscription.NONE_PENDING_IN # roster stanza query = Roster.createRosterQuery(cjid.getBare(), Subscription.getPrimaryNameFromState(subscription), cinfo.name, cinfo.groups) # stamp presence with 'from' treeCopy = deepcopy(tree) treeCopy.set('from', jid) # prepare the presence data for routing d = { 'to' : cjid, 'data' : treeCopy, } retVal = chainOutput(retVal, d) # schedules handlers in reverse order msg.setNextHandler('route-server') msg.setNextHandler('roster-push') return chainOutput(retVal, query) # C2S UNSUBSCRIBED elif type == 'unsubscribed': if not cinfo: logging.warning("[%s] 'unsubscribed' presence received for " +\ "non-existent contact %s", self.__class__, cjid) else: subscription = cinfo.subscription if subscription not in (Subscription.NONE, Subscription.NONE_PENDING_OUT, Subscription.TO): if subscription == Subscription.NONE_PENDING_IN \ or subscription == Subscription.FROM: roster.setSubscription(cinfo.id, Subscription.NONE) subscription = Subscription.NONE elif subscription == Subscription.NONE_PENDING_IN_OUT \ or subscription == Subscription.FROM_PENDING_OUT: roster.setSubscription(cinfo.id, Subscription.NONE_PENDING_OUT) subscription = Subscription.NONE elif subscription == Subscription.TO_PENDING_IN \ or subscription == Subscription.BOTH: roster.setSubscription(cinfo.id, Subscription.TO) subscription = Subscription.TO # roster query if subscription == Subscription.NONE_PENDING_OUT: itemArgs = {'ask' : 'subscribe'} else: itemArgs = {} query = roster.createRosterQuery(cjid.getBare(), Subscription.getPrimaryNameFromState(subscription), cinfo.name, cinfo.groups, itemArgs) # stamp presence with 'from' treeCopy = deepcopy(tree) treeCopy.set('from', jid) toRoute = tostring(treeCopy) # create unavailable presence stanzas for all resources of the user resources = msg.conn.server.launcher.getC2SServer().data['resources'] jidForResources = resources.has_key(jid) and resources[jid] if jidForResources: out = u'' for i in jidForResources: out += "<presence from='%s/%s'" % (jid, i) out += " to='%s' type='unavailable'/>" % cjid.getBare() # and add to output toRoute += out # prepare the presence data for routing d = { 'to' : cjid, 'data' : toRoute, } retVal = chainOutput(retVal, d) # handlers in reverse order msg.setNextHandler('route-server') msg.setNextHandler('roster-push') return chainOutput(retVal, query)
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 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)
class S2SSubscriptionHandler(ThreadedHandler): """Handles subscriptions sent from servers within <presence> stanzas. ie. <presence> elements with types. """ def __init__(self): # this is true when the threaded handler returns self.done = False # used to pass the output to the next handler self.retVal = None def handle(self, tree, msg, lastRetVal=None): self.done = False self.retVal = lastRetVal tpool = msg.conn.server.threadpool def act(): # get the contact's jid fromAddr = tree.get('from') try: cjid = JID(fromAddr) except Exception, e: logging.warning( "[%s] 'from' JID is not properly formatted. Tree: %s", self.__class__, tostring(tree)) return # get the user's jid toAddr = tree.get('to') try: jid = JID(toAddr) except Exception, e: logging.warning( "[%s] 'to' JID is not properly formatted. Tree: %s", self.__class__, tostring(tree)) return roster = Roster(jid.getBare()) doRoute = False cinfo = roster.getContactInfo(cjid.getBare()) subType = tree.get('type') retVal = lastRetVal # S2S SUBSCRIBE if subType == 'subscribe': if not cinfo: # contact doesn't exist, so it's a first-time add # need to add the contact with subscription None + Pending In roster.updateContact(cjid.getBare(), None, None, Subscription.NONE_PENDING_IN) cinfo = roster.getContactInfo(cjid.getBare()) doRoute = True if cinfo.subscription in (Subscription.NONE, Subscription.NONE_PENDING_OUT, Subscription.TO): # change state if cinfo.subscription == Subscription.NONE: roster.setSubscription(cinfo.id, Subscription.NONE_PENDING_IN) elif cinfo.subscription == Subscription.NONE_PENDING_OUT: roster.setSubscription( cinfo.id, Subscription.NONE_PENDING_IN_OUT) elif cinfo.subscription == Subscription.TO: roster.setSubscription(cinfo.id, Subscription.TO_PENDING_IN) doRoute = True elif cinfo.subscription in (Subscription.FROM, Subscription.FROM_PENDING_OUT, Subscription.BOTH): # auto-reply with "subscribed" stanza doRoute = False out = "<presence to='%s' from='%s' type='subscribed'/>" % ( cjid.getBare(), jid.getBare()) # prepare the data for routing subscribedRouting = { 'to': cjid.getBare(), 'data': out, } retVal = chainOutput(retVal, subscribedRouting) msg.setNextHandler('route-server') # ignore presence in other states if doRoute: # queue the stanza for delivery stanzaRouting = {'to': jid, 'data': tree} retVal = chainOutput(retVal, stanzaRouting) msg.setNextHandler('route-client') return retVal # S2S SUBSCRIBED elif subType == 'subscribed': if cinfo: subscription = cinfo.subscription if cinfo.subscription in (Subscription.NONE_PENDING_OUT, Subscription.NONE_PENDING_IN_OUT, Subscription.FROM_PENDING_OUT): # change state if cinfo.subscription == Subscription.NONE_PENDING_OUT: roster.setSubscription(cinfo.id, Subscription.TO) subscription = Subscription.TO elif cinfo.subscription == Subscription.NONE_PENDING_IN_OUT: roster.setSubscription(cinfo.id, Subscription.TO_PENDING_IN) subscription = Subscription.TO_PENDING_IN elif cinfo.subscription == Subscription.FROM_PENDING_OUT: roster.setSubscription(cinfo.id, Subscription.BOTH) subscription = Subscription.BOTH # forward the subscribed presence # prepare the presence data for routing d = { 'to': jid, 'data': tree, } retVal = chainOutput(retVal, d) # create an updated roster item for roster push query = Roster.createRosterQuery( cinfo.jid, Subscription.getPrimaryNameFromState(subscription), cinfo.name, cinfo.groups) routeData = {} conns = msg.conn.server.launcher.getC2SServer( ).data['resources'] bareJID = jid.getBare() if conns.has_key(bareJID): routeData['jid'] = bareJID routeData['resources'] = conns[bareJID] # next handlers (reverse order) msg.setNextHandler('route-client') msg.setNextHandler('roster-push') return chainOutput(retVal, (routeData, query)) # S2S UNSUBSCRIBE elif subType == 'unsubscribe': if cinfo: subscription = cinfo.subscription if subscription not in (Subscription.NONE, Subscription.NONE_PENDING_OUT, Subscription.TO): if subscription == Subscription.NONE_PENDING_IN \ or subscription == Subscription.FROM: roster.setSubscription(cinfo.id, Subscription.NONE) subscription = Subscription.NONE elif subscription == Subscription.NONE_PENDING_IN_OUT \ or subscription == Subscription.FROM_PENDING_OUT: roster.setSubscription( cinfo.id, Subscription.NONE_PENDING_OUT) subscription = Subscription.NONE_PENDING_OUT elif subscription == Subscription.TO_PENDING_IN \ or subscription == Subscription.BOTH: roster.setSubscription(cinfo.id, Subscription.TO) subscription = Subscription.TO # these steps are really in reverse order due to handler queuing # send unavailable presence from all resources resources = msg.conn.server.launcher.getC2SServer( ).data['resources'] bareJID = jid.getBare() jidForResources = resources.has_key( bareJID) and resources[bareJID] if jidForResources: out = u'' for i in jidForResources: out += "<presence from='%s/%s'" % (bareJID, i) out += " to='%s' type='unavailable'/>" % cjid.getBare( ) # and route it unavailableRouting = {'to': cjid, 'data': out} retVal = chainOutput(retVal, unavailableRouting) # 4. route the unavailable presence back to server msg.setNextHandler('route-server') # auto-reply with "unsubscribed" stanza out = "<presence to='%s' from='%s' type='unsubscribed'/>" % ( cjid.getBare(), jid.getBare()) unsubscribedRouting = { 'to': jid.getBare(), 'data': out } retVal = chainOutput(retVal, unsubscribedRouting) # prepare the unsubscribe presence data for routing to client unsubscribeRouting = { 'to': jid, 'data': tree, } retVal = chainOutput(retVal, unsubscribeRouting) # create an updated roster item for roster push # we should really create add an ask='subscribe' for # the NONE_PENDING_OUT state, but the spec doesn't # say anything about this, so leave it out for now. query = Roster.createRosterQuery( cinfo.jid, Subscription.getPrimaryNameFromState(subscription), cinfo.name, cinfo.groups) # needed for S2S roster push routeData = {} conns = msg.conn.server.launcher.getC2SServer( ).data['resources'] bareJID = jid.getBare() if conns.has_key(bareJID): routeData['jid'] = bareJID routeData['resources'] = conns[bareJID] # handlers in reverse order. actual order: # 1. push the updated roster # 2. route the unsubscribe presence to client # 3. route the unsubscribed presence back to server # 4. see above. it's optional, since no resources could # be online by this point msg.setNextHandler('route-server') msg.setNextHandler('route-client') msg.setNextHandler('roster-push') return chainOutput(retVal, (routeData, query)) # S2S UNSUBSCRIBED elif subType == 'unsubscribed': if cinfo: subscription = cinfo.subscription if subscription not in (Subscription.NONE, Subscription.NONE_PENDING_IN, Subscription.FROM): # change state if subscription == Subscription.NONE_PENDING_OUT \ or subscription == Subscription.TO: roster.setSubscription(cinfo.id, Subscription.NONE) subscription = Subscription.NONE elif subscription == Subscription.NONE_PENDING_IN_OUT \ or subscription == Subscription.TO_PENDING_IN: roster.setSubscription( cinfo.id, Subscription.NONE_PENDING_IN) subscription = Subscription.NONE_PENDING_IN elif subscription == Subscription.FROM_PENDING_OUT \ or subscription == Subscription.BOTH: roster.setSubscription(cinfo.id, Subscription.FROM) subscription = Subscription.FROM # prepare the unsubscribed presence data for routing d = { 'to': jid, 'data': tree, } retVal = chainOutput(retVal, d) # create an updated roster item for roster push query = Roster.createRosterQuery( cinfo.jid, Subscription.getPrimaryNameFromState(subscription), cinfo.name, cinfo.groups) # needed for S2S roster push routeData = {} conns = msg.conn.server.launcher.getC2SServer( ).data['resources'] bareJID = jid.getBare() if conns.has_key(bareJID): routeData['jid'] = bareJID routeData['resources'] = conns[bareJID] # handlers in reverse order # actually: push roster first, then route presence msg.setNextHandler('route-client') msg.setNextHandler('roster-push') return chainOutput(retVal, (routeData, query))
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(): # TODO: verify that it's coming from a known user jid = msg.conn.data['user']['jid'] cjid = JID(tree.get('to')) type = tree.get('type') if not cjid: logging.warning('[%s] No contact jid specified in subscription ' +\ 'query. Tree: %s', self.__class__, tree) # TODO: throw exception here return roster = Roster(jid) # get the RosterItem cinfo = roster.getContactInfo(cjid.getBare()) retVal = lastRetVal # C2S SUBSCRIBE if type == 'subscribe': # we must always route the subscribe presence so as to allow # the other servers to resynchronize their sub lists. # RFC 3921 9.2 if not cinfo: # contact doesn't exist, but according to RFC 3921 # section 8.2 bullet 4 we MUST create a new roster entry # for it with empty name and groups. roster.updateContact(cjid.getBare()) # now refetch the contact info cinfo = roster.getContactInfo(cjid.getBare()) cid = cinfo.id name = cinfo.name subscription = cinfo.subscription groups = cinfo.groups # update the subscription state if subscription == Subscription.NONE: roster.setSubscription(cid, Subscription.NONE_PENDING_OUT) subscription = Subscription.NONE_PENDING_OUT elif subscription == Subscription.NONE_PENDING_IN: roster.setSubscription(cid, Subscription.NONE_PENDING_IN_OUT) subscription = Subscription.NONE_PENDING_IN_OUT elif subscription == Subscription.FROM: roster.setSubscription(cid, Subscription.FROM_PENDING_OUT) subscription = Subscription.FROM_PENDING_OUT # send a roster push with ask query = Roster.createRosterQuery( cjid.getBare(), Subscription.getPrimaryNameFromState(subscription), name, groups, {'ask': 'subscribe'}) # stamp presence with 'from' JID treeCopy = deepcopy(tree) treeCopy.set('from', jid) # prepare the presence data for routing d = { 'to': cjid, 'data': treeCopy, } retVal = chainOutput(retVal, d) # sequence of events in reverse order # push the roster first, in case we have to create a new # s2s connection msg.setNextHandler('route-server') msg.setNextHandler('roster-push') return chainOutput(retVal, query) # C2S SUBSCRIBED elif type == 'subscribed': if not cinfo: logging.warning("[%s] 'subscribed' presence received for " +\ "non-existent contact %s", self.__class__, cjid) else: subscription = cinfo.subscription if cinfo.subscription in (Subscription.NONE_PENDING_IN, Subscription.NONE_PENDING_IN_OUT, Subscription.TO_PENDING_IN): # update state and deliver if cinfo.subscription == Subscription.NONE_PENDING_IN: roster.setSubscription(cinfo.id, Subscription.FROM) subscription = Subscription.FROM elif cinfo.subscription == Subscription.NONE_PENDING_IN_OUT: roster.setSubscription( cinfo.id, Subscription.FROM_PENDING_OUT) subscription = Subscription.FROM_PENDING_OUT elif cinfo.subscription == Subscription.TO_PENDING_IN: roster.setSubscription(cinfo.id, Subscription.BOTH) subscription = Subscription.BOTH # roster stanza query = Roster.createRosterQuery( cjid.getBare(), Subscription.getPrimaryNameFromState(subscription), cinfo.name, cinfo.groups) # stamp presence with 'from' treeCopy = deepcopy(tree) treeCopy.set('from', jid) toRoute = tostring(treeCopy) # create available presence stanzas for all resources of the user resources = msg.conn.server.launcher.getC2SServer( ).data['resources'] jidForResources = resources.has_key( jid) and resources[jid] if jidForResources: out = u'' for i in jidForResources: out += "<presence from='%s/%s'" % (jid, i) out += " to='%s'/>" % cjid.getBare() # and queue for routing toRoute += out # prepare the presence data for routing d = { 'to': cjid, 'data': toRoute, } retVal = chainOutput(retVal, d) # next handlers in reverse order msg.setNextHandler('route-server') msg.setNextHandler('roster-push') return chainOutput(retVal, query) # C2S UNSUBSCRIBE elif type == 'unsubscribe': # we must always route the unsubscribe presence so as to allow # the other servers to resynchronize their sub lists. # RFC 3921 9.2 if not cinfo: # we don't have this contact in our roster, but route the # presence anyway # stamp presence with 'from' treeCopy = deepcopy(tree) treeCopy.set('from', jid) # prepare the presence data for routing d = { 'to': cjid, 'data': treeCopy, } msg.setNextHandler('route-server') return chainOutput(retVal, d) else: subscription = cinfo.subscription if subscription == Subscription.BOTH: # mutual roster.setSubscription(cinfo.id, Subscription.FROM) subscription = Subscription.FROM elif subscription in ( Subscription.NONE_PENDING_OUT, # one way Subscription.NONE_PENDING_IN_OUT, Subscription.TO, Subscription.TO_PENDING_IN): if subscription == Subscription.NONE_PENDING_OUT \ or subscription == Subscription.TO: roster.setSubscription(cinfo.id, Subscription.NONE) subscription = Subscription.NONE elif subscription == Subscription.NONE_PENDING_IN_OUT \ or subscription == Subscription.TO_PENDING_IN: roster.setSubscription( cinfo.id, Subscription.NONE_PENDING_IN) subscription = Subscription.NONE_PENDING_IN # roster stanza query = Roster.createRosterQuery( cjid.getBare(), Subscription.getPrimaryNameFromState(subscription), cinfo.name, cinfo.groups) # stamp presence with 'from' treeCopy = deepcopy(tree) treeCopy.set('from', jid) # prepare the presence data for routing d = { 'to': cjid, 'data': treeCopy, } retVal = chainOutput(retVal, d) # schedules handlers in reverse order msg.setNextHandler('route-server') msg.setNextHandler('roster-push') return chainOutput(retVal, query) # C2S UNSUBSCRIBED elif type == 'unsubscribed': if not cinfo: logging.warning("[%s] 'unsubscribed' presence received for " +\ "non-existent contact %s", self.__class__, cjid) else: subscription = cinfo.subscription if subscription not in (Subscription.NONE, Subscription.NONE_PENDING_OUT, Subscription.TO): if subscription == Subscription.NONE_PENDING_IN \ or subscription == Subscription.FROM: roster.setSubscription(cinfo.id, Subscription.NONE) subscription = Subscription.NONE elif subscription == Subscription.NONE_PENDING_IN_OUT \ or subscription == Subscription.FROM_PENDING_OUT: roster.setSubscription( cinfo.id, Subscription.NONE_PENDING_OUT) subscription = Subscription.NONE elif subscription == Subscription.TO_PENDING_IN \ or subscription == Subscription.BOTH: roster.setSubscription(cinfo.id, Subscription.TO) subscription = Subscription.TO # roster query if subscription == Subscription.NONE_PENDING_OUT: itemArgs = {'ask': 'subscribe'} else: itemArgs = {} query = roster.createRosterQuery( cjid.getBare(), Subscription.getPrimaryNameFromState(subscription), cinfo.name, cinfo.groups, itemArgs) # stamp presence with 'from' treeCopy = deepcopy(tree) treeCopy.set('from', jid) toRoute = tostring(treeCopy) # create unavailable presence stanzas for all resources of the user resources = msg.conn.server.launcher.getC2SServer( ).data['resources'] jidForResources = resources.has_key( jid) and resources[jid] if jidForResources: out = u'' for i in jidForResources: out += "<presence from='%s/%s'" % (jid, i) out += " to='%s' type='unavailable'/>" % cjid.getBare( ) # and add to output toRoute += out # prepare the presence data for routing d = { 'to': cjid, 'data': toRoute, } retVal = chainOutput(retVal, d) # handlers in reverse order msg.setNextHandler('route-server') msg.setNextHandler('roster-push') return chainOutput(retVal, query)
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