def roster(self, stanza): # enforce destination (resolver) stanza['to'] = self.parent.network if not xmlstream2.has_element(stanza.query, uri=xmlstream2.NS_IQ_ROSTER, name='item'): # requesting initial roster - enter XMPP compatibility mode self.parent.compatibility_mode = True # forward to resolver self.parent.forward(stanza)
def roster(self, stanza): if not xmlstream2.has_element( stanza.query, uri=xmlstream2.NS_IQ_ROSTER, name='item'): # requesting initial roster - enter XMPP compatibility mode self.parent.compatibility_mode = True _items = stanza.query.elements(uri=xmlstream2.NS_IQ_ROSTER, name='item') requester = jid.JID(stanza['from']) stanza.consumed = True # items present, requesting roster lookup response = xmlstream.toResponse(stanza, 'result') roster = response.addElement((xmlstream2.NS_IQ_ROSTER, 'query')) probes = [] # this will be true if roster lookup is requested roster_lookup = False for item in _items: # items present, meaning roster lookup roster_lookup = True itemJid = jid.internJID(item['jid']) # include the entry in the roster reply anyway entry = self.parent.router.cache.lookup(itemJid) if entry: allowed = self.parent.router.is_presence_allowed( requester, itemJid) if allowed != -1: item = roster.addElement((None, 'item')) item['jid'] = self.parent.router.translateJID( entry.jid).userhost() if allowed == 1: probes.append(entry.presence()) # roster lookup, send presence data and vcards if roster_lookup: # lookup response self.send(response) # simulate a presence probe and send vcards # we'll use one group ID so the client knows when to stop waiting gid = stanza.getAttribute('id') if not gid: gid = util.rand_str(8, util.CHARSBOX_AZN_LOWERCASE) i = sum([len(x) for x in probes]) for presence_list in probes: for presence in presence_list: presence = deepcopy(presence) presence['to'] = stanza['from'] group = presence.addElement( (xmlstream2.NS_XMPP_STANZA_GROUP, 'group')) group['id'] = gid group['count'] = str(i) i -= 1 self.send(presence) # send vcard for this user jid_from = jid.JID(presence_list[0]['from']) iq = domish.Element((None, 'iq')) iq['type'] = 'set' iq['from'] = jid_from.userhost() iq['to'] = stanza['from'] try: self.parent.router.build_vcard(jid_from.user, iq) self.send(iq) except keyring.KeyNotFoundException: pass # no roster lookup, XMPP standard roster instead else: # include items from the user's whitelist wl = self.parent.router.get_whitelist(requester) probes = None if wl: subscriptions = [] for e in wl: item = roster.addElement((None, 'item')) item['jid'] = e itemJid = jid.JID(e) # check if subscription status is 'both' or just 'from' allowed = self.parent.router.is_presence_allowed( requester, itemJid) if allowed == 1: status = 'both' else: status = 'from' # TODO include name from PGP key? item['subscription'] = status # add to subscription list subscriptions.append(itemJid) # send the roster self.send(response) # subscribe to all users (without sending subscribed stanza of course) if wl: for itemJid in subscriptions: self.parent.router.subscribe(requester, itemJid, send_subscribed=False)
def process_message(self, stanza, hold=False): if stanza.hasAttribute('to'): to = jid.JID(stanza['to']) # process only our JIDs if util.jid_local(util.COMPONENT_C2S, self, to): chat_msg = (stanza.getAttribute('type') == 'chat') if to.user is not None: keepId = None receipt = xmlstream2.extract_receipt(stanza, 'request') received = xmlstream2.extract_receipt(stanza, 'received') has_storage = xmlstream2.has_element( stanza, xmlstream2.NS_XMPP_STORAGE, 'storage') try: """ We are deliberately ignoring messages with sent receipt because they are supposed to be volatile. """ if chat_msg and not has_storage and (receipt or received): """ Apply generated id if we are getting a received receipt. This way stanza is received by the client with the correct id to cancel preemptive storage. """ if received: keepId = stanza['id'] = util.rand_str( 30, util.CHARSBOX_AZN_LOWERCASE) # send message to offline storage just to be safe (delayed) keepId = self.message_offline_store(stanza, delayed=True, reuseId=keepId) if hold: raise Exception() # send message to sm only to non-negative resources log.debug("sending message %s" % (stanza['id'], )) self.sfactory.dispatch(stanza) except: # manager not found or holding -- send to offline storage if hold: log.debug("holding stanza for %s" % (stanza['to'], )) else: log.debug("c2s manager for %s not found" % (stanza['to'], )) """ Since our previous call to message_offline_store() was with delayed parameter, we need to store for real now. Do not store messages from local storage because """ if chat_msg and not has_storage and (stanza.body or stanza.e2e or received): self.message_offline_store(stanza, delayed=False, reuseId=keepId) if self.push_manager and chat_msg and ( stanza.body or stanza.e2e) and ( not receipt or receipt.name == 'request'): self.push_manager.notify(to) # if message is a received receipt, we can delete the original message if chat_msg and received: # delete the received message # TODO safe delete with sender/recipient self.message_offline_delete(received['id'], stanza.name) stamp = time.time() """ Receipts will be sent only if message is not coming from storage or message is from a remote server. This is because if the message is coming from storage, it means that it's a user collecting its offline messages, so we don't need to send a <sent/> again. If a message is coming from a remote server, it means that is being delivered by a remote c2s by either: * sm request (direct message from client) * offline delivery (triggered by an initial presence from this server) """ host = util.jid_host(stanza['from']) from_storage = xmlstream2.has_element( stanza, xmlstream2.NS_XMPP_STORAGE, 'storage') try: log.debug("host(unparsed): %s" % (host, )) unused, host = util.jid_component( host, util.COMPONENT_C2S) log.debug("host(parsed): %s" % (host, )) from_remote = host != self.servername except: from_remote = False if chat_msg and (not from_storage or from_remote): # send ack only for chat messages (if requested) # do not send if coming from remote storage if receipt and not from_storage: self.send_ack(stanza, 'sent', stamp) # send receipt to originating server, if requested receipt = None # receipt request: send <sent/> if stanza.request: receipt = stanza.request request = 'request' delivery = 'sent' # received receipt: send <ack/> elif stanza.received: receipt = stanza.received request = 'received' delivery = 'ack' # now send what we prepared if receipt: try: from_server = receipt['from'] if not util.hostjid_local( util.COMPONENT_C2S, self, from_server): stanza['from'] = from_server self.send_ack(stanza, delivery, stamp, request) except KeyError: pass else: # deliver local stanza self.local(stanza) """ If message is a receipt coming from a remote server, delete the message from our storage. """ r_sent = xmlstream2.extract_receipt(stanza, 'sent') if chat_msg and r_sent: sender_host = util.jid_host(stanza['from']) """ We are receiving a sent receipt from another server, meaning that the server has now responsibility for the message - we can delete it now. Special case is the sender domain being the network domain, meaning the resolver rejected the message. """ unused, sender_host = util.jid_component(sender_host) if sender_host != self.servername: log.debug( "remote server now has responsibility for message %s - deleting" % (r_sent['id'], )) # TODO safe delete with sender/recipient self.message_offline_delete(r_sent['id'], stanza.name) else: log.debug("stanza is not our concern or is an error")
def process_message(self, stanza, hold=False): if stanza.hasAttribute('to'): to = jid.JID(stanza['to']) # process only our JIDs if util.jid_local(util.COMPONENT_C2S, self, to): chat_msg = (stanza.getAttribute('type') == 'chat') if to.user is not None: keepId = None receipt = xmlstream2.extract_receipt(stanza, 'request') received = xmlstream2.extract_receipt(stanza, 'received') has_storage = xmlstream2.has_element(stanza, xmlstream2.NS_XMPP_STORAGE, 'storage') try: """ We are deliberately ignoring messages with sent receipt because they are supposed to be volatile. """ if chat_msg and not has_storage and (receipt or received): """ Apply generated id if we are getting a received receipt. This way stanza is received by the client with the correct id to cancel preemptive storage. """ if received: keepId = stanza['id'] = util.rand_str(30, util.CHARSBOX_AZN_LOWERCASE) # send message to offline storage just to be safe (delayed) keepId = self.message_offline_store(stanza, delayed=True, reuseId=keepId) if hold: raise Exception() # send message to sm only to non-negative resources log.debug("sending message %s" % (stanza['id'], )) self.sfactory.dispatch(stanza) except: # manager not found or holding -- send to offline storage if hold: log.debug("holding stanza for %s" % (stanza['to'], )) else: log.debug("c2s manager for %s not found" % (stanza['to'], )) """ Since our previous call to message_offline_store() was with delayed parameter, we need to store for real now. Do not store messages from local storage because """ if chat_msg and not has_storage and (stanza.body or stanza.e2e or received): self.message_offline_store(stanza, delayed=False, reuseId=keepId) if self.push_manager and chat_msg and (stanza.body or stanza.e2e) and (not receipt or receipt.name == 'request'): self.push_manager.notify(to) # if message is a received receipt, we can delete the original message if chat_msg and received: # delete the received message # TODO safe delete with sender/recipient self.message_offline_delete(received['id'], stanza.name) stamp = time.time() """ Receipts will be sent only if message is not coming from storage or message is from a remote server. This is because if the message is coming from storage, it means that it's a user collecting its offline messages, so we don't need to send a <sent/> again. If a message is coming from a remote server, it means that is being delivered by a remote c2s by either: * sm request (direct message from client) * offline delivery (triggered by an initial presence from this server) """ host = util.jid_host(stanza['from']) from_storage = xmlstream2.has_element(stanza, xmlstream2.NS_XMPP_STORAGE, 'storage') try: log.debug("host(unparsed): %s" % (host, )) unused, host = util.jid_component(host, util.COMPONENT_C2S) log.debug("host(parsed): %s" % (host, )) from_remote = host != self.servername except: from_remote = False if chat_msg and (not from_storage or from_remote): # send ack only for chat messages (if requested) # do not send if coming from remote storage if receipt and not from_storage: self.send_ack(stanza, 'sent', stamp) # send receipt to originating server, if requested receipt = None # receipt request: send <sent/> if stanza.request: receipt = stanza.request request = 'request' delivery = 'sent' # received receipt: send <ack/> elif stanza.received: receipt = stanza.received request = 'received' delivery = 'ack' # now send what we prepared if receipt: try: from_server = receipt['from'] if not util.hostjid_local(util.COMPONENT_C2S, self, from_server): stanza['from'] = from_server self.send_ack(stanza, delivery, stamp, request) except KeyError: pass else: # deliver local stanza self.local(stanza) """ If message is a receipt coming from a remote server, delete the message from our storage. """ r_sent = xmlstream2.extract_receipt(stanza, 'sent') if chat_msg and r_sent: sender_host = util.jid_host(stanza['from']) """ We are receiving a sent receipt from another server, meaning that the server has now responsibility for the message - we can delete it now. Special case is the sender domain being the network domain, meaning the resolver rejected the message. """ unused, sender_host = util.jid_component(sender_host) if sender_host != self.servername: log.debug("remote server now has responsibility for message %s - deleting" % (r_sent['id'], )) # TODO safe delete with sender/recipient self.message_offline_delete(r_sent['id'], stanza.name) else: log.debug("stanza is not our concern or is an error")
def roster(self, stanza): if not xmlstream2.has_element(stanza.query, uri=xmlstream2.NS_IQ_ROSTER, name="item"): # requesting initial roster - enter XMPP compatibility mode self.parent.compatibility_mode = True _items = stanza.query.elements(uri=xmlstream2.NS_IQ_ROSTER, name="item") requester = jid.JID(stanza["from"]) stanza.consumed = True # items present, requesting roster lookup response = xmlstream.toResponse(stanza, "result") roster = response.addElement((xmlstream2.NS_IQ_ROSTER, "query")) probes = [] # this will be true if roster lookup is requested roster_lookup = False for item in _items: # items present, meaning roster lookup roster_lookup = True itemJid = jid.internJID(item["jid"]) # include the entry in the roster reply anyway entry = self.parent.router.cache.lookup(itemJid) if entry: allowed = self.parent.router.is_presence_allowed(requester, itemJid) if allowed != -1: item = roster.addElement((None, "item")) item["jid"] = self.parent.router.translateJID(entry.jid).userhost() if allowed == 1: probes.append(entry.presence()) # roster lookup, send presence data and vcards if roster_lookup: # lookup response self.send(response) # simulate a presence probe and send vcards # we'll use one group ID so the client knows when to stop waiting gid = stanza.getAttribute("id") if not gid: gid = util.rand_str(8, util.CHARSBOX_AZN_LOWERCASE) i = sum([len(x) for x in probes]) for presence_list in probes: for presence in presence_list: presence = deepcopy(presence) presence["to"] = stanza["from"] group = presence.addElement((xmlstream2.NS_XMPP_STANZA_GROUP, "group")) group["id"] = gid group["count"] = str(i) i -= 1 self.send(presence) # send vcard for this user jid_from = jid.JID(presence_list[0]["from"]) iq = domish.Element((None, "iq")) iq["type"] = "set" iq["from"] = jid_from.userhost() iq["to"] = stanza["from"] try: self.parent.router.build_vcard(jid_from.user, iq) self.send(iq) except keyring.KeyNotFoundException: pass # no roster lookup, XMPP standard roster instead else: # include items from the user's whitelist wl = self.parent.router.get_whitelist(requester) probes = None if wl: subscriptions = [] for e in wl: item = roster.addElement((None, "item")) item["jid"] = e itemJid = jid.JID(e) # check if subscription status is 'both' or just 'from' allowed = self.parent.router.is_presence_allowed(requester, itemJid) if allowed == 1: status = "both" else: status = "from" # TODO include name from PGP key? item["subscription"] = status # add to subscription list subscriptions.append(itemJid) # send the roster self.send(response) # subscribe to all users (without sending subscribed stanza of course) if wl: for itemJid in subscriptions: self.parent.router.subscribe(requester, itemJid, send_subscribed=False)