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): # 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 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(): # 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