def send_user_presence(self, gid, sender, recipient): stub = self.lookup(recipient) log.debug("onProbe(%s): found %r" % (gid, stub, )) if stub: data = stub.presence() i = len(data) for x in data: presence = deepcopy(x) presence.consumed = True presence['to'] = sender.full() try: # add fingerprint fpr = self.parent.keyring.get_fingerprint(recipient.user) if fpr: pubkey = presence.addElement(('urn:xmpp:pubkey:2', 'pubkey')) fprint = pubkey.addElement((None, 'print')) fprint.addContent(fpr) except keyring.KeyNotFoundException: log.warn("key not found for user %s" % (recipient, )) if gid: # FIXME this will duplicate group elements - actually in storage there should be no group element!!! group = presence.addElement((xmlstream2.NS_XMPP_STANZA_GROUP, 'group')) group['id'] = gid group['count'] = str(i) i -= 1 self.send(presence) return True # no such user return False
def startService(self): component.Component.startService(self) resolver.ResolverMixIn.startService(self) # register the registration provider if configured if 'registration' in self.config: from kontalk.xmppserver import register provider = self.config['registration']['provider'] try: prov_class = register.providers[provider] self.registration = prov_class(self, self.config['registration']) except: log.warn(traceback.format_exc()) if self.registration: log.info("using registration provider %s (type=%s)" % (self.registration.name, self.registration.type)) else: log.info("disabling registration") # register push notifications providers if configured if 'push' in self.config: from kontalk.xmppserver import push self.push_manager = push.PushManager(self, self.config['push']) if self.push_manager: log.info("using push notifications providers: %s" % (', '.join(self.push_manager.providers.keys()))) else: log.info("disabling push notifictions") # load local presence data d = self.presencedb.get_all() d.addCallback(self._presence_data)
def onVCardSet(self, stanza, sender=None): """ Handle vCards set IQs. This simply takes care of importing the key in the keyring for future signature verification. Actual key verification is done by c2s when accepting vCards coming from clients. WARNING/1 does this mean that we bindly accept keys from components? -- YES blindly :P c2s will filter invalid requests WARNING/2 importing the key means that keys coming from local c2s are imported twice because the keyring is the same. Unless we want to make a separated keyring only for resolver? -- YES USING .gnupg-cache """ # TODO parse vcard for interesting sections if stanza.vcard.key is not None: # we do this because of the uri member in domish.Element keydata = stanza.vcard.key.firstChildElement() if keydata.name == 'uri': keydata = str(keydata) if keydata.startswith(xmlstream2.DATA_PGP_PREFIX): keydata = base64.b64decode(keydata[len(xmlstream2.DATA_PGP_PREFIX):]) # import into cache keyring userid = util.jid_user(stanza['from']) # this chould take a lot of time (up to 500ms) if self.parent.keyring.check_user_key(keydata, userid): log.debug("key cached successfully") else: log.warn("invalid key")
def _load_privacy_lists(self): try: with open(self.PERSIST_STORAGE, 'r') as f: import cPickle data = cPickle.load(f) self.whitelists = data['whitelists'] self.blacklists = data['blacklists'] except: log.warn("unable to load privacy lists") import traceback traceback.print_exc()
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 render_POST(self, request): #log.debug("request from %s: %s" % (self.userid, request.requestHeaders)) # check mime type mime = request.getHeader('content-type') if mime not in self.config['upload']['accept_content']: return self._quick_response(request, 406, 'unacceptable content type') # check length length = request.getHeader('content-length') if length != None: length = long(length) if length <= self.config['upload']['max_size']: # store file to storage # TODO convert to file-object management for less memory consumption data = request.content.read() if len(data) == length: fileid = util.rand_str(40) filename = self.fileserver.storage.store_data( fileid, mime, data) if filename: log.debug( "file stored to disk (filename=%s, fileid=%s)" % (filename, fileid)) request.setHeader('content-type', 'text/url') return str(self.config['upload']['url']) % (fileid, ) else: log.error("error storing file") return self._quick_response(request, 500, 'unable to store file') else: log.warn( "file length not matching content-length header (%d/%d)" % (len(data), length)) return self._quick_response(request, 400, 'bad request') else: log.warn("file too big (%d bytes)" % length) return self._quick_response(request, 413, 'request too large') else: log.warn("content-length header not found") return self._quick_response(request, 411, 'content length not declared')
def render_POST(self, request): # log.debug("request from %s: %s" % (self.userid, request.requestHeaders)) # check mime type mime = request.getHeader("content-type") if mime not in self.config["upload"]["accept_content"]: return self._quick_response(request, 406, "unacceptable content type") # check length length = request.getHeader("content-length") if length != None: length = long(length) if length <= self.config["upload"]["max_size"]: # store file to storage # TODO convert to file-object management for less memory consumption data = request.content.read() if len(data) == length: fileid = util.rand_str(40) filename = self.fileserver.storage.store_data(fileid, mime, data) if filename: log.debug("file stored to disk (filename=%s, fileid=%s)" % (filename, fileid)) request.setHeader("content-type", "text/url") return str(self.config["upload"]["url"]) % (fileid,) else: log.error("error storing file") return self._quick_response(request, 500, "unable to store file") else: log.warn("file length not matching content-length header (%d/%d)" % (len(data), length)) return self._quick_response(request, 400, "bad request") else: log.warn("file too big (%d bytes)" % length) return self._quick_response(request, 413, "request too large") else: log.warn("content-length header not found") return self._quick_response(request, 411, "content length not declared")
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 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 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))