/* * protocol.cpp - XMPP-Core protocol state machine * Copyright (C) 2004 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ // TODO: let the app know if tls is required // require mutual auth for server out/in // report ErrProtocol if server uses wrong NS // use send() instead of writeElement() in CoreProtocol #include"protocol.h" #include #include"base64.h" #include"hash.h" #ifdef XMPP_TEST #include"td.h" #endif using namespace XMPP; // printArray // // This function prints out an array of bytes as latin characters, converting // non-printable bytes into hex values as necessary. Useful for displaying // QByteArrays for debugging purposes. static TQString printArray(const TQByteArray &a) { TQString s; for(uint n = 0; n < a.size(); ++n) { unsigned char c = (unsigned char)a[(int)n]; if(c < 32 || c >= 127) { TQString str; str.sprintf("[%02x]", c); s += str; } else s += c; } return s; } // firstChildElement // // Get an element's first child element static TQDomElement firstChildElement(const TQDomElement &e) { for(TQDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { if(n.isElement()) return n.toElement(); } return TQDomElement(); } //---------------------------------------------------------------------------- // Version //---------------------------------------------------------------------------- Version::Version(int maj, int min) { major = maj; minor = min; } //---------------------------------------------------------------------------- // StreamFeatures //---------------------------------------------------------------------------- StreamFeatures::StreamFeatures() { tls_supported = false; sasl_supported = false; bind_supported = false; tls_required = false; } //---------------------------------------------------------------------------- // BasicProtocol //---------------------------------------------------------------------------- BasicProtocol::SASLCondEntry BasicProtocol::saslCondTable[] = { { "aborted", Aborted }, { "incorrect-encoding", IncorrectEncoding }, { "invalid-authzid", InvalidAuthzid }, { "invalid-mechanism", InvalidMech }, { "mechanism-too-weak", MechTooWeak }, { "not-authorized", NotAuthorized }, { "temporary-auth-failure", TemporaryAuthFailure }, { 0, 0 }, }; BasicProtocol::StreamCondEntry BasicProtocol::streamCondTable[] = { { "bad-format", BadFormat }, { "bad-namespace-prefix", BadNamespacePrefix }, { "conflict", Conflict }, { "connection-timeout", ConnectionTimeout }, { "host-gone", HostGone }, { "host-unknown", HostUnknown }, { "improper-addressing", ImproperAddressing }, { "internal-server-error", InternalServerError }, { "invalid-from", InvalidFrom }, { "invalid-id", InvalidId }, { "invalid-namespace", InvalidNamespace }, { "invalid-xml", InvalidXml }, { "not-authorized", StreamNotAuthorized }, { "policy-violation", PolicyViolation }, { "remote-connection-failed", RemoteConnectionFailed }, { "resource-constraint", ResourceConstraint }, { "restricted-xml", RestrictedXml }, { "see-other-host", SeeOtherHost }, { "system-shutdown", SystemShutdown }, { "undefined-condition", UndefinedCondition }, { "unsupported-encoding", UnsupportedEncoding }, { "unsupported-stanza-type", UnsupportedStanzaType }, { "unsupported-version", UnsupportedVersion }, { "xml-not-well-formed", XmlNotWellFormed }, { 0, 0 }, }; BasicProtocol::BasicProtocol() :XmlProtocol() { init(); } BasicProtocol::~BasicProtocol() { } void BasicProtocol::init() { errCond = -1; sasl_authed = false; doShutdown = false; delayedError = false; closeError = false; ready = false; stanzasPending = 0; stanzasWritten = 0; } void BasicProtocol::reset() { XmlProtocol::reset(); init(); to = TQString(); from = TQString(); id = TQString(); lang = TQString(); version = Version(1,0); errText = TQString(); errAppSpec = TQDomElement(); otherHost = TQString(); spare.resize(0); sasl_mech = TQString(); sasl_mechlist.clear(); sasl_step.resize(0); stanzaToRecv = TQDomElement(); sendList.clear(); } void BasicProtocol::sendStanza(const TQDomElement &e) { SendItem i; i.stanzaToSend = e; sendList += i; } void BasicProtocol::sendDirect(const TQString &s) { SendItem i; i.stringToSend = s; sendList += i; } void BasicProtocol::sendWhitespace() { SendItem i; i.doWhitespace = true; sendList += i; } TQDomElement BasicProtocol::recvStanza() { TQDomElement e = stanzaToRecv; stanzaToRecv = TQDomElement(); return e; } void BasicProtocol::shutdown() { doShutdown = true; } void BasicProtocol::shutdownWithError(int cond, const TQString &str) { otherHost = str; delayErrorAndClose(cond); } bool BasicProtocol::isReady() const { return ready; } void BasicProtocol::setReady(bool b) { ready = b; } TQString BasicProtocol::saslMech() const { return sasl_mech; } TQByteArray BasicProtocol::saslStep() const { return sasl_step; } void BasicProtocol::setSASLMechList(const TQStringList &list) { sasl_mechlist = list; } void BasicProtocol::setSASLFirst(const TQString &mech, const TQByteArray &step) { sasl_mech = mech; sasl_step = step; } void BasicProtocol::setSASLNext(const TQByteArray &step) { sasl_step = step; } void BasicProtocol::setSASLAuthed() { sasl_authed = true; } int BasicProtocol::stringToSASLCond(const TQString &s) { for(int n = 0; saslCondTable[n].str; ++n) { if(s == saslCondTable[n].str) return saslCondTable[n].cond; } return -1; } int BasicProtocol::stringToStreamCond(const TQString &s) { for(int n = 0; streamCondTable[n].str; ++n) { if(s == streamCondTable[n].str) return streamCondTable[n].cond; } return -1; } TQString BasicProtocol::saslCondToString(int x) { for(int n = 0; saslCondTable[n].str; ++n) { if(x == saslCondTable[n].cond) return saslCondTable[n].str; } return TQString(); } TQString BasicProtocol::streamCondToString(int x) { for(int n = 0; streamCondTable[n].str; ++n) { if(x == streamCondTable[n].cond) return streamCondTable[n].str; } return TQString(); } void BasicProtocol::extractStreamError(const TQDomElement &e) { TQString text; TQDomElement appSpec; TQDomElement t = firstChildElement(e); if(t.isNull() || t.namespaceURI() != NS_STREAMS) { // probably old-style error errCond = -1; errText = e.text(); } else errCond = stringToStreamCond(t.tagName()); if(errCond != -1) { if(errCond == SeeOtherHost) otherHost = t.text(); t = e.elementsByTagNameNS(NS_STREAMS, "text").item(0).toElement(); if(!t.isNull()) text = t.text(); // find first non-standard namespaced element TQDomNodeList nl = e.childNodes(); for(uint n = 0; n < nl.count(); ++n) { TQDomNode i = nl.item(n); if(i.isElement() && i.namespaceURI() != NS_STREAMS) { appSpec = i.toElement(); break; } } errText = text; errAppSpec = appSpec; } } void BasicProtocol::send(const TQDomElement &e, bool clip) { writeElement(e, TypeElement, false, clip); } void BasicProtocol::sendStreamError(int cond, const TQString &text, const TQDomElement &appSpec) { TQDomElement se = doc.createElementNS(NS_ETHERX, "stream:error"); TQDomElement err = doc.createElementNS(NS_STREAMS, streamCondToString(cond)); if(!otherHost.isEmpty()) err.appendChild(doc.createTextNode(otherHost)); se.appendChild(err); if(!text.isEmpty()) { TQDomElement te = doc.createElementNS(NS_STREAMS, "text"); te.setAttributeNS(NS_XML, "xml:lang", "en"); te.appendChild(doc.createTextNode(text)); se.appendChild(te); } se.appendChild(appSpec); writeElement(se, 100, false); } void BasicProtocol::sendStreamError(const TQString &text) { TQDomElement se = doc.createElementNS(NS_ETHERX, "stream:error"); se.appendChild(doc.createTextNode(text)); writeElement(se, 100, false); } bool BasicProtocol::errorAndClose(int cond, const TQString &text, const TQDomElement &appSpec) { closeError = true; errCond = cond; errText = text; errAppSpec = appSpec; sendStreamError(cond, text, appSpec); return close(); } bool BasicProtocol::error(int code) { event = EError; errorCode = code; return true; } void BasicProtocol::delayErrorAndClose(int cond, const TQString &text, const TQDomElement &appSpec) { errorCode = ErrStream; errCond = cond; errText = text; errAppSpec = appSpec; delayedError = true; } void BasicProtocol::delayError(int code) { errorCode = code; delayedError = true; } TQDomElement BasicProtocol::docElement() { // create the root element TQDomElement e = doc.createElementNS(NS_ETHERX, "stream:stream"); TQString defns = defaultNamespace(); TQStringList list = extraNamespaces(); // HACK: using attributes seems to be the only way to get additional namespaces in here if(!defns.isEmpty()) e.setAttribute("xmlns", defns); for(TQStringList::ConstIterator it = list.begin(); it != list.end();) { TQString prefix = *(it++); TQString uri = *(it++); e.setAttribute(TQString("xmlns:") + prefix, uri); } // additional attributes if(!isIncoming() && !to.isEmpty()) e.setAttribute("to", to); if(isIncoming() && !from.isEmpty()) e.setAttribute("from", from); if(!id.isEmpty()) e.setAttribute("id", id); if(!lang.isEmpty()) e.setAttributeNS(NS_XML, "xml:lang", lang); if(version.major > 0 || version.minor > 0) e.setAttribute("version", TQString::number(version.major) + '.' + TQString::number(version.minor)); return e; } void BasicProtocol::handleDocOpen(const Parser::Event &pe) { if(isIncoming()) { if(xmlEncoding() != "UTF-8") { delayErrorAndClose(UnsupportedEncoding); return; } } if(pe.namespaceURI() == NS_ETHERX && pe.localName() == "stream") { TQXmlAttributes atts = pe.atts(); // grab the version int major = 0; int minor = 0; TQString verstr = atts.value("version"); if(!verstr.isEmpty()) { int n = verstr.find('.'); if(n != -1) { major = verstr.mid(0, n).toInt(); minor = verstr.mid(n+1).toInt(); } else { major = verstr.toInt(); minor = 0; } } version = Version(major, minor); if(isIncoming()) { to = atts.value("to"); TQString peerLang = atts.value(NS_XML, "lang"); if(!peerLang.isEmpty()) lang = peerLang; } // outgoing else { from = atts.value("from"); lang = atts.value(NS_XML, "lang"); id = atts.value("id"); } handleStreamOpen(pe); } else { if(isIncoming()) delayErrorAndClose(BadFormat); else delayError(ErrProtocol); } } bool BasicProtocol::handleError() { if(isIncoming()) return errorAndClose(XmlNotWellFormed); else return error(ErrParse); } bool BasicProtocol::handleCloseFinished() { if(closeError) { event = EError; errorCode = ErrStream; // note: errCond and friends are already set at this point } else event = EClosed; return true; } bool BasicProtocol::doStep(const TQDomElement &e) { // handle pending error if(delayedError) { if(isIncoming()) return errorAndClose(errCond, errText, errAppSpec); else return error(errorCode); } // shutdown? if(doShutdown) { doShutdown = false; return close(); } if(!e.isNull()) { // check for error if(e.namespaceURI() == NS_ETHERX && e.tagName() == "error") { extractStreamError(e); return error(ErrStream); } } if(ready) { // stanzas written? if(stanzasWritten > 0) { --stanzasWritten; event = EStanzaSent; return true; } // send items? if(!sendList.isEmpty()) { SendItem i; { TQValueList::Iterator it = sendList.begin(); i = (*it); sendList.remove(it); } // outgoing stanza? if(!i.stanzaToSend.isNull()) { ++stanzasPending; writeElement(i.stanzaToSend, TypeStanza, true); event = ESend; } // direct send? else if(!i.stringToSend.isEmpty()) { writeString(i.stringToSend, TypeDirect, true); event = ESend; } // whitespace keepalive? else if(i.doWhitespace) { writeString("\n", TypePing, false); event = ESend; } return true; } else { // if we have pending outgoing stanzas, ask for write notification if(stanzasPending) notify |= NSend; } } return doStep2(e); } void BasicProtocol::itemWritten(int id, int) { if(id == TypeStanza) { --stanzasPending; ++stanzasWritten; } } TQString BasicProtocol::defaultNamespace() { // default none return TQString(); } TQStringList BasicProtocol::extraNamespaces() { // default none return TQStringList(); } void BasicProtocol::handleStreamOpen(const Parser::Event &) { // default does nothing } //---------------------------------------------------------------------------- // CoreProtocol //---------------------------------------------------------------------------- CoreProtocol::CoreProtocol() :BasicProtocol() { init(); } CoreProtocol::~CoreProtocol() { } void CoreProtocol::init() { step = Start; // ?? server = false; dialback = false; dialback_verify = false; // settings jid = Jid(); password = TQString(); oldOnly = false; allowPlain = false; doTLS = true; doAuth = true; doBinding = true; // input user = TQString(); host = TQString(); // status old = false; digest = false; tls_started = false; sasl_started = false; } void CoreProtocol::reset() { BasicProtocol::reset(); init(); } void CoreProtocol::startClientOut(const Jid &_jid, bool _oldOnly, bool tlsActive, bool _doAuth) { jid = _jid; to = _jid.domain(); oldOnly = _oldOnly; doAuth = _doAuth; tls_started = tlsActive; if(oldOnly) version = Version(0,0); startConnect(); } void CoreProtocol::startServerOut(const TQString &_to) { server = true; to = _to; startConnect(); } void CoreProtocol::startDialbackOut(const TQString &_to, const TQString &_from) { server = true; dialback = true; to = _to; self_from = _from; startConnect(); } void CoreProtocol::startDialbackVerifyOut(const TQString &_to, const TQString &_from, const TQString &id, const TQString &key) { server = true; dialback = true; dialback_verify = true; to = _to; self_from = _from; dialback_id = id; dialback_key = key; startConnect(); } void CoreProtocol::startClientIn(const TQString &_id) { id = _id; startAccept(); } void CoreProtocol::startServerIn(const TQString &_id) { server = true; id = _id; startAccept(); } void CoreProtocol::setLang(const TQString &s) { lang = s; } void CoreProtocol::setAllowTLS(bool b) { doTLS = b; } void CoreProtocol::setAllowBind(bool b) { doBinding = b; } void CoreProtocol::setAllowPlain(bool b) { allowPlain = b; } void CoreProtocol::setPassword(const TQString &s) { password = s; } void CoreProtocol::setFrom(const TQString &s) { from = s; } void CoreProtocol::setDialbackKey(const TQString &s) { dialback_key = s; } bool CoreProtocol::loginComplete() { setReady(true); event = EReady; step = Done; return true; } int CoreProtocol::getOldErrorCode(const TQDomElement &e) { TQDomElement err = e.elementsByTagNameNS(NS_CLIENT, "error").item(0).toElement(); if(err.isNull() || !err.hasAttribute("code")) return -1; return err.attribute("code").toInt(); } /*TQString CoreProtocol::xmlToString(const TQDomElement &e, bool clip) { // determine an appropriate 'fakeNS' to use TQString ns; if(e.prefix() == "stream") ns = NS_ETHERX; else if(e.prefix() == "db") ns = NS_DIALBACK; else ns = NS_CLIENT; return ::xmlToString(e, ns, "stream:stream", clip); }*/ bool CoreProtocol::stepAdvancesParser() const { if(stepRequiresElement()) return true; else if(isReady()) return true; return false; } // all element-needing steps need to be registered here bool CoreProtocol::stepRequiresElement() const { switch(step) { case GetFeatures: case GetTLSProceed: case GetSASLChallenge: case GetBindResponse: case GetAuthGetResponse: case GetAuthSetResponse: case GetRequest: case GetSASLResponse: return true; } return false; } void CoreProtocol::stringSend(const TQString &s) { #ifdef XMPP_TEST TD::outgoingTag(s); #endif } void CoreProtocol::stringRecv(const TQString &s) { #ifdef XMPP_TEST TD::incomingTag(s); #endif } TQString CoreProtocol::defaultNamespace() { if(server) return NS_SERVER; else return NS_CLIENT; } TQStringList CoreProtocol::extraNamespaces() { TQStringList list; if(dialback) { list += "db"; list += NS_DIALBACK; } return list; } void CoreProtocol::handleStreamOpen(const Parser::Event &pe) { if(isIncoming()) { TQString ns = pe.nsprefix(); TQString db; if(server) { db = pe.nsprefix("db"); if(!db.isEmpty()) dialback = true; } // verify namespace if((!server && ns != NS_CLIENT) || (server && ns != NS_SERVER) || (dialback && db != NS_DIALBACK)) { delayErrorAndClose(InvalidNamespace); return; } // verify version if(version.major < 1 && !dialback) { delayErrorAndClose(UnsupportedVersion); return; } } else { if(!dialback) { if(version.major >= 1 && !oldOnly) old = false; else old = true; } } } void CoreProtocol::elementSend(const TQDomElement &e) { #ifdef XMPP_TEST TD::outgoingXml(e); #endif } void CoreProtocol::elementRecv(const TQDomElement &e) { #ifdef XMPP_TEST TD::incomingXml(e); #endif } bool CoreProtocol::doStep2(const TQDomElement &e) { if(dialback) return dialbackStep(e); else return normalStep(e); } bool CoreProtocol::isValidStanza(const TQDomElement &e) const { TQString s = e.tagName(); if(e.namespaceURI() == (server ? NS_SERVER : NS_CLIENT) && (s == "message" || s == "presence" || s == "iq")) return true; else return false; } bool CoreProtocol::grabPendingItem(const Jid &to, const Jid &from, int type, DBItem *item) { for(TQValueList::Iterator it = dbpending.begin(); it != dbpending.end(); ++it) { const DBItem &i = *it; if(i.type == type && i.to.compare(to) && i.from.compare(from)) { const DBItem &i = (*it); *item = i; dbpending.remove(it); return true; } } return false; } bool CoreProtocol::dialbackStep(const TQDomElement &e) { if(step == Start) { setReady(true); step = Done; event = EReady; return true; } if(!dbrequests.isEmpty()) { // process a request DBItem i; { TQValueList::Iterator it = dbrequests.begin(); i = (*it); dbrequests.remove(it); } TQDomElement r; if(i.type == DBItem::ResultRequest) { r = doc.createElementNS(NS_DIALBACK, "db:result"); r.setAttribute("to", i.to.full()); r.setAttribute("from", i.from.full()); r.appendChild(doc.createTextNode(i.key)); dbpending += i; } else if(i.type == DBItem::ResultGrant) { r = doc.createElementNS(NS_DIALBACK, "db:result"); r.setAttribute("to", i.to.full()); r.setAttribute("from", i.from.full()); r.setAttribute("type", i.ok ? "valid" : "invalid"); if(i.ok) { i.type = DBItem::Validated; dbvalidated += i; } else { // TODO: disconnect after writing element } } else if(i.type == DBItem::VerifyRequest) { r = doc.createElementNS(NS_DIALBACK, "db:verify"); r.setAttribute("to", i.to.full()); r.setAttribute("from", i.from.full()); r.setAttribute("id", i.id); r.appendChild(doc.createTextNode(i.key)); dbpending += i; } // VerifyGrant else { r = doc.createElementNS(NS_DIALBACK, "db:verify"); r.setAttribute("to", i.to.full()); r.setAttribute("from", i.from.full()); r.setAttribute("id", i.id); r.setAttribute("type", i.ok ? "valid" : "invalid"); } writeElement(r, TypeElement, false); event = ESend; return true; } if(!e.isNull()) { if(e.namespaceURI() == NS_DIALBACK) { if(e.tagName() == "result") { Jid to, from; to.set(e.attribute("to"), ""); from.set(e.attribute("from"), ""); if(isIncoming()) { TQString key = e.text(); // TODO: report event } else { bool ok = (e.attribute("type") == "valid") ? true: false; DBItem i; if(grabPendingItem(from, to, DBItem::ResultRequest, &i)) { if(ok) { i.type = DBItem::Validated; i.ok = true; dbvalidated += i; // TODO: report event } else { // TODO: report event } } } } else if(e.tagName() == "verify") { Jid to, from; to.set(e.attribute("to"), ""); from.set(e.attribute("from"), ""); TQString id = e.attribute("id"); if(isIncoming()) { TQString key = e.text(); // TODO: report event } else { bool ok = (e.attribute("type") == "valid") ? true: false; DBItem i; if(grabPendingItem(from, to, DBItem::VerifyRequest, &i)) { if(ok) { // TODO: report event } else { // TODO: report event } } } } } else { if(isReady()) { if(isValidStanza(e)) { // TODO: disconnect if stanza is from unverified sender // TODO: ignore packets from receiving servers stanzaToRecv = e; event = EStanzaReady; return true; } } } } need = NNotify; notify |= NRecv; return false; } bool CoreProtocol::normalStep(const TQDomElement &e) { if(step == Start) { if(isIncoming()) { need = NSASLMechs; step = SendFeatures; return false; } else { if(old) { if(doAuth) step = HandleAuthGet; else return loginComplete(); } else step = GetFeatures; return processStep(); } } else if(step == HandleFeatures) { // deal with TLS? if(doTLS && !tls_started && !sasl_authed && features.tls_supported) { TQDomElement e = doc.createElementNS(NS_TLS, "starttls"); send(e, true); event = ESend; step = GetTLSProceed; return true; } // deal with SASL? if(!sasl_authed) { if(!features.sasl_supported) { // SASL MUST be supported event = EError; errorCode = ErrProtocol; return true; } #ifdef XMPP_TEST TD::msg("starting SASL authentication..."); #endif need = NSASLFirst; step = GetSASLFirst; return false; } if(server) { return loginComplete(); } else { if(!doBinding) return loginComplete(); } // deal with bind if(!features.bind_supported) { // bind MUST be supported event = EError; errorCode = ErrProtocol; return true; } TQDomElement e = doc.createElement("iq"); e.setAttribute("type", "set"); e.setAttribute("id", "bind_1"); TQDomElement b = doc.createElementNS(NS_BIND, "bind"); // request specific resource? TQString resource = jid.resource(); if(!resource.isEmpty()) { TQDomElement r = doc.createElement("resource"); r.appendChild(doc.createTextNode(jid.resource())); b.appendChild(r); } e.appendChild(b); send(e); event = ESend; step = GetBindResponse; return true; } else if(step == GetSASLFirst) { TQDomElement e = doc.createElementNS(NS_SASL, "auth"); e.setAttribute("mechanism", sasl_mech); if(!sasl_step.isEmpty()) { #ifdef XMPP_TEST TD::msg(TQString("SASL OUT: [%1]").arg(printArray(sasl_step))); #endif e.appendChild(doc.createTextNode(Base64::arrayToString(sasl_step))); } send(e, true); event = ESend; step = GetSASLChallenge; return true; } else if(step == GetSASLNext) { if(isIncoming()) { if(sasl_authed) { TQDomElement e = doc.createElementNS(NS_SASL, "success"); writeElement(e, TypeElement, false, true); event = ESend; step = IncHandleSASLSuccess; return true; } else { TQByteArray stepData = sasl_step; TQDomElement e = doc.createElementNS(NS_SASL, "challenge"); if(!stepData.isEmpty()) e.appendChild(doc.createTextNode(Base64::arrayToString(stepData))); writeElement(e, TypeElement, false, true); event = ESend; step = GetSASLResponse; return true; } } else { TQByteArray stepData = sasl_step; #ifdef XMPP_TEST TD::msg(TQString("SASL OUT: [%1]").arg(printArray(sasl_step))); #endif TQDomElement e = doc.createElementNS(NS_SASL, "response"); if(!stepData.isEmpty()) e.appendChild(doc.createTextNode(Base64::arrayToString(stepData))); send(e, true); event = ESend; step = GetSASLChallenge; return true; } } else if(step == HandleSASLSuccess) { need = NSASLLayer; spare = resetStream(); step = Start; return false; } else if(step == HandleAuthGet) { TQDomElement e = doc.createElement("iq"); e.setAttribute("to", to); e.setAttribute("type", "get"); e.setAttribute("id", "auth_1"); TQDomElement q = doc.createElementNS("jabber:iq:auth", "query"); TQDomElement u = doc.createElement("username"); u.appendChild(doc.createTextNode(jid.node())); q.appendChild(u); e.appendChild(q); send(e); event = ESend; step = GetAuthGetResponse; return true; } else if(step == HandleAuthSet) { TQDomElement e = doc.createElement("iq"); e.setAttribute("to", to); e.setAttribute("type", "set"); e.setAttribute("id", "auth_2"); TQDomElement q = doc.createElementNS("jabber:iq:auth", "query"); TQDomElement u = doc.createElement("username"); u.appendChild(doc.createTextNode(jid.node())); q.appendChild(u); TQDomElement p; if(digest) { // need SHA1 here if(!QCA::isSupported(QCA::CAP_SHA1)) QCA::insertProvider(createProviderHash()); p = doc.createElement("digest"); TQCString cs = id.utf8() + password.utf8(); p.appendChild(doc.createTextNode(QCA::SHA1::hashToString(cs))); } else { p = doc.createElement("password"); p.appendChild(doc.createTextNode(password)); } q.appendChild(p); TQDomElement r = doc.createElement("resource"); r.appendChild(doc.createTextNode(jid.resource())); q.appendChild(r); e.appendChild(q); send(e, true); event = ESend; step = GetAuthSetResponse; return true; } // server else if(step == SendFeatures) { TQDomElement f = doc.createElementNS(NS_ETHERX, "stream:features"); if(!tls_started && !sasl_authed) { // don't offer tls if we are already sasl'd TQDomElement tls = doc.createElementNS(NS_TLS, "starttls"); f.appendChild(tls); } if(sasl_authed) { if(!server) { TQDomElement bind = doc.createElementNS(NS_BIND, "bind"); f.appendChild(bind); } } else { TQDomElement mechs = doc.createElementNS(NS_SASL, "mechanisms"); for(TQStringList::ConstIterator it = sasl_mechlist.begin(); it != sasl_mechlist.end(); ++it) { TQDomElement m = doc.createElement("mechanism"); m.appendChild(doc.createTextNode(*it)); mechs.appendChild(m); } f.appendChild(mechs); } writeElement(f, TypeElement, false); event = ESend; step = GetRequest; return true; } // server else if(step == HandleTLS) { tls_started = true; need = NStartTLS; spare = resetStream(); step = Start; return false; } // server else if(step == IncHandleSASLSuccess) { event = ESASLSuccess; spare = resetStream(); step = Start; printf("sasl success\n"); return true; } else if(step == GetFeatures) { // we are waiting for stream features if(e.namespaceURI() == NS_ETHERX && e.tagName() == "features") { // extract features StreamFeatures f; TQDomElement s = e.elementsByTagNameNS(NS_TLS, "starttls").item(0).toElement(); if(!s.isNull()) { f.tls_supported = true; f.tls_required = s.elementsByTagNameNS(NS_TLS, "required").count() > 0; } TQDomElement m = e.elementsByTagNameNS(NS_SASL, "mechanisms").item(0).toElement(); if(!m.isNull()) { f.sasl_supported = true; TQDomNodeList l = m.elementsByTagNameNS(NS_SASL, "mechanism"); for(uint n = 0; n < l.count(); ++n) f.sasl_mechs += l.item(n).toElement().text(); } TQDomElement b = e.elementsByTagNameNS(NS_BIND, "bind").item(0).toElement(); if(!b.isNull()) f.bind_supported = true; if(f.tls_supported) { #ifdef XMPP_TEST TQString s = "STARTTLS is available"; if(f.tls_required) s += " (required)"; TD::msg(s); #endif } if(f.sasl_supported) { #ifdef XMPP_TEST TQString s = "SASL mechs:"; for(TQStringList::ConstIterator it = f.sasl_mechs.begin(); it != f.sasl_mechs.end(); ++it) s += TQString(" [%1]").arg((*it)); TD::msg(s); #endif } if(doAuth) { event = EFeatures; features = f; step = HandleFeatures; return true; } else return loginComplete(); } else { // ignore } } else if(step == GetTLSProceed) { // waiting for proceed to starttls if(e.namespaceURI() == NS_TLS) { if(e.tagName() == "proceed") { #ifdef XMPP_TEST TD::msg("Server wants us to proceed with ssl handshake"); #endif tls_started = true; need = NStartTLS; spare = resetStream(); step = Start; return false; } else if(e.tagName() == "failure") { event = EError; errorCode = ErrStartTLS; return true; } else { event = EError; errorCode = ErrProtocol; return true; } } else { // ignore } } else if(step == GetSASLChallenge) { // waiting for sasl challenge/success/fail if(e.namespaceURI() == NS_SASL) { if(e.tagName() == "challenge") { TQByteArray a = Base64::stringToArray(e.text()); #ifdef XMPP_TEST TD::msg(TQString("SASL IN: [%1]").arg(printArray(a))); #endif sasl_step = a; need = NSASLNext; step = GetSASLNext; return false; } else if(e.tagName() == "success") { sasl_authed = true; event = ESASLSuccess; step = HandleSASLSuccess; return true; } else if(e.tagName() == "failure") { TQDomElement t = firstChildElement(e); if(t.isNull() || t.namespaceURI() != NS_SASL) errCond = -1; else errCond = stringToSASLCond(t.tagName()); event = EError; errorCode = ErrAuth; return true; } else { event = EError; errorCode = ErrProtocol; return true; } } } else if(step == GetBindResponse) { if(e.namespaceURI() == NS_CLIENT && e.tagName() == "iq") { TQString type(e.attribute("type")); TQString id(e.attribute("id")); if(id == "bind_1" && (type == "result" || type == "error")) { if(type == "result") { TQDomElement b = e.elementsByTagNameNS(NS_BIND, "bind").item(0).toElement(); Jid j; if(!b.isNull()) { TQDomElement je = e.elementsByTagName("jid").item(0).toElement(); j = je.text(); } if(!j.isValid()) { event = EError; errorCode = ErrProtocol; return true; } jid = j; return loginComplete(); } else { errCond = -1; TQDomElement err = e.elementsByTagNameNS(NS_CLIENT, "error").item(0).toElement(); if(!err.isNull()) { // get error condition TQDomNodeList nl = err.childNodes(); TQDomElement t; for(uint n = 0; n < nl.count(); ++n) { TQDomNode i = nl.item(n); if(i.isElement()) { t = i.toElement(); break; } } if(!t.isNull() && t.namespaceURI() == NS_STANZAS) { TQString cond = t.tagName(); if(cond == "not-allowed") errCond = BindNotAllowed; else if(cond == "conflict") errCond = BindConflict; } } event = EError; errorCode = ErrBind; return true; } } else { // ignore } } else { // ignore } } else if(step == GetAuthGetResponse) { // waiting for an iq if(e.namespaceURI() == NS_CLIENT && e.tagName() == "iq") { Jid from(e.attribute("from")); TQString type(e.attribute("type")); TQString id(e.attribute("id")); bool okfrom = (from.isEmpty() || from.compare(Jid(to))); if(okfrom && id == "auth_1" && (type == "result" || type == "error")) { if(type == "result") { TQDomElement q = e.elementsByTagNameNS("jabber:iq:auth", "query").item(0).toElement(); if(q.isNull() || q.elementsByTagName("username").item(0).isNull() || q.elementsByTagName("resource").item(0).isNull()) { event = EError; errorCode = ErrProtocol; return true; } bool plain_supported = !q.elementsByTagName("password").item(0).isNull(); bool digest_supported = !q.elementsByTagName("digest").item(0).isNull(); if(!digest_supported && !plain_supported) { event = EError; errorCode = ErrProtocol; return true; } // plain text not allowed? if(!digest_supported && !allowPlain) { event = EError; errorCode = ErrPlain; return true; } digest = digest_supported; need = NPassword; step = HandleAuthSet; return false; } else { errCond = getOldErrorCode(e); event = EError; errorCode = ErrAuth; return true; } } else { // ignore } } else { // ignore } } else if(step == GetAuthSetResponse) { // waiting for an iq if(e.namespaceURI() == NS_CLIENT && e.tagName() == "iq") { Jid from(e.attribute("from")); TQString type(e.attribute("type")); TQString id(e.attribute("id")); bool okfrom = (from.isEmpty() || from.compare(Jid(to))); if(okfrom && id == "auth_2" && (type == "result" || type == "error")) { if(type == "result") { return loginComplete(); } else { errCond = getOldErrorCode(e); event = EError; errorCode = ErrAuth; return true; } } else { // ignore } } else { // ignore } } // server else if(step == GetRequest) { printf("get request: [%s], %s\n", e.namespaceURI().latin1(), e.tagName().latin1()); if(e.namespaceURI() == NS_TLS && e.localName() == "starttls") { // TODO: don't let this be done twice TQDomElement e = doc.createElementNS(NS_TLS, "proceed"); writeElement(e, TypeElement, false, true); event = ESend; step = HandleTLS; return true; } if(e.namespaceURI() == NS_SASL) { if(e.localName() == "auth") { if(sasl_started) { // TODO printf("error\n"); return false; } sasl_started = true; sasl_mech = e.attribute("mechanism"); // TODO: if child text missing, don't pass it sasl_step = Base64::stringToArray(e.text()); need = NSASLFirst; step = GetSASLNext; return false; } else { // TODO printf("unknown sasl tag\n"); return false; } } if(e.namespaceURI() == NS_CLIENT && e.tagName() == "iq") { TQDomElement b = e.elementsByTagNameNS(NS_BIND, "bind").item(0).toElement(); if(!b.isNull()) { TQDomElement res = b.elementsByTagName("resource").item(0).toElement(); TQString resource = res.text(); TQDomElement r = doc.createElement("iq"); r.setAttribute("type", "result"); r.setAttribute("id", e.attribute("id")); TQDomElement bind = doc.createElementNS(NS_BIND, "bind"); TQDomElement jid = doc.createElement("jid"); Jid j = user + '@' + host + '/' + resource; jid.appendChild(doc.createTextNode(j.full())); bind.appendChild(jid); r.appendChild(bind); writeElement(r, TypeElement, false); event = ESend; // TODO return true; } else { // TODO } } } else if(step == GetSASLResponse) { if(e.namespaceURI() == NS_SASL && e.localName() == "response") { sasl_step = Base64::stringToArray(e.text()); need = NSASLNext; step = GetSASLNext; return false; } } if(isReady()) { if(!e.isNull() && isValidStanza(e)) { stanzaToRecv = e; event = EStanzaReady; setIncomingAsExternal(); return true; } } need = NNotify; notify |= NRecv; return false; }