def dispatch(self, xs, stanza): """ Send a stanza to the router, checking some stuff first. """ log.debug("stanza from %s: %s" % (xs.otherEntity.full(), stanza.toXml())) util.resetNamespace(stanza, xs.namespace) stanzaFrom = stanza.getAttribute('from') stanzaTo = stanza.getAttribute('to') if not stanza.bind and not stanzaFrom or not stanzaTo: xs.sendStreamError(error.StreamError('improper-addressing')) else: try: sender = jid.internJID(stanzaFrom) jid.internJID(stanzaTo) except jid.InvalidFormat: log.debug("dropping stanza with malformed JID") log.debug("sender = %s, otherEntity = %s" % (sender.full(), xs.otherEntity.full())) try: unused, host = util.jid_component(sender.host) if host in self.keyring.hostlist(): self.router.send(stanza) else: raise Exception() except: xs.sendStreamError(error.StreamError('invalid-from'))
def _bind(stanza, name): if not stanza.hasAttribute('error'): # no errors, register route unused, host = util.jid_component(name) if host not in self.routes: self.routes[host] = [] self.routes[host].append(name) log.debug("ROUTES: %s" % (self.routes, ))
def translateJID(self, _jid, resource=True): """ Translate a server JID ([email protected]) into a network JID ([email protected]). """ # TODO ehm :D try: unused, host = util.jid_component(_jid.host) if host in self.keyring.hostlist(): return jid.JID(tuple=(_jid.user, self.network, _jid.resource if resource else None)) except ValueError: pass return _jid if resource else _jid.userhostJID()
def iq(self, stanza): """ Removes the from attribute if it matches the c2s JID. This was actually done to "fake" IQ stanzas coming from remote c2s that should appear to the client as coming from nowhere. """ sender = stanza.getAttribute('from') if sender and not ('@' in sender): # receiving iq from remote c2s, remove from attribute unused, host = util.jid_component(sender, util.COMPONENT_C2S) if host in self.keyring.hostlist(): del stanza['from']
def unbind(self, name): # send unbind command to router unbind = domish.Element((None, 'unbind')) unbind['name'] = name self.send(unbind) # unregister route unused, host = util.jid_component(name) if host in self.routes: self.routes[host].remove(name) if len(self.routes[host]) == 0: del self.routes[host] else: # this is an error, it shouldn't happen log.warn("unbinding non-registered route: %s" % (name, ))
def onPresenceAvailable(self, stanza): """Handle available presence stanzas.""" if stanza.consumed: return try: # initial presence from a remote resolver component, host = util.jid_component(stanza['from'], util.COMPONENT_C2S) if host != self.parent.servername and host in self.parent.keyring.hostlist(): self.send_privacy_lists('blocklist', self.parent.blacklists, stanza['from']) self.send_privacy_lists('whitelist', self.parent.whitelists, stanza['from']) except: pass self.parent.broadcastSubscribers(stanza)
def broadcastSubscribers(self, stanza): """Broadcast stanza to JID subscribers.""" # direct delivery: do not broadcast if stanza.direct and stanza.direct.uri == xmlstream2.NS_XMPP_DIRECT: return user = jid.JID(stanza['from']) try: unused, host = util.jid_component(user.host) # FIXME wrong host check (this is a one of the causes of the invalid-from bug) if host in self.keyring.hostlist(): # local or network user: translate host name watched = jid.JID(tuple=(user.user, self.network, user.resource)) else: # other JIDs, use unchanged watched = user except ValueError: # other JIDs, use unchanged watched = user bareWatched = watched.userhostJID() if bareWatched in self.subscriptions: #stanza['from'] = watched.full() removed = [] for sub in self.subscriptions[bareWatched]: if self.is_presence_allowed(sub, watched) == 1: log.debug("notifying subscriber %s" % (sub, )) stanza['to'] = sub.userhost() # direct delivery to workaround presence loops stanza.addElement((xmlstream2.NS_XMPP_DIRECT, 'direct')) stanza.consumed = True self.send(stanza) else: log.debug("%s is not allowed to see presence" % (sub, )) removed.append(sub) # remove unauthorized users for e in removed: self.subscriptions[bareWatched].remove(e)
def onPresenceUnavailable(self, stanza): """Handle unavailable presence stanzas.""" if self.parent.logTraffic: log.debug("user unavailable: %s" % (stanza.toXml().encode('utf-8'), )) else: log.debug("user unavailable from %s" % (stanza['from'], )) # local c2s or remote server has disconnected, remove presences from cache try: unused, host = util.jid_component(stanza['from'], util.COMPONENT_C2S) if host in self.parent.keyring.hostlist(): log.debug("server %s is disconnecting, taking over presence data" % (host, )) ordered_presence = [] for stub in self.presence_cache.itervalues(): if stub.jid.host == stanza['from']: ordered_presence.append(stub) # TEST TEST TEST # TODO this needs serious re-design from scratch (I mean the whole presence sharing architecture) from operator import attrgetter ordered_presence.sort(key=attrgetter('jid')) # take the N-th part index = 1 for s in self.parent.keyring.hostlist(): # skip missing server if s == host: continue # we found ourselves if s == self.parent.servername: break index += 1 if index > len(self.parent.keyring.hostlist()): log.warn("we can't find ourselves on the servers table! WTF!?!?") else: network_len = len(self.parent.keyring.hostlist()) presence_len = len(ordered_presence) slice_start = presence_len / (network_len-1) * (index - 1) slice_end = presence_len / (network_len-1) * ((index+1) - 1) log.debug("slice_start = %d, slice_end = %d" % (slice_start, slice_end)) for i in range(slice_start, slice_end): e = ordered_presence[i] rewrite = None presence = e.presence() for p in presence: if not p.hasAttribute('type'): # available presence will be converted into unavailable p['type'] = 'unavailable' if p.getAttribute('type') == 'unavailable': # unavailable presence will be broadcasted rewrite = PresenceStub.fromElement(p, util .component_jid(self.parent.servername, util.COMPONENT_C2S)) self.presence_cache[e.jid.user] = rewrite # simulate presence broadcast so resolvers will insert it into their cache if rewrite: p = rewrite.presence() try: fpr = self.parent.keyring.get_fingerprint(e.jid.user) self.parent.presencedb.presence(p[0]) self.parent.presencedb.public_key(e.jid.user, fpr) except keyring.KeyNotFoundException: pass self.send(p[0].toXml().encode('utf-8')) return except TypeError: pass except: import traceback traceback.print_exc() # normal user unavailable user = jid.JID(stanza['from']) if user.user: self.user_unavailable(stanza)
def presence(self, stanza): """ This initial presence is from a broadcast sent by external entities (e.g. not the sm); sm wouldn't see it because it has no observer. Here we are sending offline messages directly to the connected user. """ log.debug("initial presence from router by %s" % (stanza['from'], )) try: # receiving initial presence from remote c2s, send all presence data unused, host = util.jid_component(stanza['from'], util.COMPONENT_C2S) if host != self.parent.servername and host in self.parent.keyring.hostlist( ): log.debug( "remote c2s appeared, sending all local presence and vCards to %s" % (stanza['from'], )) self.send_presence(stanza['from']) except: pass sender = jid.JID(stanza['from']) # check for external conflict self.parent.sfactory.check_conflict(sender) if sender.user: try: unused, host = util.jid_component(sender.host, util.COMPONENT_C2S) # initial presence from a client connected to another server, clear it from our presence table if host != self.parent.servername and host in self.parent.keyring.hostlist( ): log.debug("deleting %s from presence table" % (sender.user, )) self.parent.presencedb.delete(sender.user) except: pass # initial presence - deliver offline storage def output(data, user): log.debug("data: %r" % (data, )) to = user.full() for msg in data: log.debug("msg[%s]=%s" % ( msg['id'], msg['stanza'].toXml().encode('utf-8'), )) try: """ Mark the stanza with our server name, so we'll receive a copy of the receipt """ if msg['stanza'].request: msg['stanza'].request[ 'from'] = self.xmlstream.thisEntity.full() elif msg['stanza'].received: msg['stanza'].received[ 'from'] = self.xmlstream.thisEntity.full() # mark delayed delivery if 'timestamp' in msg: delay = msg['stanza'].addElement( (xmlstream2.NS_XMPP_DELAY, 'delay')) delay['stamp'] = msg['timestamp'].strftime( xmlstream2.XMPP_STAMP_FORMAT) msg['to'] = to self.send(msg['stanza']) """ If a receipt is requested, we won't delete the message from storage now; we must be sure client has received it. Otherwise just delete the message immediately. """ if not xmlstream2.extract_receipt(msg['stanza'], 'request') and \ not xmlstream2.extract_receipt(stanza, 'received'): self.parent.message_offline_delete( msg['id'], msg['stanza'].name) except: log.debug("offline message delivery failed (%s)" % (msg['id'], )) traceback.print_exc() d = self.parent.stanzadb.get_by_recipient(sender) d.addCallback(output, sender)
def presence(self, stanza): """ This initial presence is from a broadcast sent by external entities (e.g. not the sm); sm wouldn't see it because it has no observer. Here we are sending offline messages directly to the connected user. """ log.debug("initial presence from router by %s" % (stanza['from'], )) try: # receiving initial presence from remote c2s, send all presence data unused, host = util.jid_component(stanza['from'], util.COMPONENT_C2S) if host != self.parent.servername and host in self.parent.keyring.hostlist(): log.debug("remote c2s appeared, sending all local presence and vCards to %s" % (stanza['from'], )) self.send_presence(stanza['from']) except: pass sender = jid.JID(stanza['from']) # check for external conflict self.parent.sfactory.check_conflict(sender) if sender.user: try: unused, host = util.jid_component(sender.host, util.COMPONENT_C2S) # initial presence from a client connected to another server, clear it from our presence table if host != self.parent.servername and host in self.parent.keyring.hostlist(): log.debug("deleting %s from presence table" % (sender.user, )) self.parent.presencedb.delete(sender.user) except: pass # initial presence - deliver offline storage def output(data, user): log.debug("data: %r" % (data, )) to = user.full() for msg in data: log.debug("msg[%s]=%s" % (msg['id'], msg['stanza'].toXml().encode('utf-8'), )) try: """ Mark the stanza with our server name, so we'll receive a copy of the receipt """ if msg['stanza'].request: msg['stanza'].request['from'] = self.xmlstream.thisEntity.full() elif msg['stanza'].received: msg['stanza'].received['from'] = self.xmlstream.thisEntity.full() # mark delayed delivery if 'timestamp' in msg: delay = msg['stanza'].addElement((xmlstream2.NS_XMPP_DELAY, 'delay')) delay['stamp'] = msg['timestamp'].strftime(xmlstream2.XMPP_STAMP_FORMAT) msg['to'] = to self.send(msg['stanza']) """ If a receipt is requested, we won't delete the message from storage now; we must be sure client has received it. Otherwise just delete the message immediately. """ if not xmlstream2.extract_receipt(msg['stanza'], 'request') and \ not xmlstream2.extract_receipt(stanza, 'received'): self.parent.message_offline_delete(msg['id'], msg['stanza'].name) except: log.debug("offline message delivery failed (%s)" % (msg['id'], )) traceback.print_exc() d = self.parent.stanzadb.get_by_recipient(sender) d.addCallback(output, sender)
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")