diff options
author | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
---|---|---|
committer | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
commit | bcb704366cb5e333a626c18c308c7e0448a8e69f (patch) | |
tree | f0d6ab7d78ecdd9207cf46536376b44b91a1ca71 /kopete/protocols/jabber/libiris/iris/xmpp-core/protocol.cpp | |
download | tdenetwork-bcb704366cb5e333a626c18c308c7e0448a8e69f.tar.gz tdenetwork-bcb704366cb5e333a626c18c308c7e0448a8e69f.zip |
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdenetwork@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kopete/protocols/jabber/libiris/iris/xmpp-core/protocol.cpp')
-rw-r--r-- | kopete/protocols/jabber/libiris/iris/xmpp-core/protocol.cpp | 1595 |
1 files changed, 1595 insertions, 0 deletions
diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/protocol.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/protocol.cpp new file mode 100644 index 00000000..dfd3253c --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/protocol.cpp @@ -0,0 +1,1595 @@ +/* + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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<qca.h> +#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 QString printArray(const QByteArray &a) +{ + QString s; + for(uint n = 0; n < a.size(); ++n) { + unsigned char c = (unsigned char)a[(int)n]; + if(c < 32 || c >= 127) { + QString str; + str.sprintf("[%02x]", c); + s += str; + } + else + s += c; + } + return s; +} + +// firstChildElement +// +// Get an element's first child element +static QDomElement firstChildElement(const QDomElement &e) +{ + for(QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { + if(n.isElement()) + return n.toElement(); + } + return QDomElement(); +} + +//---------------------------------------------------------------------------- +// 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 = QString(); + from = QString(); + id = QString(); + lang = QString(); + version = Version(1,0); + errText = QString(); + errAppSpec = QDomElement(); + otherHost = QString(); + spare.resize(0); + sasl_mech = QString(); + sasl_mechlist.clear(); + sasl_step.resize(0); + stanzaToRecv = QDomElement(); + sendList.clear(); +} + +void BasicProtocol::sendStanza(const QDomElement &e) +{ + SendItem i; + i.stanzaToSend = e; + sendList += i; +} + +void BasicProtocol::sendDirect(const QString &s) +{ + SendItem i; + i.stringToSend = s; + sendList += i; +} + +void BasicProtocol::sendWhitespace() +{ + SendItem i; + i.doWhitespace = true; + sendList += i; +} + +QDomElement BasicProtocol::recvStanza() +{ + QDomElement e = stanzaToRecv; + stanzaToRecv = QDomElement(); + return e; +} + +void BasicProtocol::shutdown() +{ + doShutdown = true; +} + +void BasicProtocol::shutdownWithError(int cond, const QString &str) +{ + otherHost = str; + delayErrorAndClose(cond); +} + +bool BasicProtocol::isReady() const +{ + return ready; +} + +void BasicProtocol::setReady(bool b) +{ + ready = b; +} + +QString BasicProtocol::saslMech() const +{ + return sasl_mech; +} + +QByteArray BasicProtocol::saslStep() const +{ + return sasl_step; +} + +void BasicProtocol::setSASLMechList(const QStringList &list) +{ + sasl_mechlist = list; +} + +void BasicProtocol::setSASLFirst(const QString &mech, const QByteArray &step) +{ + sasl_mech = mech; + sasl_step = step; +} + +void BasicProtocol::setSASLNext(const QByteArray &step) +{ + sasl_step = step; +} + +void BasicProtocol::setSASLAuthed() +{ + sasl_authed = true; +} + +int BasicProtocol::stringToSASLCond(const QString &s) +{ + for(int n = 0; saslCondTable[n].str; ++n) { + if(s == saslCondTable[n].str) + return saslCondTable[n].cond; + } + return -1; +} + +int BasicProtocol::stringToStreamCond(const QString &s) +{ + for(int n = 0; streamCondTable[n].str; ++n) { + if(s == streamCondTable[n].str) + return streamCondTable[n].cond; + } + return -1; +} + +QString BasicProtocol::saslCondToString(int x) +{ + for(int n = 0; saslCondTable[n].str; ++n) { + if(x == saslCondTable[n].cond) + return saslCondTable[n].str; + } + return QString(); +} + +QString BasicProtocol::streamCondToString(int x) +{ + for(int n = 0; streamCondTable[n].str; ++n) { + if(x == streamCondTable[n].cond) + return streamCondTable[n].str; + } + return QString(); +} + +void BasicProtocol::extractStreamError(const QDomElement &e) +{ + QString text; + QDomElement appSpec; + + QDomElement 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 + QDomNodeList nl = e.childNodes(); + for(uint n = 0; n < nl.count(); ++n) { + QDomNode i = nl.item(n); + if(i.isElement() && i.namespaceURI() != NS_STREAMS) { + appSpec = i.toElement(); + break; + } + } + + errText = text; + errAppSpec = appSpec; + } +} + +void BasicProtocol::send(const QDomElement &e, bool clip) +{ + writeElement(e, TypeElement, false, clip); +} + +void BasicProtocol::sendStreamError(int cond, const QString &text, const QDomElement &appSpec) +{ + QDomElement se = doc.createElementNS(NS_ETHERX, "stream:error"); + QDomElement err = doc.createElementNS(NS_STREAMS, streamCondToString(cond)); + if(!otherHost.isEmpty()) + err.appendChild(doc.createTextNode(otherHost)); + se.appendChild(err); + if(!text.isEmpty()) { + QDomElement 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 QString &text) +{ + QDomElement se = doc.createElementNS(NS_ETHERX, "stream:error"); + se.appendChild(doc.createTextNode(text)); + + writeElement(se, 100, false); +} + +bool BasicProtocol::errorAndClose(int cond, const QString &text, const QDomElement &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 QString &text, const QDomElement &appSpec) +{ + errorCode = ErrStream; + errCond = cond; + errText = text; + errAppSpec = appSpec; + delayedError = true; +} + +void BasicProtocol::delayError(int code) +{ + errorCode = code; + delayedError = true; +} + +QDomElement BasicProtocol::docElement() +{ + // create the root element + QDomElement e = doc.createElementNS(NS_ETHERX, "stream:stream"); + + QString defns = defaultNamespace(); + QStringList 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(QStringList::ConstIterator it = list.begin(); it != list.end();) { + QString prefix = *(it++); + QString uri = *(it++); + e.setAttribute(QString("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", QString::number(version.major) + '.' + QString::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") { + QXmlAttributes atts = pe.atts(); + + // grab the version + int major = 0; + int minor = 0; + QString 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"); + QString 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 QDomElement &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; + { + QValueList<SendItem>::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; + } +} + +QString BasicProtocol::defaultNamespace() +{ + // default none + return QString(); +} + +QStringList BasicProtocol::extraNamespaces() +{ + // default none + return QStringList(); +} + +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 = QString(); + oldOnly = false; + allowPlain = false; + doTLS = true; + doAuth = true; + doBinding = true; + + // input + user = QString(); + host = QString(); + + // 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 QString &_to) +{ + server = true; + to = _to; + startConnect(); +} + +void CoreProtocol::startDialbackOut(const QString &_to, const QString &_from) +{ + server = true; + dialback = true; + to = _to; + self_from = _from; + startConnect(); +} + +void CoreProtocol::startDialbackVerifyOut(const QString &_to, const QString &_from, const QString &id, const QString &key) +{ + server = true; + dialback = true; + dialback_verify = true; + to = _to; + self_from = _from; + dialback_id = id; + dialback_key = key; + startConnect(); +} + +void CoreProtocol::startClientIn(const QString &_id) +{ + id = _id; + startAccept(); +} + +void CoreProtocol::startServerIn(const QString &_id) +{ + server = true; + id = _id; + startAccept(); +} + +void CoreProtocol::setLang(const QString &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 QString &s) +{ + password = s; +} + +void CoreProtocol::setFrom(const QString &s) +{ + from = s; +} + +void CoreProtocol::setDialbackKey(const QString &s) +{ + dialback_key = s; +} + +bool CoreProtocol::loginComplete() +{ + setReady(true); + + event = EReady; + step = Done; + return true; +} + +int CoreProtocol::getOldErrorCode(const QDomElement &e) +{ + QDomElement err = e.elementsByTagNameNS(NS_CLIENT, "error").item(0).toElement(); + if(err.isNull() || !err.hasAttribute("code")) + return -1; + return err.attribute("code").toInt(); +} + +/*QString CoreProtocol::xmlToString(const QDomElement &e, bool clip) +{ + // determine an appropriate 'fakeNS' to use + QString 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 QString &s) +{ +#ifdef XMPP_TEST + TD::outgoingTag(s); +#endif +} + +void CoreProtocol::stringRecv(const QString &s) +{ +#ifdef XMPP_TEST + TD::incomingTag(s); +#endif +} + +QString CoreProtocol::defaultNamespace() +{ + if(server) + return NS_SERVER; + else + return NS_CLIENT; +} + +QStringList CoreProtocol::extraNamespaces() +{ + QStringList list; + if(dialback) { + list += "db"; + list += NS_DIALBACK; + } + return list; +} + +void CoreProtocol::handleStreamOpen(const Parser::Event &pe) +{ + if(isIncoming()) { + QString ns = pe.nsprefix(); + QString 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 QDomElement &e) +{ +#ifdef XMPP_TEST + TD::outgoingXml(e); +#endif +} + +void CoreProtocol::elementRecv(const QDomElement &e) +{ +#ifdef XMPP_TEST + TD::incomingXml(e); +#endif +} + +bool CoreProtocol::doStep2(const QDomElement &e) +{ + if(dialback) + return dialbackStep(e); + else + return normalStep(e); +} + +bool CoreProtocol::isValidStanza(const QDomElement &e) const +{ + QString 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(QValueList<DBItem>::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 QDomElement &e) +{ + if(step == Start) { + setReady(true); + step = Done; + event = EReady; + return true; + } + + if(!dbrequests.isEmpty()) { + // process a request + DBItem i; + { + QValueList<DBItem>::Iterator it = dbrequests.begin(); + i = (*it); + dbrequests.remove(it); + } + + QDomElement 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()) { + QString 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"), ""); + QString id = e.attribute("id"); + if(isIncoming()) { + QString 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 QDomElement &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) { + QDomElement 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; + } + + QDomElement e = doc.createElement("iq"); + e.setAttribute("type", "set"); + e.setAttribute("id", "bind_1"); + QDomElement b = doc.createElementNS(NS_BIND, "bind"); + + // request specific resource? + QString resource = jid.resource(); + if(!resource.isEmpty()) { + QDomElement 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) { + QDomElement e = doc.createElementNS(NS_SASL, "auth"); + e.setAttribute("mechanism", sasl_mech); + if(!sasl_step.isEmpty()) { +#ifdef XMPP_TEST + TD::msg(QString("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) { + QDomElement e = doc.createElementNS(NS_SASL, "success"); + writeElement(e, TypeElement, false, true); + event = ESend; + step = IncHandleSASLSuccess; + return true; + } + else { + QByteArray stepData = sasl_step; + QDomElement 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 { + QByteArray stepData = sasl_step; +#ifdef XMPP_TEST + TD::msg(QString("SASL OUT: [%1]").arg(printArray(sasl_step))); +#endif + QDomElement 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) { + QDomElement e = doc.createElement("iq"); + e.setAttribute("to", to); + e.setAttribute("type", "get"); + e.setAttribute("id", "auth_1"); + QDomElement q = doc.createElementNS("jabber:iq:auth", "query"); + QDomElement 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) { + QDomElement e = doc.createElement("iq"); + e.setAttribute("to", to); + e.setAttribute("type", "set"); + e.setAttribute("id", "auth_2"); + QDomElement q = doc.createElementNS("jabber:iq:auth", "query"); + QDomElement u = doc.createElement("username"); + u.appendChild(doc.createTextNode(jid.node())); + q.appendChild(u); + QDomElement p; + if(digest) { + // need SHA1 here + if(!QCA::isSupported(QCA::CAP_SHA1)) + QCA::insertProvider(createProviderHash()); + + p = doc.createElement("digest"); + QCString 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); + QDomElement 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) { + QDomElement f = doc.createElementNS(NS_ETHERX, "stream:features"); + if(!tls_started && !sasl_authed) { // don't offer tls if we are already sasl'd + QDomElement tls = doc.createElementNS(NS_TLS, "starttls"); + f.appendChild(tls); + } + + if(sasl_authed) { + if(!server) { + QDomElement bind = doc.createElementNS(NS_BIND, "bind"); + f.appendChild(bind); + } + } + else { + QDomElement mechs = doc.createElementNS(NS_SASL, "mechanisms"); + for(QStringList::ConstIterator it = sasl_mechlist.begin(); it != sasl_mechlist.end(); ++it) { + QDomElement 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; + QDomElement 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; + } + QDomElement m = e.elementsByTagNameNS(NS_SASL, "mechanisms").item(0).toElement(); + if(!m.isNull()) { + f.sasl_supported = true; + QDomNodeList l = m.elementsByTagNameNS(NS_SASL, "mechanism"); + for(uint n = 0; n < l.count(); ++n) + f.sasl_mechs += l.item(n).toElement().text(); + } + QDomElement b = e.elementsByTagNameNS(NS_BIND, "bind").item(0).toElement(); + if(!b.isNull()) + f.bind_supported = true; + + if(f.tls_supported) { +#ifdef XMPP_TEST + QString s = "STARTTLS is available"; + if(f.tls_required) + s += " (required)"; + TD::msg(s); +#endif + } + if(f.sasl_supported) { +#ifdef XMPP_TEST + QString s = "SASL mechs:"; + for(QStringList::ConstIterator it = f.sasl_mechs.begin(); it != f.sasl_mechs.end(); ++it) + s += QString(" [%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") { + QByteArray a = Base64::stringToArray(e.text()); +#ifdef XMPP_TEST + TD::msg(QString("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") { + QDomElement 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") { + QString type(e.attribute("type")); + QString id(e.attribute("id")); + + if(id == "bind_1" && (type == "result" || type == "error")) { + if(type == "result") { + QDomElement b = e.elementsByTagNameNS(NS_BIND, "bind").item(0).toElement(); + Jid j; + if(!b.isNull()) { + QDomElement 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; + + QDomElement err = e.elementsByTagNameNS(NS_CLIENT, "error").item(0).toElement(); + if(!err.isNull()) { + // get error condition + QDomNodeList nl = err.childNodes(); + QDomElement t; + for(uint n = 0; n < nl.count(); ++n) { + QDomNode i = nl.item(n); + if(i.isElement()) { + t = i.toElement(); + break; + } + } + if(!t.isNull() && t.namespaceURI() == NS_STANZAS) { + QString 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")); + QString type(e.attribute("type")); + QString id(e.attribute("id")); + + bool okfrom = (from.isEmpty() || from.compare(Jid(to))); + if(okfrom && id == "auth_1" && (type == "result" || type == "error")) { + if(type == "result") { + QDomElement 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")); + QString type(e.attribute("type")); + QString 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 + + QDomElement 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") { + QDomElement b = e.elementsByTagNameNS(NS_BIND, "bind").item(0).toElement(); + if(!b.isNull()) { + QDomElement res = b.elementsByTagName("resource").item(0).toElement(); + QString resource = res.text(); + + QDomElement r = doc.createElement("iq"); + r.setAttribute("type", "result"); + r.setAttribute("id", e.attribute("id")); + QDomElement bind = doc.createElementNS(NS_BIND, "bind"); + QDomElement 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; +} |