def render_GET(self, request): log.debug("request from %s: %s" % (self.userid, request.args)) if 'f' in request.args: fn = request.args['f'][0] info = self.fileserver.storage.get(fn, False) if info: filename, mime, md5sum = info log.debug("sending file type %s, path %s, md5sum %s" % (mime, filename, md5sum)) genfilename = util.generate_filename(mime) request.setHeader('content-type', mime) request.setHeader('content-length', os.path.getsize(filename)) request.setHeader('content-disposition', 'attachment; filename="%s"' % (genfilename)) request.setHeader('x-md5sum', md5sum) # stream file to the client fp = open(filename, 'rb') d = FileSender().beginFileTransfer(fp, request) def finished(ignored): fp.close() request.finish() d.addErrback(log.error).addCallback(finished) return server.NOT_DONE_YET # file not found in extra storage else: return self._quick_response(request, 404, 'not found') return self._quick_response(request, 400, 'bad request')
def _presence(stanza, callback, timeout, buf): # presence probe error - finish here if stanza.getAttribute('type') == 'error': # TODO duplicated code self.xmlstream.removeObserver("/presence/group[@id='%s']" % (stanza['id'], ), _presence) self.xmlstream.removeObserver("/presence[@type='error'][@id='%s']" % (stanza['id'], ), _presence) if not callback.called: # cancel timeout timeout.cancel() # fire deferred callback.callback(buf) return sender = jid.JID(stanza['from']) log.debug("JID %s found!" % (sender.full(), )) stanza.consumed = True buf.append(sender) chain = stanza.group # end of presence chain!!! if not chain or int(chain['count']) == len(buf): # TODO duplicated code self.xmlstream.removeObserver("/presence/group[@id='%s']" % (stanza['id'], ), _presence) self.xmlstream.removeObserver("/presence[@type='error'][@id='%s']" % (stanza['id'], ), _presence) if not callback.called: # cancel timeout timeout.cancel() # fire deferred callback.callback(buf)
def initiateOutgoingStream(self, otherHost): """ Initiate an outgoing XMPP server-to-server connection. """ def resetConnecting(_): self._outgoingConnecting.remove(otherHost) def bounceError(_): resetConnecting(None) log.debug("unable to connect to remote server") if otherHost in self._outgoingConnecting: log.debug("pending connection found to %s - aborting" % otherHost) return log.debug("connecting to %s" % otherHost) authenticator = XMPPNetConnectAuthenticator(self.defaultDomain, otherHost, self.keyring) factory = server.DeferredS2SClientFactory(authenticator) factory.addBootstrap(xmlstream.STREAM_AUTHD_EVENT, self.outgoingInitialized) factory.logTraffic = self.logTraffic self._outgoingConnecting.add(otherHost) d = initiateNet(factory, self.credentials) d.addCallback(resetConnecting) d.addErrback(bounceError) return d
def bind(self, stanza, xs): log.debug("binding component %s" % (stanza.toXml().encode('utf-8'), )) stanza.consumed = True if stanza.default: route = None else: try: route = stanza['name'] except: xs.send( domish.Element((None, 'bind'), attribs={'error': 'bad-request'})) xs.transport.loseConnection() if route not in self.routes: self.routes[route] = xs xs.send(domish.Element((None, 'bind'))) if stanza.log: self.logs.add(xs) else: xs.send( domish.Element((None, 'bind'), attribs={'error': 'conflict'})) xs.transport.loseConnection()
def onConnectionLost(self, xs, reason): thisHost = xs.thisEntity.host otherHost = xs.otherEntity.host log.debug("Incoming connection %d from %s to %s disconnected" % (xs.serial, otherHost, thisHost)) self.service.invalidateConnection(xs)
def render_GET(self, request): log.debug("request from %s: %s" % (self.userid, request.args)) if "f" in request.args: fn = request.args["f"][0] info = self.fileserver.storage.get(fn, False) if info: filename, mime, md5sum = info log.debug("sending file type %s, path %s, md5sum %s" % (mime, filename, md5sum)) genfilename = util.generate_filename(mime) request.setHeader("content-type", mime) request.setHeader("content-length", os.path.getsize(filename)) request.setHeader("content-disposition", 'attachment; filename="%s"' % (genfilename)) request.setHeader("x-md5sum", md5sum) # stream file to the client fp = open(filename, "rb") d = FileSender().beginFileTransfer(fp, request) def finished(ignored): fp.close() request.finish() d.addErrback(log.error).addCallback(finished) return server.NOT_DONE_YET # file not found in extra storage else: return self._quick_response(request, 404, "not found") return self._quick_response(request, 400, "bad request")
def onSubscribed(self, stanza): if stanza.consumed: return log.debug("user %s accepted subscription by %s" % (self.xmlstream.otherEntity, stanza['to'])) stanza.consumed = True jid_to = jid.JID(stanza['to']) jid_from = self.xmlstream.otherEntity.userhostJID() # add "to" user to whitelist of "from" user self.parent.router.add_whitelist(jid_from, jid_to) log.debug("SUBSCRIPTION SUCCESSFUL") if self.parent.router.cache.jid_available(jid_from): # send subscription accepted immediately and subscribe # TODO this is wrong, but do it for the moment until we find a way to handle this case self.parent.router.doSubscribe(jid_from, jid_to, stanza.getAttribute('id'), response_only=False) # blindly try to deliver offline storage # this will process any pending message self.parent.router.deliver_offline_storage(jid_from)
def error(self, stanza, condition='service-unavailable', errtype='cancel', text=None): if not stanza.consumed: log.debug("error %s" % (stanza.toXml(), )) stanza.consumed = True util.resetNamespace(stanza, self.namespace) e = error.StanzaError(condition, errtype, text) self.send(e.toResponse(stanza), True)
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 register(self, stanza): """This is actually used for key regeneration.""" log.debug("client requested key regeneration: %s" % (stanza.toXml(), )) stanza.consumed = True fields = stanza.query.x.elements(uri='jabber:x:data', name='field') var_pkey = None var_revoked = None for f in fields: if f['var'] == 'publickey': var_pkey = f elif f['var'] == 'revoked': var_revoked = f # FIXME maybe some stuff here should go to c2s? if var_pkey: userid = self.parent.xmlstream.otherEntity.user # check if user has already a key # this is used for users coming from version 2.x (no key back then) d = self.parent.router.presencedb.get(userid) d.addCallback(self._register_continue, userid, var_pkey, var_revoked, stanza) else: # bad request stanza.consumed = False self.parent.error(stanza, 'bad-request')
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 _abort(stanzaId, callback, data): log.debug("iq/last broadcast request timed out!") self.xmlstream.removeObserver( "/iq[@id='%s']" % stanza['id'], find_latest) if not callback.called: #callback.errback(failure.Failure(internet_error.TimeoutError())) callback.callback(data['latest'])
def onProbe(self, stanza): """Handle presence probes.""" if stanza.consumed: return log.debug("probe request: %s" % (stanza.toXml(), )) stanza.consumed = True to = jid.JID(stanza['to']) sender = jid.JID(stanza['from']) # are we probing a user we have blocked? if self.parent.is_presence_allowed(to, sender) == -1: log.debug("probing blocked user, bouncing error") e = error.StanzaError('not-acceptable', 'cancel') errstanza = e.toResponse(stanza) errstanza.error.addElement((xmlstream2.NS_IQ_BLOCKING_ERRORS, 'blocked')) self.send(errstanza) elif self.parent.is_presence_allowed(sender, to) == 1: gid = stanza.getAttribute('id') if not self.send_user_presence(gid, sender, to): response = xmlstream.toResponse(stanza, 'error') # TODO include error cause? self.send(response)
def send(self, stanza): """ Send stanza to the proper XML Stream. This uses addressing embedded in the stanza to find the correct stream to forward the stanza to. """ otherHost = jid.internJID(stanza["to"]).host stanzaFrom = jid.JID(stanza['from']) if stanzaFrom.host != self.defaultDomain: stanzaFrom.host = self.defaultDomain stanza['origin'] = stanza['from'] stanza['from'] = stanzaFrom.full() log.debug("sending data to %s [%r]" % (otherHost, self._outgoingStreams, )) if otherHost not in self._outgoingStreams: # There is no connection with the destination (yet). Cache the # outgoing stanza until the connection has been established. # XXX: If the connection cannot be established, the queue should # be emptied at some point. if otherHost not in self._outgoingQueues: self._outgoingQueues[otherHost] = [] self._outgoingQueues[otherHost].append(stanza) self.initiateOutgoingStream(otherHost) else: self._outgoingStreams[otherHost].send(stanza)
def onPresenceUnavailable(self, xs, stanza): # if component (i.e. no user part of JID) unbind name from router stanzaFrom = jid.internJID(stanza['from']) if not stanzaFrom.user and stanzaFrom.host.endswith(xs.otherEntity.full()): stanza.handled = True log.debug("unbinding name %s" % (stanzaFrom.host, )) self.router.unbind(stanzaFrom.host)
def check_conflict(self, _jid): """Checks for local conflict and disconnects the conflicting local resource.""" if _jid.user and _jid.host in self.router.keyring.hostlist(): if _jid.user in self.streams and _jid.resource in self.streams[ _jid.user]: log.debug("network resource conflict for %s" % (_jid.full(), )) self.streams[_jid.user][_jid.resource].conflict()
def last_activity(self, stanza): log.debug("local last activity request: %s" % (stanza.toXml(), )) stanza.consumed = True def _db(presence, stanza): log.debug("iq/last: presence=%r" % (presence, )) if type(presence) == list and len(presence) > 0: user = presence[0] response = xmlstream.toResponse(stanza, 'result') response_from = util.userid_to_jid(user['userid'], self.xmlstream.thisEntity.host) response['from'] = response_from.userhost() query = response.addElement((xmlstream2.NS_IQ_LAST, 'query')) if self.parent.sfactory.client_connected(response_from): query['seconds'] = '0' else: latest = None for user in presence: if latest is None or latest['timestamp'] > user['timestamp']: latest = user # TODO timediff from latest #log.debug("max timestamp: %r" % (max, )) query['seconds'] = '123456' self.send(response) log.debug("iq/last result sent: %s" % (response.toXml().encode('utf-8'), )) else: # TODO return error? log.debug("iq/last: user not found") userid = util.jid_user(stanza['to']) d = self.parent.presencedb.get(userid) d.addCallback(_db, stanza)
def handle(self, stanza): # enforce sender stanza['from'] = self.resolveJID(self.xmlstream.otherEntity).full() to = stanza.getAttribute('to') if to is not None: try: to = jid.JID(to) except: # invalid destination, consume stanza and return error stanza.consumed = True log.debug("invalid address: %s" % (to, )) e = error.StanzaError('jid-malformed', 'modify') self.send(e.toResponse(stanza)) return # stanza is for us if to.host == self.network: # sending to full JID, forward to router if to.user is not None and to.resource is not None: self.forward(stanza) # stanza is not intended to component either elif to.host != util.component_jid(self.servername, util.COMPONENT_C2S): self.forward(stanza)
def outgoingInitialized(self, xs): thisHost = xs.thisEntity.host otherHost = xs.otherEntity.host log.debug("Outgoing connection %d from %s to %s established" % (xs.serial, thisHost, otherHost)) self._outgoingStreams[otherHost] = xs xs.addObserver(xmlstream.STREAM_END_EVENT, lambda _: self.outgoingDisconnected(xs)) xs.addObserver('/*', self.onElement, 0, xs) """ Here we introduce ourselves to remote c2s, so it will reply with all presence. We are faking resolver identity using 'origin' so the reply will go directly to it. """ p = domish.Element((None, 'presence')) p['from'] = self.defaultDomain # TRANSLATED BY OBSERVER p['origin'] = self.network p['to'] = otherHost xs.send(p) if otherHost in self._outgoingQueues: for element in self._outgoingQueues[otherHost]: xs.send(element) del self._outgoingQueues[otherHost]
def message(self, stanza): if not stanza.consumed: jid_from = self.parent.resolveJID(self.xmlstream.otherEntity) stanza["from"] = jid_from.full() # no destination - use sender bare JID if not stanza.hasAttribute("to"): jid_to = jid_from stanza["to"] = jid_to.userhost() else: jid_to = jid.JID(stanza["to"]) # are we sending a message to a user we have blocked? if self.parent.router.is_presence_allowed(jid_to, jid_from) == -1: log.debug("sending message to blocked user, bouncing error") e = error.StanzaError("not-acceptable", "cancel") errstanza = e.toResponse(stanza) errstanza.error.addElement((xmlstream2.NS_IQ_BLOCKING_ERRORS, "blocked")) self.parent.send(errstanza) else: # check for permission allowed = self.parent.router.is_presence_allowed(jid_from, jid_to) if allowed == -1: # user is blocked! log.debug("not allowed to send messages, sending fake response to %s" % (stanza["from"],)) if stanza.getAttribute("type") == "chat" and xmlstream2.extract_receipt(stanza, "request"): self.send_fake_receipt(stanza) else: # send to c2s hub (without implicitly consuming) self.parent.router.send(stanza, force_delivery=True, hold=(allowed != 1)) # we have now consumed the stanza stanza.consumed = True
def register(self, stanza): """This is actually used for key regeneration.""" log.debug("client requested key regeneration: %s" % (stanza.toXml(),)) stanza.consumed = True fields = stanza.query.x.elements(uri="jabber:x:data", name="field") var_pkey = None var_revoked = None for f in fields: if f["var"] == "publickey": var_pkey = f elif f["var"] == "revoked": var_revoked = f # FIXME maybe some stuff here should go to c2s? if var_pkey: userid = self.parent.xmlstream.otherEntity.user # check if user has already a key # this is used for users coming from version 2.x (no key back then) d = self.parent.router.presencedb.get(userid) d.addCallback(self._register_continue, userid, var_pkey, var_revoked, stanza) else: # bad request stanza.consumed = False self.parent.error(stanza, "bad-request")
def onPresenceUnavailable(self, xs, stanza): # if component (i.e. no user part of JID) unbind name from router stanzaFrom = jid.internJID(stanza['from']) if not stanzaFrom.user and stanzaFrom.host.endswith( xs.otherEntity.full()): stanza.handled = True log.debug("unbinding name %s" % (stanzaFrom.host, )) self.router.unbind(stanzaFrom.host)
def push_regid(self, stanza): for child in stanza.children: if child.name == 'c' and child.uri == xmlstream2.NS_PRESENCE_PUSH: regid = str(child) provider = child.getAttribute('provider') if regid and provider: log.debug("registering %s using %s with %s" % (self.xmlstream.otherEntity, provider, regid)) self.parent.router.push_manager.register(self.xmlstream.otherEntity, provider, regid)
def outgoingDisconnected(self, xs): thisHost = xs.thisEntity.host otherHost = xs.otherEntity.host log.debug("Outgoing connection %d from %r to %r disconnected" % (xs.serial, thisHost, otherHost)) del self._outgoingStreams[thisHost, otherHost]
def _local_presence_output(self, data, user): log.debug("data: %r" % (data, )) # this will be used to set a safe recipient # WARNING this will create a JID anyway :( jid_to = self.resolveJID(user) for msg in data: log.debug("msg[%s]=%s" % (msg['id'], msg['stanza'].toXml().encode('utf-8'), )) stanza = msg['stanza'] try: """ Mark the stanza with our server name, so we'll receive a copy of the receipt """ if stanza.request: stanza.request['from'] = self.xmlstream.thisEntity.full() elif stanza.received: stanza.received['from'] = self.xmlstream.thisEntity.full() # mark delayed delivery if 'timestamp' in msg: delay = stanza.addElement((xmlstream2.NS_XMPP_DELAY, 'delay')) delay['stamp'] = msg['timestamp'].strftime(xmlstream2.XMPP_STAMP_FORMAT) # are we sending a message to a user we have blocked? jid_from = jid.JID(stanza['from']) if self.is_presence_allowed(jid_to, jid_from) == -1: log.debug("sending message to blocked user, bouncing error") e = error.StanzaError('not-acceptable', 'cancel') errstanza = e.toResponse(msg['stanza']) errstanza.error.addElement((xmlstream2.NS_IQ_BLOCKING_ERRORS, 'blocked')) self.send(errstanza) else: # check for permission allowed = self.is_presence_allowed(jid_from, jid_to) if allowed == -1: # user is blocked! log.debug("not allowed to send messages to %s, discarding message" % (stanza['to'], )) self.message_offline_delete(msg['id'], stanza.name) else: """ We use direct delivery here: it's faster and does not involve JID resolution """ stanza['to'] = jid_to.full() self.dispatch(stanza, hold=(allowed != 1)) """ 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(stanza, 'request') and \ not xmlstream2.extract_receipt(stanza, 'received'): self.message_offline_delete(msg['id'], stanza.name) except: log.debug("offline message delivery failed (%s)" % (msg['id'], )) traceback.print_exc()
def _error(failure): log.debug("error: %s" % (failure, )) if isinstance(failure.value, RuntimeError): e = error.StanzaError('bad-request', 'modify', failure.getErrorMessage()) else: e = error.StanzaError('service-unavailable', 'cancel', failure.getErrorMessage()) iq = xmlstream.toResponse(stanza, 'error') iq.addChild(e.getElement()) manager.send(iq, True)
def _presence_data(self, presence): log.debug("presence: %r" % (presence, )) if type(presence) == list and len(presence) > 0: for user in presence: # store fingerprint self.keyring.set_fingerprint(user['userid'], user['fingerprint']) host = util.component_jid(self.servername, util.COMPONENT_C2S) response_from = jid.JID(tuple=(user['userid'], host, None)).full() num_avail = 0 try: streams = self.sfactory.streams[user['userid']] for x in streams.itervalues(): presence = x._presence if presence and not presence.hasAttribute('type'): self.cache.user_available(presence) if self.logTraffic: log.debug("local presence: %s" % (presence.toXml().encode('utf-8'), )) else: log.debug("local presence: %s" % (presence['from'], )) num_avail += 1 except KeyError: pass # no available resources - send unavailable presence if not num_avail: response = domish.Element((None, 'presence')) response['from'] = response_from if user['status'] is not None: response.addElement((None, 'status'), content=user['status']) if user['show'] is not None: response.addElement((None, 'show'), content=user['show']) response['type'] = 'unavailable' delay = domish.Element(('urn:xmpp:delay', 'delay')) delay['stamp'] = user['timestamp'].strftime( xmlstream2.XMPP_STAMP_FORMAT) response.addChild(delay) self.cache.user_unavailable(response) if self.logTraffic: log.debug("local presence: %s" % (response.toXml().encode('utf-8'), )) else: log.debug("local presence: %s" % (response['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 _authd(self, xs): component.Component._authd(self, xs) log.debug("connected to router.") self.xmlstream.addObserver("/presence", self.dispatch, ignore_consumed=False) self.xmlstream.addObserver("/iq", self.iq, 50) self.xmlstream.addObserver("/iq", self.dispatch, ignore_consumed=False) # <message/> has its own handler # resolver stuff resolver.ResolverMixIn._authd(self, xs)
def bounce(self, stanza): """Bounce stanzas as results.""" if not stanza.consumed: util.resetNamespace(stanza, self.namespace) if self.router.logTraffic: log.debug("bouncing %s" % (stanza.toXml(), )) stanza.consumed = True self.send(xmlstream.toResponse(stanza, 'result'))
def connectionInitialized(self, xs): """Called from the handler when a client has authenticated.""" userid, resource = util.jid_to_userid(xs.otherEntity, True) if userid not in self.streams: self.streams[userid] = {} if resource in self.streams[userid]: log.debug("resource conflict for %s" % (xs.otherEntity, )) self.streams[userid][resource].conflict() self.streams[userid][resource] = xs.manager
def dispatch(self, stanza): """Incoming message from router.""" if not stanza.consumed: if self.parent.logTraffic: log.debug("incoming message: %s" % (stanza.toXml().encode('utf-8'))) stanza.consumed = True util.resetNamespace(stanza, component.NS_COMPONENT_ACCEPT) self.parent.process_message(stanza)
def _authd(self, xs): component.Component._authd(self, xs) log.debug("connected to router") xs.addObserver("/iq", self.iq, 500) xs.addObserver("/presence", self.presence, 500) # bind to network route bind = domish.Element((None, 'bind')) bind['name'] = self.network bind.addElement((None, 'private')) xs.send(bind)
def _error(failure, stanza): log.debug("error: %s" % (failure, )) if isinstance(failure.value, oursql.IntegrityError): # duplicate key of userid: throttling e = error.StanzaError('service-unavailable', 'wait', 'Too many attempts.') else: e = error.StanzaError('service-unavailable', 'cancel', failure.getErrorMessage()) iq = xmlstream.toResponse(stanza, 'error') iq.addChild(e.getElement()) manager.send(iq, True)
def onPresenceAvailable(self, stanza): """Handle availability presence stanzas.""" if self.parent.logTraffic: log.debug("presence: %s" % (stanza.toXml().encode('utf-8'), )) else: log.debug("presence available from %s" % (stanza['from'], )) # update usercache with last seen and status user = jid.JID(stanza['from']) if user.user: self.user_available(stanza)
def upload(self, stanza): node = stanza.upload.getAttribute('node') log.debug("upload request received: %s" % (node, )) if node and node in self.serv_handlers: try: return self.serv_handlers[node].upload(stanza) except: import traceback traceback.print_exc() self.parent.error(stanza)
def outgoingDisconnected(self, xs): thisHost = xs.thisEntity.host otherHost = xs.otherEntity.host log.debug("Outgoing connection %d from %s to %s disconnected" % (xs.serial, thisHost, otherHost)) # TODO this actually does the same as invalidateConnection del self._outgoingStreams[otherHost] self.router.serverDisconnected(otherHost)
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 dispatch(self, stanza): """Handle incoming stanza from router to the proper server stream.""" if not stanza.consumed: stanza.consumed = True log.debug("incoming stanza from router %s" % (stanza.toXml().encode('utf-8'), )) to = stanza.getAttribute('to') if to is not None: to = jid.JID(to) if to.host != self.xmlstream.thisEntity.host: util.resetNamespace(stanza, component.NS_COMPONENT_ACCEPT) self.service.send(stanza)