def handle(self, tree, msg, lastRetVal=None): iq = tree id = iq.get('id') if id: bind = iq[0] if len(bind) > 0: resource = bind[0].text else: # generate an id resource = generateId()[:6] # TODO: check that we don't already have such a resource jid = msg.conn.data['user']['jid'] bindResource(msg, resource) res = Element('iq', {'type' : 'result', 'id' : id}) bind = Element('bind', {'xmlns' : 'urn:ietf:params:xml:ns:xmpp-bind'}) jidEl = Element('jid') jidEl.text = '%s/%s' % (jid, resource) bind.append(jidEl) res.append(bind) return chainOutput(lastRetVal, res) else: logging.warning("[%s] No id in <iq>:\n%s", self.__class__, tostring(iq)) return lastRetVal
def handle(self, tree, msg, lastRetVal=None): iq = tree id = iq.get("id") if id: bind = iq[0] if len(bind) > 0: resource = bind[0].text else: # generate an id resource = generateId()[:6] # TODO: check that we don't already have such a resource jid = msg.conn.data["user"]["jid"] bindResource(msg, resource) res = Element("iq", {"type": "result", "id": id}) bind = Element("bind", {"xmlns": "urn:ietf:params:xml:ns:xmpp-bind"}) jidEl = Element("jid") jidEl.text = "%s/%s" % (jid, resource) bind.append(jidEl) res.append(bind) return chainOutput(lastRetVal, res) else: logging.warning("[%s] No id in <iq>:\n%s", self.__class__, tostring(iq)) return lastRetVal
def handle(self, tree, msg, lastRetVal=None): # The spec is silent on the case when a reinitialized <stream> is different # from the initial <stream>. In theory, there is never a need to change # any attributes in the new stream other than to change the ns prefix. # That seems like a dubious use case, so for now we just assume the stream # is the same as when it was first sent. This can be changed if it doesn't # play well with some clients. ns = msg.conn.parser.ns id = generateId() msg.conn.data['stream']['id'] = id msg.addTextOutput(u"<stream:stream from='%s' id='%s' xmlns='%s' " \ % (msg.conn.server.hostname, id, ns) + \ "xmlns:stream='http://etherx.jabber.org/streams' " + \ "version='1.0'>") if msg.conn.data['tls']['complete']: # TODO: go to features-auth return lastRetVal if msg.conn.data['sasl']['complete']: msg.setNextHandler('write') msg.setNextHandler('features-postauth') return lastRetVal
def handle(self, tree, msg, lastRetVal=None): iq = tree id = iq.get('id') if id: bind = iq[0] if len(bind) > 0: resource = bind[0].text else: # generate an id resource = generateId()[:6] # TODO: check that we don't already have such a resource jid = msg.conn.data['user']['jid'] bindResource(msg, resource) res = Element('iq', {'type': 'result', 'id': id}) bind = Element('bind', {'xmlns': 'urn:ietf:params:xml:ns:xmpp-bind'}) jidEl = Element('jid') jidEl.text = '%s/%s' % (jid, resource) bind.append(jidEl) res.append(bind) return chainOutput(lastRetVal, res) else: logging.warning("[%s] No id in <iq>:\n%s", self.__class__, tostring(iq)) return lastRetVal
def handle(self, tree, msg, lastRetVal=None): # Expat removes the xmlns attributes, so we save them in the parser # class and check them here. ns = msg.conn.parser.ns if ns == 'jabber:client': streamType = 'client' elif ns == 'jabber:server': streamType = 'server' else: # TODO: send <bad-namespace-prefix/> logging.warning("[%s] Unknown stream namespace: %s", self.__class__, ns) return lastRetVal # TODO: version check id = generateId() msg.conn.data['stream']['in-stream'] = True msg.conn.data['stream']['type'] = streamType msg.conn.data['stream']['id'] = id # no one should need to modify this, so we don't pass it along # to the next handler, but just add it to the socket write queue # commenting this out for now as it causes expat problems msg.addTextOutput(u"<?xml version='1.0'?>" + \ #msg.addTextOutput( "<stream:stream from='%s' id='%s' xmlns='%s' " \ % (msg.conn.server.hostname, id, ns) + \ "xmlns:stream='http://etherx.jabber.org/streams' " + \ "version='1.0'>")
def handle(self, tree, msg, lastRetVal=None): # The spec is silent on the case when a reinitialized <stream> is different # from the initial <stream>. In theory, there is never a need to change # any attributes in the new stream other than to change the ns prefix. # That seems like a dubious use case, so for now we just assume the stream # is the same as when it was first sent. This can be changed if it doesn't # play well with some clients. ns = msg.conn.parser.ns id = generateId() msg.conn.data["stream"]["id"] = id msg.addTextOutput( u"<stream:stream from='%s' id='%s' xmlns='%s' " % (msg.conn.server.hostname, id, ns) + "xmlns:stream='http://etherx.jabber.org/streams' " + "version='1.0'>" ) if msg.conn.data["tls"]["complete"]: # TODO: go to features-auth return lastRetVal if msg.conn.data["sasl"]["complete"]: msg.setNextHandler("write") msg.setNextHandler("features-postauth") return lastRetVal
def handle(self, tree, msg, lastRetVal=None): # Expat removes the xmlns attributes, so we save them in the parser # class and check them here. ns = msg.conn.parser.ns if ns == "jabber:client": streamType = "client" elif ns == "jabber:server": streamType = "server" else: # TODO: send <bad-namespace-prefix/> logging.warning("[%s] Unknown stream namespace: %s", self.__class__, ns) return lastRetVal # TODO: version check id = generateId() msg.conn.data["stream"]["in-stream"] = True msg.conn.data["stream"]["type"] = streamType msg.conn.data["stream"]["id"] = id # no one should need to modify this, so we don't pass it along # to the next handler, but just add it to the socket write queue # commenting this out for now as it causes expat problems msg.addTextOutput( u"<?xml version='1.0'?>" + # msg.addTextOutput( "<stream:stream from='%s' id='%s' xmlns='%s' " % (msg.conn.server.hostname, id, ns) + "xmlns:stream='http://etherx.jabber.org/streams' " + "version='1.0'>" )
def bindResource(msg, resource): """Records the resource binding. Returns the bare JID. This should only be called from the C2Sserver. """ data = msg.conn.data server = msg.conn.server jid = data["user"]["jid"] # check if we have this resource already if server.data["resources"].has_key(jid) and server.data["resources"][jid].has_key(resource): # create our own resource = resource + generateId()[:6] data["user"]["resource"] = resource # record the resource in the JID object of the (JID, Connection) pair # this is for local delivery lookups server.conns[msg.conn.id][0].resource = resource # save the jid/resource in the server's global storage if not server.data["resources"].has_key(jid): server.data["resources"][jid] = {} server.data["resources"][jid][resource] = msg.conn
def bindResource(msg, resource): """Records the resource binding. Returns the bare JID. This should only be called from the C2Sserver. """ data = msg.conn.data server = msg.conn.server jid = data['user']['jid'] # check if we have this resource already if server.data['resources'].has_key(jid) and \ server.data['resources'][jid].has_key(resource): # create our own resource = resource + generateId()[:6] data['user']['resource'] = resource # record the resource in the JID object of the (JID, Connection) pair # this is for local delivery lookups server.conns[msg.conn.id][0].resource = resource # save the jid/resource in the server's global storage if not server.data['resources'].has_key(jid): server.data['resources'][jid] = {} server.data['resources'][jid][resource] = msg.conn
def act(): # we have to be passed a tree to work # or a tuple with routingData and a tree if not isinstance(lastRetVal, list): logging.warning('[%s] lastRetVal is not a list', self.__class__) return if isinstance(lastRetVal[-1], Element): if lastRetVal[-1].tag.find('query') == -1: logging.warning('[%s] Got a non-query Element last return value' +\ '. Last return value: %s', self.__class__, lastRetVal) elif isinstance(lastRetVal[-1], tuple): if not isinstance(lastRetVal[-1][0], dict) \ or not isinstance(lastRetVal[-1][1], Element): logging.warning('[%s] Got a non-query Element last return value' +\ '. Last return value: %s', self.__class__, lastRetVal) return else: logging.warning('[%s] Roster push needs either a <query> Element ' +\ 'as the last item in lastRetVal or a tuple ' + \ 'with (routeData, query Element)', self.__class__) return # this is the roster <query> that we'll send # it could be a tuple if we got routing data as well query = lastRetVal.pop(-1) routeData = None # did we get routing data (from S2S) if isinstance(query, tuple): routeData = query[0] query = query[1] if routeData: jid = routeData['jid'] resources = routeData['resources'] else: jid = msg.conn.data['user']['jid'] resource = msg.conn.data['user']['resource'] resources = msg.conn.server.data['resources'][jid] for res, con in resources.items(): # don't send the roster to clients that didn't request it if con.data['user']['requestedRoster']: iq = Element('iq', { 'to' : '%s/%s' % (jid, res), 'type' : 'set', 'id' : generateId()[:10] }) iq.append(query) # TODO: remove this. debug. logging.debug("Sending " + tostring(iq)) con.send(tostring(iq)) if tree.tag == '{jabber:client}iq' and tree.get('id'): # send an ack to client if this is in reply to a roster get/set id = tree.get('id') d = { 'to' : '%s/%s' % (jid, resource), 'type' : 'result', 'id' : id } iq = Element('iq', d) return chainOutput(lastRetVal, iq)
def act(): # we have to be passed a tree to work # or a tuple with routingData and a tree if not isinstance(lastRetVal, list): logging.warning("[%s] lastRetVal is not a list", self.__class__) return if isinstance(lastRetVal[-1], Element): if lastRetVal[-1].tag.find("query") == -1: logging.warning( "[%s] Got a non-query Element last return value" + ". Last return value: %s", self.__class__, lastRetVal, ) elif isinstance(lastRetVal[-1], tuple): if not isinstance(lastRetVal[-1][0], dict) or not isinstance(lastRetVal[-1][1], Element): logging.warning( "[%s] Got a non-query Element last return value" + ". Last return value: %s", self.__class__, lastRetVal, ) return else: logging.warning( "[%s] Roster push needs either a <query> Element " + "as the last item in lastRetVal or a tuple " + "with (routeData, query Element)", self.__class__, ) return # this is the roster <query> that we'll send # it could be a tuple if we got routing data as well query = lastRetVal.pop(-1) routeData = None # did we get routing data (from S2S) if isinstance(query, tuple): routeData = query[0] query = query[1] if routeData: jid = routeData["jid"] resources = routeData["resources"] else: jid = msg.conn.data["user"]["jid"] resource = msg.conn.data["user"]["resource"] resources = msg.conn.server.data["resources"][jid] for res, con in resources.items(): # don't send the roster to clients that didn't request it if con.data["user"]["requestedRoster"]: iq = Element("iq", {"to": "%s/%s" % (jid, res), "type": "set", "id": generateId()[:10]}) iq.append(query) # TODO: remove this. debug. logging.debug("Sending " + tostring(iq)) con.send(tostring(iq)) if tree.tag == "{jabber:client}iq" and tree.get("id"): # send an ack to client if this is in reply to a roster get/set id = tree.get("id") d = {"to": "%s/%s" % (jid, resource), "type": "result", "id": id} iq = Element("iq", d) return chainOutput(lastRetVal, iq)
def handle(self, data=None): """Performs DIGEST-MD5 auth based on current state. data -- either None for initial challenge, base64-encoded text when the client responds to challenge 1, or the tree when the client responds to challenge 2. """ # TODO: authz # TODO: subsequent auth qop = 'qop="auth"' charset = 'charset=utf-8' algo = 'algorithm=md5-sess' if self.state == SASLDigestMD5.INIT: # initial challenge self.nonce = generateId() self.state = SASLDigestMD5.SENT_CHALLENGE1 nonce = 'nonce="%s"' % self.nonce realm = 'realm="%s"' % self.realm res = Element('challenge', {'xmlns': 'urn:ietf:params:xml:ns:xmpp-sasl'}) res.text = base64.b64encode(','.join( [realm, qop, nonce, charset, algo])) return res elif self.state == SASLDigestMD5.SENT_CHALLENGE1 and data: # response to client's reponse (ie. challenge 2) try: text = fromBase64(data) except: raise SASLIncorrectEncodingError pairs = self._parse(text) try: username = pairs['username'] nonce = pairs['nonce'] realm = pairs['realm'] cnonce = pairs['cnonce'] nc = pairs['nc'] qop = pairs['qop'] response = pairs['response'] digest_uri = pairs['digest-uri'] except KeyError: self._handleFailure() raise SASLAuthError self.username = username # authz is ignored for now if nonce != self.nonce or realm != self.realm \ or int(nc, 16) != 1 or qop[0] != 'auth' or not response\ or not digest_uri: self._handleFailure() raise SASLAuthError # fetch the password now con = DBautocommit() c = con.cursor() c.execute( "SELECT password FROM jids WHERE \ jid = ?", (username + '@%s' % self.msg.conn.server.hostname, )) for row in c: password = row['password'] break else: self._handleFailure() c.close() con.close() raise SASLAuthError c.close() con.close() # compute the digest as per RFC 2831 a1 = "%s:%s:%s" % (H("%s:%s:%s" % (username, realm, password)), nonce, cnonce) a2 = ":%s" % digest_uri a2client = "AUTHENTICATE:%s" % digest_uri digest = HEX( KD( HEX(H(a1)), "%s:%s:%s:%s:%s" % (nonce, nc, cnonce, "auth", HEX(H(a2client))))) if digest == response: rspauth = HEX( KD( HEX(H(a1)), "%s:%s:%s:%s:%s" % (nonce, nc, cnonce, "auth", HEX(H(a2))))) self.state = SASLDigestMD5.SENT_CHALLENGE2 res = Element('challenge', {'xmlns': 'urn:ietf:params:xml:ns:xmpp-sasl'}) res.text = base64.b64encode(u"rspauth=%s" % rspauth) return res else: self._handleFailure() raise SASLAuthError elif self.state == SASLDigestMD5.SENT_CHALLENGE2 and isinstance( data, Element): # expect to get <response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/> respInd = data.tag.find( '{urn:ietf:params:xml:ns:xmpp-sasl}response') d = self.msg.conn.data if respInd != -1 and len(data) == 0: self.state = SASLDigestMD5.INIT d['sasl']['complete'] = True d['sasl']['in-progress'] = False d['user']['jid'] = '%s@%s' % (self.username, self.msg.conn.server.hostname) # record the JID for local delivery self.msg.conn.server.conns[self.msg.conn.id] = (JID( d['user']['jid']), self.msg.conn) self.msg.conn.parser.resetParser() res = Element('success', {'xmlns': 'urn:ietf:params:xml:ns:xmpp-sasl'}) return res else: self._handleFailure() raise SASLAuthError else: self._handleFailure() raise SASLAuthError
def handle(self, data=None): """Performs DIGEST-MD5 auth based on current state. data -- either None for initial challenge, base64-encoded text when the client responds to challenge 1, or the tree when the client responds to challenge 2. """ # TODO: authz # TODO: subsequent auth qop = 'qop="auth"' charset = 'charset=utf-8' algo = 'algorithm=md5-sess' if self.state == SASLDigestMD5.INIT: # initial challenge self.nonce = generateId() self.state = SASLDigestMD5.SENT_CHALLENGE1 nonce = 'nonce="%s"' % self.nonce realm = 'realm="%s"' % self.realm res = Element('challenge', {'xmlns' : 'urn:ietf:params:xml:ns:xmpp-sasl'}) res.text = base64.b64encode(','.join([realm, qop, nonce, charset, algo])) return res elif self.state == SASLDigestMD5.SENT_CHALLENGE1 and data: # response to client's reponse (ie. challenge 2) try: text = fromBase64(data) except: raise SASLIncorrectEncodingError pairs = self._parse(text) try: username = pairs['username'] nonce = pairs['nonce'] realm = pairs['realm'] cnonce = pairs['cnonce'] nc = pairs['nc'] qop = pairs['qop'] response = pairs['response'] digest_uri = pairs['digest-uri'] except KeyError: self._handleFailure() raise SASLAuthError self.username = username # authz is ignored for now if nonce != self.nonce or realm != self.realm \ or int(nc, 16) != 1 or qop[0] != 'auth' or not response\ or not digest_uri: self._handleFailure() raise SASLAuthError # fetch the password now con = DBautocommit() c = con.cursor() c.execute("SELECT password FROM jids WHERE \ jid = ?", (username + '@%s' % self.msg.conn.server.hostname,)) for row in c: password = row['password'] break else: self._handleFailure() c.close() con.close() raise SASLAuthError c.close() con.close() # compute the digest as per RFC 2831 a1 = "%s:%s:%s" % (H("%s:%s:%s" % (username, realm, password)), nonce, cnonce) a2 = ":%s" % digest_uri a2client = "AUTHENTICATE:%s" % digest_uri digest = HEX(KD(HEX(H(a1)), "%s:%s:%s:%s:%s" % (nonce, nc, cnonce, "auth", HEX(H(a2client))))) if digest == response: rspauth = HEX(KD(HEX(H(a1)), "%s:%s:%s:%s:%s" % (nonce, nc, cnonce, "auth", HEX(H(a2))))) self.state = SASLDigestMD5.SENT_CHALLENGE2 res = Element('challenge', {'xmlns' : 'urn:ietf:params:xml:ns:xmpp-sasl'}) res.text = base64.b64encode(u"rspauth=%s" % rspauth) return res else: self._handleFailure() raise SASLAuthError elif self.state == SASLDigestMD5.SENT_CHALLENGE2 and isinstance(data, Element): # expect to get <response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/> respInd = data.tag.find('{urn:ietf:params:xml:ns:xmpp-sasl}response') d = self.msg.conn.data if respInd != -1 and len(data) == 0: self.state = SASLDigestMD5.INIT d['sasl']['complete'] = True d['sasl']['in-progress'] = False d['user']['jid'] = '%s@%s' % (self.username, self.msg.conn.server.hostname) # record the JID for local delivery self.msg.conn.server.conns[self.msg.conn.id] = (JID(d['user']['jid']), self.msg.conn) self.msg.conn.parser.resetParser() res = Element('success', {'xmlns' : 'urn:ietf:params:xml:ns:xmpp-sasl'}) return res else: self._handleFailure() raise SASLAuthError else: self._handleFailure() raise SASLAuthError
def act(): # we have to be passed a tree to work # or a tuple with routingData and a tree if not isinstance(lastRetVal, list): logging.warning('[%s] lastRetVal is not a list', self.__class__) return if isinstance(lastRetVal[-1], Element): if lastRetVal[-1].tag.find('query') == -1: logging.warning('[%s] Got a non-query Element last return value' +\ '. Last return value: %s', self.__class__, lastRetVal) elif isinstance(lastRetVal[-1], tuple): if not isinstance(lastRetVal[-1][0], dict) \ or not isinstance(lastRetVal[-1][1], Element): logging.warning('[%s] Got a non-query Element last return value' +\ '. Last return value: %s', self.__class__, lastRetVal) return else: logging.warning('[%s] Roster push needs either a <query> Element ' +\ 'as the last item in lastRetVal or a tuple ' + \ 'with (routeData, query Element)', self.__class__) return # this is the roster <query> that we'll send # it could be a tuple if we got routing data as well query = lastRetVal.pop(-1) routeData = None # did we get routing data (from S2S) if isinstance(query, tuple): routeData = query[0] query = query[1] if routeData: jid = routeData['jid'] resources = routeData['resources'] else: jid = msg.conn.data['user']['jid'] resource = msg.conn.data['user']['resource'] resources = msg.conn.server.data['resources'][jid] for res, con in resources.items(): # don't send the roster to clients that didn't request it if con.data['user']['requestedRoster']: iq = Element( 'iq', { 'to': '%s/%s' % (jid, res), 'type': 'set', 'id': generateId()[:10] }) iq.append(query) # TODO: remove this. debug. logging.debug("Sending " + tostring(iq)) con.send(tostring(iq)) if tree.tag == '{jabber:client}iq' and tree.get('id'): # send an ack to client if this is in reply to a roster get/set id = tree.get('id') d = { 'to': '%s/%s' % (jid, resource), 'type': 'result', 'id': id } iq = Element('iq', d) return chainOutput(lastRetVal, iq)