def broadcast(self, stanza, same=False): """ Broadcast a stanza to every component. This alters the to attribute in outgoing stanza for each component. """ from_host = util.jid_host(stanza['from']) for host, xs in self.routes.iteritems(): # do not send to the original sender if host is not None and (host != from_host or same): log.debug("sending to %s" % (host, )) stanza['to'] = host xs.send(stanza)
def presence(self, stanza): """ Presence broadcast from local c2s (intended for remote c2s), deliver also to remote resolver. """ if not stanza.consumed: host = util.jid_host(stanza['from']) if host == self.servername: """ Original stanza will be sent to remote c2s, which will provider offline storage delivery and network conflicts. """ self.dispatch(stanza) """ Intended destination is not remote resolver, so be sure to deliver. """ if stanza.getAttribute('destination') != self.servername: # be sure to unconsume for the next dispatch stanza.consumed = False stanza['destination'] = self.network
def route(self, stanza, xs): """ Route a stanza. @param stanza: The stanza to be routed. @type stanza: L{domish.Element}. """ if stanza.consumed: return """" TEST check sender host is component stanzaFrom = jid.JID(stanza['from']) if stanzaFrom.host != xs.thisEntity.host: log.error("stanza is not from component - dropping") return """ # reset namespace util.resetNamespace(stanza, component.NS_COMPONENT_ACCEPT) # send stanza to logging entities for lg in self.logs: lg.send(stanza) if not stanza.hasAttribute('to'): if self.logTraffic: log.debug("broadcasting stanza %s" % (stanza.toXml().encode('utf-8'), )) self.broadcast(stanza) else: """ FIXME we have encoding problems here... (why not in other components?!?!?) """ # check for stanza loops errors = 0 for child in stanza.children: if domish.IElement.providedBy(child) and child.name == 'error': errors += 1 if errors > 1: if self.logTraffic: log.debug("error loop, dropping stanza %s" % (stanza.toXml().encode('utf-8'), )) else: log.debug("error loop, dropping stanza") return if self.logTraffic: log.debug("routing stanza %s" % (stanza.toXml().encode('utf-8'), )) try: destination_host = util.jid_host(stanza['to']) if destination_host in self.routes: self.routes[destination_host].send(stanza) elif destination_host in self.private: self.private[destination_host].send(stanza) else: self.routes[None].send(stanza) except KeyError: log.warn("unroutable stanza, bouncing back to component") e = error.StanzaError('service-unavailable') xs.send(e.toResponse(stanza))
def test_jid_host(self): jidstring = '[email protected]/resource' user = util.jid_host(jidstring) self.assertEqual(user, 'localhost.localdomain')
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 route(self, stanza, xs): """ Route a stanza. @param stanza: The stanza to be routed. @type stanza: L{domish.Element}. """ if stanza.consumed: return """" TEST check sender host is component stanzaFrom = jid.JID(stanza['from']) if stanzaFrom.host != xs.thisEntity.host: log.error("stanza is not from component - dropping") return """ # reset namespace util.resetNamespace(stanza, component.NS_COMPONENT_ACCEPT) # send stanza to logging entities for lg in self.logs: lg.send(stanza) if not stanza.hasAttribute('to'): if self.logTraffic: log.debug("broadcasting stanza %s" % (stanza.toXml().encode('utf-8'), )) self.broadcast(stanza) else: """ FIXME we have encoding problems here... (why not in other components?!?!?) """ # check for stanza loops errors = 0 for child in stanza.children: if domish.IElement.providedBy(child) and child.name == 'error': errors += 1 if errors > 1: if self.logTraffic: log.debug("error loop, dropping stanza %s" % (stanza.toXml().encode('utf-8'), )) else: log.debug("error loop, dropping stanza") return if self.logTraffic: log.debug("routing stanza %s" % (stanza.toXml().encode('utf-8'), )) try: destination_host = util.jid_host(stanza['to']) if destination_host in self.routes: self.routes[destination_host].send(stanza) else: self.routes[None].send(stanza) except KeyError: log.warn("unroutable stanza, bouncing back to component") e = error.StanzaError('service-unavailable') xs.send(xmlstream2.errorResponse(e, stanza))