diff options
Diffstat (limited to 'kopete/protocols/jabber/libiris/iris/xmpp-core/stream.cpp')
-rw-r--r-- | kopete/protocols/jabber/libiris/iris/xmpp-core/stream.cpp | 1762 |
1 files changed, 1762 insertions, 0 deletions
diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/stream.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/stream.cpp new file mode 100644 index 00000000..bfcc218c --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/stream.cpp @@ -0,0 +1,1762 @@ +/* + * stream.cpp - handles a client stream + * Copyright (C) 2003 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 + * + */ + +/* + Notes: + - For Non-SASL auth (JEP-0078), username and resource fields are required. + + TODO: + - sasl needParams is totally jacked? PLAIN requires authzid, etc + - server error handling + - reply with protocol errors if the client send something wrong + - don't necessarily disconnect on protocol error. prepare for more. + - server function + - deal with stream 'to' attribute dynamically + - flag tls/sasl/binding support dynamically (have the ability to specify extra stream:features) + - inform the caller about the user authentication information + - sasl security settings + - resource-binding interaction + - timeouts + - allow exchanges of non-standard stanzas + - send </stream:stream> even if we close prematurely? + - ensure ClientStream and child classes are fully deletable after signals + - xml:lang in root (<stream>) element + - sasl external + - sasl anonymous +*/ + +#include"xmpp.h" + +#include<qtextstream.h> +#include<qguardedptr.h> +#include<qtimer.h> +#include<qca.h> +#include<stdlib.h> +#include"bytestream.h" +#include"base64.h" +#include"hash.h" +#include"simplesasl.h" +#include"securestream.h" +#include"protocol.h" + +#ifdef XMPP_TEST +#include"td.h" +#endif + +//#define XMPP_DEBUG + +using namespace XMPP; + +static Debug *debug_ptr = 0; +void XMPP::setDebug(Debug *p) +{ + debug_ptr = p; +} + +static QByteArray randomArray(int size) +{ + QByteArray a(size); + for(int n = 0; n < size; ++n) + a[n] = (char)(256.0*rand()/(RAND_MAX+1.0)); + return a; +} + +static QString genId() +{ + // need SHA1 here + if(!QCA::isSupported(QCA::CAP_SHA1)) + QCA::insertProvider(createProviderHash()); + + return QCA::SHA1::hashToString(randomArray(128)); +} + +//---------------------------------------------------------------------------- +// Stanza +//---------------------------------------------------------------------------- +Stanza::Error::Error(int _type, int _condition, const QString &_text, const QDomElement &_appSpec) +{ + type = _type; + condition = _condition; + text = _text; + appSpec = _appSpec; +} + +class Stanza::Private +{ +public: + struct ErrorTypeEntry + { + const char *str; + int type; + }; + static ErrorTypeEntry errorTypeTable[]; + + struct ErrorCondEntry + { + const char *str; + int cond; + }; + static ErrorCondEntry errorCondTable[]; + + static int stringToKind(const QString &s) + { + if(s == "message") + return Message; + else if(s == "presence") + return Presence; + else if(s == "iq") + return IQ; + else + return -1; + } + + static QString kindToString(Kind k) + { + if(k == Message) + return "message"; + else if(k == Presence) + return "presence"; + else + return "iq"; + } + + static int stringToErrorType(const QString &s) + { + for(int n = 0; errorTypeTable[n].str; ++n) { + if(s == errorTypeTable[n].str) + return errorTypeTable[n].type; + } + return -1; + } + + static QString errorTypeToString(int x) + { + for(int n = 0; errorTypeTable[n].str; ++n) { + if(x == errorTypeTable[n].type) + return errorTypeTable[n].str; + } + return QString(); + } + + static int stringToErrorCond(const QString &s) + { + for(int n = 0; errorCondTable[n].str; ++n) { + if(s == errorCondTable[n].str) + return errorCondTable[n].cond; + } + return -1; + } + + static QString errorCondToString(int x) + { + for(int n = 0; errorCondTable[n].str; ++n) { + if(x == errorCondTable[n].cond) + return errorCondTable[n].str; + } + return QString(); + } + + Stream *s; + QDomElement e; +}; + +Stanza::Private::ErrorTypeEntry Stanza::Private::errorTypeTable[] = +{ + { "cancel", Cancel }, + { "continue", Continue }, + { "modify", Modify }, + { "auth", Auth }, + { "wait", Wait }, + { 0, 0 }, +}; + +Stanza::Private::ErrorCondEntry Stanza::Private::errorCondTable[] = +{ + { "bad-request", BadRequest }, + { "conflict", Conflict }, + { "feature-not-implemented", FeatureNotImplemented }, + { "forbidden", Forbidden }, + { "internal-server-error", InternalServerError }, + { "item-not-found", ItemNotFound }, + { "jid-malformed", JidMalformed }, + { "not-allowed", NotAllowed }, + { "payment-required", PaymentRequired }, + { "recipient-unavailable", RecipientUnavailable }, + { "registration-required", RegistrationRequired }, + { "remote-server-not-found", ServerNotFound }, + { "remote-server-timeout", ServerTimeout }, + { "resource-constraint", ResourceConstraint }, + { "service-unavailable", ServiceUnavailable }, + { "subscription-required", SubscriptionRequired }, + { "undefined-condition", UndefinedCondition }, + { "unexpected-request", UnexpectedRequest }, + { 0, 0 }, +}; + +Stanza::Stanza() +{ + d = 0; +} + +Stanza::Stanza(Stream *s, Kind k, const Jid &to, const QString &type, const QString &id) +{ + d = new Private; + + Kind kind; + if(k == Message || k == Presence || k == IQ) + kind = k; + else + kind = Message; + + d->s = s; + d->e = d->s->doc().createElementNS(s->baseNS(), Private::kindToString(kind)); + if(to.isValid()) + setTo(to); + if(!type.isEmpty()) + setType(type); + if(!id.isEmpty()) + setId(id); +} + +Stanza::Stanza(Stream *s, const QDomElement &e) +{ + d = 0; + if(e.namespaceURI() != s->baseNS()) + return; + int x = Private::stringToKind(e.tagName()); + if(x == -1) + return; + d = new Private; + d->s = s; + d->e = e; +} + +Stanza::Stanza(const Stanza &from) +{ + d = 0; + *this = from; +} + +Stanza & Stanza::operator=(const Stanza &from) +{ + delete d; + d = 0; + if(from.d) + d = new Private(*from.d); + return *this; +} + +Stanza::~Stanza() +{ + delete d; +} + +bool Stanza::isNull() const +{ + return (d ? false: true); +} + +QDomElement Stanza::element() const +{ + return d->e; +} + +QString Stanza::toString() const +{ + return Stream::xmlToString(d->e); +} + +QDomDocument & Stanza::doc() const +{ + return d->s->doc(); +} + +QString Stanza::baseNS() const +{ + return d->s->baseNS(); +} + +QString Stanza::xhtmlImNS() const +{ + return d->s->xhtmlImNS(); +} + +QString Stanza::xhtmlNS() const +{ + return d->s->xhtmlNS(); +} + +QDomElement Stanza::createElement(const QString &ns, const QString &tagName) +{ + return d->s->doc().createElementNS(ns, tagName); +} + +QDomElement Stanza::createTextElement(const QString &ns, const QString &tagName, const QString &text) +{ + QDomElement e = d->s->doc().createElementNS(ns, tagName); + e.appendChild(d->s->doc().createTextNode(text)); + return e; +} + +QDomElement Stanza::createXHTMLElement(const QString &xHTML) +{ + QDomDocument doc; + + doc.setContent(xHTML, true); + QDomElement root = doc.documentElement(); + //QDomElement e; + return (root); +} + +void Stanza::appendChild(const QDomElement &e) +{ + d->e.appendChild(e); +} + +Stanza::Kind Stanza::kind() const +{ + return (Kind)Private::stringToKind(d->e.tagName()); +} + +void Stanza::setKind(Kind k) +{ + d->e.setTagName(Private::kindToString(k)); +} + +Jid Stanza::to() const +{ + return Jid(d->e.attribute("to")); +} + +Jid Stanza::from() const +{ + return Jid(d->e.attribute("from")); +} + +QString Stanza::id() const +{ + return d->e.attribute("id"); +} + +QString Stanza::type() const +{ + return d->e.attribute("type"); +} + +QString Stanza::lang() const +{ + return d->e.attributeNS(NS_XML, "lang", QString()); +} + +void Stanza::setTo(const Jid &j) +{ + d->e.setAttribute("to", j.full()); +} + +void Stanza::setFrom(const Jid &j) +{ + d->e.setAttribute("from", j.full()); +} + +void Stanza::setId(const QString &id) +{ + d->e.setAttribute("id", id); +} + +void Stanza::setType(const QString &type) +{ + d->e.setAttribute("type", type); +} + +void Stanza::setLang(const QString &lang) +{ + d->e.setAttribute("xml:lang", lang); +} + +Stanza::Error Stanza::error() const +{ + Error err; + QDomElement e = d->e.elementsByTagNameNS(d->s->baseNS(), "error").item(0).toElement(); + if(e.isNull()) + return err; + + // type + int x = Private::stringToErrorType(e.attribute("type")); + if(x != -1) + err.type = x; + + // condition: find first element + QDomNodeList nl = e.childNodes(); + QDomElement t; + uint n; + for(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) { + x = Private::stringToErrorCond(t.tagName()); + if(x != -1) + err.condition = x; + } + + // text + t = e.elementsByTagNameNS(NS_STANZAS, "text").item(0).toElement(); + if(!t.isNull()) + err.text = t.text(); + else + err.text = e.text(); + + // appspec: find first non-standard namespaced element + nl = e.childNodes(); + for(n = 0; n < nl.count(); ++n) { + QDomNode i = nl.item(n); + if(i.isElement() && i.namespaceURI() != NS_STANZAS) { + err.appSpec = i.toElement(); + break; + } + } + return err; +} + +void Stanza::setError(const Error &err) +{ + // create the element if necessary + QDomElement errElem = d->e.elementsByTagNameNS(d->s->baseNS(), "error").item(0).toElement(); + if(errElem.isNull()) { + errElem = d->e.ownerDocument().createElementNS(d->s->baseNS(), "error"); + d->e.appendChild(errElem); + } + + // error type/condition + if(d->s->old()) { + errElem.setAttribute("code", QString::number(err.condition)); + } + else { + QString stype = Private::errorTypeToString(err.type); + if(stype.isEmpty()) + return; + QString scond = Private::errorCondToString(err.condition); + if(scond.isEmpty()) + return; + + errElem.setAttribute("type", stype); + errElem.appendChild(d->e.ownerDocument().createElementNS(d->s->baseNS(), scond)); + } + + // text + if(d->s->old()) { + errElem.appendChild(d->e.ownerDocument().createTextNode(err.text)); + } + else { + QDomElement te = d->e.ownerDocument().createElementNS(d->s->baseNS(), "text"); + te.appendChild(d->e.ownerDocument().createTextNode(err.text)); + errElem.appendChild(te); + } + + // application specific + errElem.appendChild(err.appSpec); +} + +void Stanza::clearError() +{ + QDomElement errElem = d->e.elementsByTagNameNS(d->s->baseNS(), "error").item(0).toElement(); + if(!errElem.isNull()) + d->e.removeChild(errElem); +} + +//---------------------------------------------------------------------------- +// Stream +//---------------------------------------------------------------------------- +static XmlProtocol *foo = 0; +Stream::Stream(QObject *parent) +:QObject(parent) +{ +} + +Stream::~Stream() +{ +} + +Stanza Stream::createStanza(Stanza::Kind k, const Jid &to, const QString &type, const QString &id) +{ + return Stanza(this, k, to, type, id); +} + +Stanza Stream::createStanza(const QDomElement &e) +{ + return Stanza(this, e); +} + +QString Stream::xmlToString(const QDomElement &e, bool clip) +{ + if(!foo) + foo = new CoreProtocol; + return foo->elementToString(e, clip); +} + +//---------------------------------------------------------------------------- +// ClientStream +//---------------------------------------------------------------------------- +enum { + Idle, + Connecting, + WaitVersion, + WaitTLS, + NeedParams, + Active, + Closing +}; + +enum { + Client, + Server +}; + +class ClientStream::Private +{ +public: + Private() + { + conn = 0; + bs = 0; + ss = 0; + tlsHandler = 0; + tls = 0; + sasl = 0; + in.setAutoDelete(true); + + oldOnly = false; + allowPlain = false; + mutualAuth = false; + haveLocalAddr = false; + minimumSSF = 0; + maximumSSF = 0; + doBinding = true; + + in_rrsig = false; + + reset(); + } + + void reset() + { + state = Idle; + notify = 0; + newStanzas = false; + sasl_ssf = 0; + tls_warned = false; + using_tls = false; + } + + Jid jid; + QString server; + bool oldOnly; + bool allowPlain, mutualAuth; + bool haveLocalAddr; + QHostAddress localAddr; + Q_UINT16 localPort; + int minimumSSF, maximumSSF; + QString sasl_mech; + bool doBinding; + + bool in_rrsig; + + Connector *conn; + ByteStream *bs; + TLSHandler *tlsHandler; + QCA::TLS *tls; + QCA::SASL *sasl; + SecureStream *ss; + CoreProtocol client; + CoreProtocol srv; + + QString defRealm; + + int mode; + int state; + int notify; + bool newStanzas; + int sasl_ssf; + bool tls_warned, using_tls; + bool doAuth; + + QStringList sasl_mechlist; + + int errCond; + QString errText; + QDomElement errAppSpec; + + QPtrList<Stanza> in; + + QTimer noopTimer; + int noop_time; +}; + +ClientStream::ClientStream(Connector *conn, TLSHandler *tlsHandler, QObject *parent) +:Stream(parent) +{ + d = new Private; + d->mode = Client; + d->conn = conn; + connect(d->conn, SIGNAL(connected()), SLOT(cr_connected())); + connect(d->conn, SIGNAL(error()), SLOT(cr_error())); + + d->noop_time = 0; + connect(&d->noopTimer, SIGNAL(timeout()), SLOT(doNoop())); + + d->tlsHandler = tlsHandler; +} + +ClientStream::ClientStream(const QString &host, const QString &defRealm, ByteStream *bs, QCA::TLS *tls, QObject *parent) +:Stream(parent) +{ + d = new Private; + d->mode = Server; + d->bs = bs; + connect(d->bs, SIGNAL(connectionClosed()), SLOT(bs_connectionClosed())); + connect(d->bs, SIGNAL(delayedCloseFinished()), SLOT(bs_delayedCloseFinished())); + connect(d->bs, SIGNAL(error(int)), SLOT(bs_error(int))); + + QByteArray spare = d->bs->read(); + + d->ss = new SecureStream(d->bs); + connect(d->ss, SIGNAL(readyRead()), SLOT(ss_readyRead())); + connect(d->ss, SIGNAL(bytesWritten(int)), SLOT(ss_bytesWritten(int))); + connect(d->ss, SIGNAL(tlsHandshaken()), SLOT(ss_tlsHandshaken())); + connect(d->ss, SIGNAL(tlsClosed()), SLOT(ss_tlsClosed())); + connect(d->ss, SIGNAL(error(int)), SLOT(ss_error(int))); + + d->server = host; + d->defRealm = defRealm; + + d->tls = tls; + + d->srv.startClientIn(genId()); + //d->srv.startServerIn(genId()); + //d->state = Connecting; + //d->jid = Jid(); + //d->server = QString(); +} + +ClientStream::~ClientStream() +{ + reset(); + delete d; +} + +void ClientStream::reset(bool all) +{ + d->reset(); + d->noopTimer.stop(); + + // delete securestream + delete d->ss; + d->ss = 0; + + // reset sasl + delete d->sasl; + d->sasl = 0; + + // client + if(d->mode == Client) { + // reset tls + if(d->tlsHandler) + d->tlsHandler->reset(); + + // reset connector + if(d->bs) { + d->bs->close(); + d->bs = 0; + } + d->conn->done(); + + // reset state machine + d->client.reset(); + } + // server + else { + if(d->tls) + d->tls->reset(); + + if(d->bs) { + d->bs->close(); + d->bs = 0; + } + + d->srv.reset(); + } + + if(all) + d->in.clear(); +} + +Jid ClientStream::jid() const +{ + return d->jid; +} + +void ClientStream::connectToServer(const Jid &jid, bool auth) +{ + reset(true); + d->state = Connecting; + d->jid = jid; + d->doAuth = auth; + d->server = d->jid.domain(); + + d->conn->connectToServer(d->server); +} + +void ClientStream::continueAfterWarning() +{ + if(d->state == WaitVersion) { + // if we don't have TLS yet, then we're never going to get it + if(!d->tls_warned && !d->using_tls) { + d->tls_warned = true; + d->state = WaitTLS; + warning(WarnNoTLS); + return; + } + d->state = Connecting; + processNext(); + } + else if(d->state == WaitTLS) { + d->state = Connecting; + processNext(); + } +} + +void ClientStream::accept() +{ + d->srv.host = d->server; + processNext(); +} + +bool ClientStream::isActive() const +{ + return (d->state != Idle) ? true: false; +} + +bool ClientStream::isAuthenticated() const +{ + return (d->state == Active) ? true: false; +} + +void ClientStream::setUsername(const QString &s) +{ + if(d->sasl) + d->sasl->setUsername(s); +} + +void ClientStream::setPassword(const QString &s) +{ + if(d->client.old) { + d->client.setPassword(s); + } + else { + if(d->sasl) + d->sasl->setPassword(s); + } +} + +void ClientStream::setRealm(const QString &s) +{ + if(d->sasl) + d->sasl->setRealm(s); +} + +void ClientStream::continueAfterParams() +{ + if(d->state == NeedParams) { + d->state = Connecting; + if(d->client.old) { + processNext(); + } + else { + if(d->sasl) + d->sasl->continueAfterParams(); + } + } +} + +void ClientStream::setResourceBinding(bool b) +{ + d->doBinding = b; +} + +void ClientStream::setNoopTime(int mills) +{ + d->noop_time = mills; + + if(d->state != Active) + return; + + if(d->noop_time == 0) { + d->noopTimer.stop(); + return; + } + d->noopTimer.start(d->noop_time); +} + +QString ClientStream::saslMechanism() const +{ + return d->client.saslMech(); +} + +int ClientStream::saslSSF() const +{ + return d->sasl_ssf; +} + +void ClientStream::setSASLMechanism(const QString &s) +{ + d->sasl_mech = s; +} + +void ClientStream::setLocalAddr(const QHostAddress &addr, Q_UINT16 port) +{ + d->haveLocalAddr = true; + d->localAddr = addr; + d->localPort = port; +} + +int ClientStream::errorCondition() const +{ + return d->errCond; +} + +QString ClientStream::errorText() const +{ + return d->errText; +} + +QDomElement ClientStream::errorAppSpec() const +{ + return d->errAppSpec; +} + +bool ClientStream::old() const +{ + return d->client.old; +} + +void ClientStream::close() +{ + if(d->state == Active) { + d->state = Closing; + d->client.shutdown(); + processNext(); + } + else if(d->state != Idle && d->state != Closing) { + reset(); + } +} + +QDomDocument & ClientStream::doc() const +{ + return d->client.doc; +} + +QString ClientStream::baseNS() const +{ + return NS_CLIENT; +} + +QString ClientStream::xhtmlImNS() const +{ + return NS_XHTML_IM; +} + +QString ClientStream::xhtmlNS() const +{ + return NS_XHTML; +} + +void ClientStream::setAllowPlain(bool b) +{ + d->allowPlain = b; +} + +void ClientStream::setRequireMutualAuth(bool b) +{ + d->mutualAuth = b; +} + +void ClientStream::setSSFRange(int low, int high) +{ + d->minimumSSF = low; + d->maximumSSF = high; +} + +void ClientStream::setOldOnly(bool b) +{ + d->oldOnly = b; +} + +bool ClientStream::stanzaAvailable() const +{ + return (!d->in.isEmpty()); +} + +Stanza ClientStream::read() +{ + if(d->in.isEmpty()) + return Stanza(); + else { + Stanza *sp = d->in.getFirst(); + Stanza s = *sp; + d->in.removeRef(sp); + return s; + } +} + +void ClientStream::write(const Stanza &s) +{ + if(d->state == Active) { + d->client.sendStanza(s.element()); + processNext(); + } +} + +void ClientStream::cr_connected() +{ + d->bs = d->conn->stream(); + connect(d->bs, SIGNAL(connectionClosed()), SLOT(bs_connectionClosed())); + connect(d->bs, SIGNAL(delayedCloseFinished()), SLOT(bs_delayedCloseFinished())); + + QByteArray spare = d->bs->read(); + + d->ss = new SecureStream(d->bs); + connect(d->ss, SIGNAL(readyRead()), SLOT(ss_readyRead())); + connect(d->ss, SIGNAL(bytesWritten(int)), SLOT(ss_bytesWritten(int))); + connect(d->ss, SIGNAL(tlsHandshaken()), SLOT(ss_tlsHandshaken())); + connect(d->ss, SIGNAL(tlsClosed()), SLOT(ss_tlsClosed())); + connect(d->ss, SIGNAL(error(int)), SLOT(ss_error(int))); + + //d->client.startDialbackOut("andbit.net", "im.pyxa.org"); + //d->client.startServerOut(d->server); + + d->client.startClientOut(d->jid, d->oldOnly, d->conn->useSSL(), d->doAuth); + d->client.setAllowTLS(d->tlsHandler ? true: false); + d->client.setAllowBind(d->doBinding); + d->client.setAllowPlain(d->allowPlain); + + /*d->client.jid = d->jid; + d->client.server = d->server; + d->client.allowPlain = d->allowPlain; + d->client.oldOnly = d->oldOnly; + d->client.sasl_mech = d->sasl_mech; + d->client.doTLS = d->tlsHandler ? true: false; + d->client.doBinding = d->doBinding;*/ + + QGuardedPtr<QObject> self = this; + connected(); + if(!self) + return; + + // immediate SSL? + if(d->conn->useSSL()) { + d->using_tls = true; + d->ss->startTLSClient(d->tlsHandler, d->server, spare); + } + else { + d->client.addIncomingData(spare); + processNext(); + } +} + +void ClientStream::cr_error() +{ + reset(); + error(ErrConnection); +} + +void ClientStream::bs_connectionClosed() +{ + reset(); + connectionClosed(); +} + +void ClientStream::bs_delayedCloseFinished() +{ + // we don't care about this (we track all important data ourself) +} + +void ClientStream::bs_error(int) +{ + // TODO +} + +void ClientStream::ss_readyRead() +{ + QByteArray a = d->ss->read(); + +#ifdef XMPP_DEBUG + QCString cs(a.data(), a.size()+1); + fprintf(stderr, "ClientStream: recv: %d [%s]\n", a.size(), cs.data()); +#endif + + if(d->mode == Client) + d->client.addIncomingData(a); + else + d->srv.addIncomingData(a); + if(d->notify & CoreProtocol::NRecv) { +#ifdef XMPP_DEBUG + printf("We needed data, so let's process it\n"); +#endif + processNext(); + } +} + +void ClientStream::ss_bytesWritten(int bytes) +{ + if(d->mode == Client) + d->client.outgoingDataWritten(bytes); + else + d->srv.outgoingDataWritten(bytes); + + if(d->notify & CoreProtocol::NSend) { +#ifdef XMPP_DEBUG + printf("We were waiting for data to be written, so let's process\n"); +#endif + processNext(); + } +} + +void ClientStream::ss_tlsHandshaken() +{ + QGuardedPtr<QObject> self = this; + securityLayerActivated(LayerTLS); + if(!self) + return; + processNext(); +} + +void ClientStream::ss_tlsClosed() +{ + reset(); + connectionClosed(); +} + +void ClientStream::ss_error(int x) +{ + if(x == SecureStream::ErrTLS) { + reset(); + d->errCond = TLSFail; + error(ErrTLS); + } + else { + reset(); + error(ErrSecurityLayer); + } +} + +void ClientStream::sasl_clientFirstStep(const QString &mech, const QByteArray *stepData) +{ + d->client.setSASLFirst(mech, stepData ? *stepData : QByteArray()); + //d->client.sasl_mech = mech; + //d->client.sasl_firstStep = stepData ? true : false; + //d->client.sasl_step = stepData ? *stepData : QByteArray(); + + processNext(); +} + +void ClientStream::sasl_nextStep(const QByteArray &stepData) +{ + if(d->mode == Client) + d->client.setSASLNext(stepData); + //d->client.sasl_step = stepData; + else + d->srv.setSASLNext(stepData); + //d->srv.sasl_step = stepData; + + processNext(); +} + +void ClientStream::sasl_needParams(bool user, bool authzid, bool pass, bool realm) +{ +#ifdef XMPP_DEBUG + printf("need params: %d,%d,%d,%d\n", user, authzid, pass, realm); +#endif + if(authzid && !user) { + d->sasl->setAuthzid(d->jid.bare()); + //d->sasl->setAuthzid("infiniti.homelesshackers.org"); + } + if(user || pass || realm) { + d->state = NeedParams; + needAuthParams(user, pass, realm); + } + else + d->sasl->continueAfterParams(); +} + +void ClientStream::sasl_authCheck(const QString &user, const QString &) +{ +//#ifdef XMPP_DEBUG +// printf("authcheck: [%s], [%s]\n", user.latin1(), authzid.latin1()); +//#endif + QString u = user; + int n = u.find('@'); + if(n != -1) + u.truncate(n); + d->srv.user = u; + d->sasl->continueAfterAuthCheck(); +} + +void ClientStream::sasl_authenticated() +{ +#ifdef XMPP_DEBUG + printf("sasl authed!!\n"); +#endif + d->sasl_ssf = d->sasl->ssf(); + + if(d->mode == Server) { + d->srv.setSASLAuthed(); + processNext(); + } +} + +void ClientStream::sasl_error(int) +{ +//#ifdef XMPP_DEBUG +// printf("sasl error: %d\n", c); +//#endif + // has to be auth error + int x = convertedSASLCond(); + reset(); + d->errCond = x; + error(ErrAuth); +} + +void ClientStream::srvProcessNext() +{ + while(1) { + printf("Processing step...\n"); + if(!d->srv.processStep()) { + int need = d->srv.need; + if(need == CoreProtocol::NNotify) { + d->notify = d->srv.notify; + if(d->notify & CoreProtocol::NSend) + printf("More data needs to be written to process next step\n"); + if(d->notify & CoreProtocol::NRecv) + printf("More data is needed to process next step\n"); + } + else if(need == CoreProtocol::NSASLMechs) { + if(!d->sasl) { + d->sasl = new QCA::SASL; + connect(d->sasl, SIGNAL(authCheck(const QString &, const QString &)), SLOT(sasl_authCheck(const QString &, const QString &))); + connect(d->sasl, SIGNAL(nextStep(const QByteArray &)), SLOT(sasl_nextStep(const QByteArray &))); + connect(d->sasl, SIGNAL(authenticated()), SLOT(sasl_authenticated())); + connect(d->sasl, SIGNAL(error(int)), SLOT(sasl_error(int))); + + //d->sasl->setAllowAnonymous(false); + //d->sasl->setRequirePassCredentials(true); + //d->sasl->setExternalAuthID("localhost"); + + d->sasl->setMinimumSSF(0); + d->sasl->setMaximumSSF(256); + + QStringList list; + // TODO: d->server is probably wrong here + if(!d->sasl->startServer("xmpp", d->server, d->defRealm, &list)) { + printf("Error initializing SASL\n"); + return; + } + d->sasl_mechlist = list; + } + d->srv.setSASLMechList(d->sasl_mechlist); + continue; + } + else if(need == CoreProtocol::NStartTLS) { + printf("Need StartTLS\n"); + if(!d->tls->startServer()) { + printf("unable to start server!\n"); + // TODO + return; + } + QByteArray a = d->srv.spare; + d->ss->startTLSServer(d->tls, a); + } + else if(need == CoreProtocol::NSASLFirst) { + printf("Need SASL First Step\n"); + QByteArray a = d->srv.saslStep(); + d->sasl->putServerFirstStep(d->srv.saslMech(), a); + } + else if(need == CoreProtocol::NSASLNext) { + printf("Need SASL Next Step\n"); + QByteArray a = d->srv.saslStep(); + QCString cs(a.data(), a.size()+1); + printf("[%s]\n", cs.data()); + d->sasl->putStep(a); + } + else if(need == CoreProtocol::NSASLLayer) { + } + + // now we can announce stanzas + //if(!d->in.isEmpty()) + // readyRead(); + return; + } + + d->notify = 0; + + int event = d->srv.event; + printf("event: %d\n", event); + switch(event) { + case CoreProtocol::EError: { + printf("Error! Code=%d\n", d->srv.errorCode); + reset(); + error(ErrProtocol); + //handleError(); + return; + } + case CoreProtocol::ESend: { + QByteArray a = d->srv.takeOutgoingData(); + QCString cs(a.size()+1); + memcpy(cs.data(), a.data(), a.size()); + printf("Need Send: {%s}\n", cs.data()); + d->ss->write(a); + break; + } + case CoreProtocol::ERecvOpen: { + printf("Break (RecvOpen)\n"); + + // calculate key + QCString str = QCA::SHA1::hashToString("secret").utf8(); + str = QCA::SHA1::hashToString(str + "im.pyxa.org").utf8(); + str = QCA::SHA1::hashToString(str + d->srv.id.utf8()).utf8(); + d->srv.setDialbackKey(str); + + //d->srv.setDialbackKey("3c5d721ea2fcc45b163a11420e4e358f87e3142a"); + + if(d->srv.to != d->server) { + // host-gone, host-unknown, see-other-host + d->srv.shutdownWithError(CoreProtocol::HostUnknown); + } + else + d->srv.setFrom(d->server); + break; + } + case CoreProtocol::ESASLSuccess: { + printf("Break SASL Success\n"); + disconnect(d->sasl, SIGNAL(error(int)), this, SLOT(sasl_error(int))); + QByteArray a = d->srv.spare; + d->ss->setLayerSASL(d->sasl, a); + break; + } + case CoreProtocol::EPeerClosed: { + // TODO: this isn' an error + printf("peer closed\n"); + reset(); + error(ErrProtocol); + return; + } + } + } +} + +void ClientStream::doReadyRead() +{ + //QGuardedPtr<QObject> self = this; + readyRead(); + //if(!self) + // return; + //d->in_rrsig = false; +} + +void ClientStream::processNext() +{ + if(d->mode == Server) { + srvProcessNext(); + return; + } + + QGuardedPtr<QObject> self = this; + + while(1) { +#ifdef XMPP_DEBUG + printf("Processing step...\n"); +#endif + bool ok = d->client.processStep(); + // deal with send/received items + for(QValueList<XmlProtocol::TransferItem>::ConstIterator it = d->client.transferItemList.begin(); it != d->client.transferItemList.end(); ++it) { + const XmlProtocol::TransferItem &i = *it; + if(i.isExternal) + continue; + QString str; + if(i.isString) { + // skip whitespace pings + if(i.str.stripWhiteSpace().isEmpty()) + continue; + str = i.str; + } + else + str = d->client.elementToString(i.elem); + if(i.isSent) + outgoingXml(str); + else + incomingXml(str); + } + + if(!ok) { + bool cont = handleNeed(); + + // now we can announce stanzas + //if(!d->in_rrsig && !d->in.isEmpty()) { + if(!d->in.isEmpty()) { + //d->in_rrsig = true; + QTimer::singleShot(0, this, SLOT(doReadyRead())); + } + + if(cont) + continue; + return; + } + + int event = d->client.event; + d->notify = 0; + switch(event) { + case CoreProtocol::EError: { +#ifdef XMPP_DEBUG + printf("Error! Code=%d\n", d->client.errorCode); +#endif + handleError(); + return; + } + case CoreProtocol::ESend: { + QByteArray a = d->client.takeOutgoingData(); +#ifdef XMPP_DEBUG + QCString cs(a.size()+1); + memcpy(cs.data(), a.data(), a.size()); + printf("Need Send: {%s}\n", cs.data()); +#endif + d->ss->write(a); + break; + } + case CoreProtocol::ERecvOpen: { +#ifdef XMPP_DEBUG + printf("Break (RecvOpen)\n"); +#endif + +#ifdef XMPP_TEST + QString s = QString("handshake success (lang=[%1]").arg(d->client.lang); + if(!d->client.from.isEmpty()) + s += QString(", from=[%1]").arg(d->client.from); + s += ')'; + TD::msg(s); +#endif + + if(d->client.old) { + d->state = WaitVersion; + warning(WarnOldVersion); + return; + } + break; + } + case CoreProtocol::EFeatures: { +#ifdef XMPP_DEBUG + printf("Break (Features)\n"); +#endif + if(!d->tls_warned && !d->using_tls && !d->client.features.tls_supported) { + d->tls_warned = true; + d->state = WaitTLS; + warning(WarnNoTLS); + return; + } + break; + } + case CoreProtocol::ESASLSuccess: { +#ifdef XMPP_DEBUG + printf("Break SASL Success\n"); +#endif + break; + } + case CoreProtocol::EReady: { +#ifdef XMPP_DEBUG + printf("Done!\n"); +#endif + // grab the JID, in case it changed + // TODO: d->jid = d->client.jid; + d->state = Active; + setNoopTime(d->noop_time); + authenticated(); + if(!self) + return; + break; + } + case CoreProtocol::EPeerClosed: { +#ifdef XMPP_DEBUG + printf("DocumentClosed\n"); +#endif + reset(); + connectionClosed(); + return; + } + case CoreProtocol::EStanzaReady: { +#ifdef XMPP_DEBUG + printf("StanzaReady\n"); +#endif + // store the stanza for now, announce after processing all events + Stanza s = createStanza(d->client.recvStanza()); + if(s.isNull()) + break; + d->in.append(new Stanza(s)); + break; + } + case CoreProtocol::EStanzaSent: { +#ifdef XMPP_DEBUG + printf("StanzasSent\n"); +#endif + stanzaWritten(); + if(!self) + return; + break; + } + case CoreProtocol::EClosed: { +#ifdef XMPP_DEBUG + printf("Closed\n"); +#endif + reset(); + delayedCloseFinished(); + return; + } + } + } +} + +bool ClientStream::handleNeed() +{ + int need = d->client.need; + if(need == CoreProtocol::NNotify) { + d->notify = d->client.notify; +#ifdef XMPP_DEBUG + if(d->notify & CoreProtocol::NSend) + printf("More data needs to be written to process next step\n"); + if(d->notify & CoreProtocol::NRecv) + printf("More data is needed to process next step\n"); +#endif + return false; + } + + d->notify = 0; + switch(need) { + case CoreProtocol::NStartTLS: { +#ifdef XMPP_DEBUG + printf("Need StartTLS\n"); +#endif + d->using_tls = true; + d->ss->startTLSClient(d->tlsHandler, d->server, d->client.spare); + return false; + } + case CoreProtocol::NSASLFirst: { +#ifdef XMPP_DEBUG + printf("Need SASL First Step\n"); +#endif + // no SASL plugin? fall back to Simple SASL + if(!QCA::isSupported(QCA::CAP_SASL)) { + // Simple SASL needs MD5. do we have that either? + if(!QCA::isSupported(QCA::CAP_MD5)) + QCA::insertProvider(createProviderHash()); + QCA::insertProvider(createProviderSimpleSASL()); + } + + d->sasl = new QCA::SASL; + connect(d->sasl, SIGNAL(clientFirstStep(const QString &, const QByteArray *)), SLOT(sasl_clientFirstStep(const QString &, const QByteArray *))); + connect(d->sasl, SIGNAL(nextStep(const QByteArray &)), SLOT(sasl_nextStep(const QByteArray &))); + connect(d->sasl, SIGNAL(needParams(bool, bool, bool, bool)), SLOT(sasl_needParams(bool, bool, bool, bool))); + connect(d->sasl, SIGNAL(authenticated()), SLOT(sasl_authenticated())); + connect(d->sasl, SIGNAL(error(int)), SLOT(sasl_error(int))); + + if(d->haveLocalAddr) + d->sasl->setLocalAddr(d->localAddr, d->localPort); + if(d->conn->havePeerAddress()) + d->sasl->setRemoteAddr(d->conn->peerAddress(), d->conn->peerPort()); + + d->sasl->setAllowAnonymous(false); + + //d->sasl_mech = "ANONYMOUS"; + //d->sasl->setRequirePassCredentials(true); + + //d->sasl->setExternalAuthID("localhost"); + //d->sasl->setExternalSSF(64); + //d->sasl_mech = "EXTERNAL"; + + d->sasl->setAllowPlain(d->allowPlain); + d->sasl->setRequireMutualAuth(d->mutualAuth); + + d->sasl->setMinimumSSF(d->minimumSSF); + d->sasl->setMaximumSSF(d->maximumSSF); + + QStringList ml; + if(!d->sasl_mech.isEmpty()) + ml += d->sasl_mech; + else + ml = d->client.features.sasl_mechs; + + if(!d->sasl->startClient("xmpp", d->server, ml, true)) { + int x = convertedSASLCond(); + reset(); + d->errCond = x; + error(ErrAuth); + return false; + } + return false; + } + case CoreProtocol::NSASLNext: { +#ifdef XMPP_DEBUG + printf("Need SASL Next Step\n"); +#endif + QByteArray a = d->client.saslStep(); + d->sasl->putStep(a); + return false; + } + case CoreProtocol::NSASLLayer: { + // SecureStream will handle the errors from this point + disconnect(d->sasl, SIGNAL(error(int)), this, SLOT(sasl_error(int))); + d->ss->setLayerSASL(d->sasl, d->client.spare); + if(d->sasl_ssf > 0) { + QGuardedPtr<QObject> self = this; + securityLayerActivated(LayerSASL); + if(!self) + return false; + } + break; + } + case CoreProtocol::NPassword: { +#ifdef XMPP_DEBUG + printf("Need Password\n"); +#endif + d->state = NeedParams; + needAuthParams(false, true, false); + return false; + } + } + + return true; +} + +int ClientStream::convertedSASLCond() const +{ + int x = d->sasl->errorCondition(); + if(x == QCA::SASL::NoMech) + return NoMech; + else if(x == QCA::SASL::BadProto) + return BadProto; + else if(x == QCA::SASL::BadServ) + return BadServ; + else if(x == QCA::SASL::TooWeak) + return MechTooWeak; + else + return GenericAuthError; +} + +void ClientStream::doNoop() +{ + if(d->state == Active) { +#ifdef XMPP_DEBUG + printf("doPing\n"); +#endif + d->client.sendWhitespace(); + processNext(); + } +} + +void ClientStream::writeDirect(const QString &s) +{ + if(d->state == Active) { +#ifdef XMPP_DEBUG + printf("writeDirect\n"); +#endif + d->client.sendDirect(s); + processNext(); + } +} + +void ClientStream::handleError() +{ + int c = d->client.errorCode; + if(c == CoreProtocol::ErrParse) { + reset(); + error(ErrParse); + } + else if(c == CoreProtocol::ErrProtocol) { + reset(); + error(ErrProtocol); + } + else if(c == CoreProtocol::ErrStream) { + int x = d->client.errCond; + QString text = d->client.errText; + QDomElement appSpec = d->client.errAppSpec; + + int connErr = -1; + int strErr = -1; + + switch(x) { + case CoreProtocol::BadFormat: { break; } // should NOT happen (we send the right format) + case CoreProtocol::BadNamespacePrefix: { break; } // should NOT happen (we send prefixes) + case CoreProtocol::Conflict: { strErr = Conflict; break; } + case CoreProtocol::ConnectionTimeout: { strErr = ConnectionTimeout; break; } + case CoreProtocol::HostGone: { connErr = HostGone; break; } + case CoreProtocol::HostUnknown: { connErr = HostUnknown; break; } + case CoreProtocol::ImproperAddressing: { break; } // should NOT happen (we aren't a server) + case CoreProtocol::InternalServerError: { strErr = InternalServerError; break; } + case CoreProtocol::InvalidFrom: { strErr = InvalidFrom; break; } + case CoreProtocol::InvalidId: { break; } // should NOT happen (clients don't specify id) + case CoreProtocol::InvalidNamespace: { break; } // should NOT happen (we set the right ns) + case CoreProtocol::InvalidXml: { strErr = InvalidXml; break; } // shouldn't happen either, but just in case ... + case CoreProtocol::StreamNotAuthorized: { break; } // should NOT happen (we're not stupid) + case CoreProtocol::PolicyViolation: { strErr = PolicyViolation; break; } + case CoreProtocol::RemoteConnectionFailed: { connErr = RemoteConnectionFailed; break; } + case CoreProtocol::ResourceConstraint: { strErr = ResourceConstraint; break; } + case CoreProtocol::RestrictedXml: { strErr = InvalidXml; break; } // group with this one + case CoreProtocol::SeeOtherHost: { connErr = SeeOtherHost; break; } + case CoreProtocol::SystemShutdown: { strErr = SystemShutdown; break; } + case CoreProtocol::UndefinedCondition: { break; } // leave as null error + case CoreProtocol::UnsupportedEncoding: { break; } // should NOT happen (we send good encoding) + case CoreProtocol::UnsupportedStanzaType: { break; } // should NOT happen (we're not stupid) + case CoreProtocol::UnsupportedVersion: { connErr = UnsupportedVersion; break; } + case CoreProtocol::XmlNotWellFormed: { strErr = InvalidXml; break; } // group with this one + default: { break; } + } + + reset(); + + d->errText = text; + d->errAppSpec = appSpec; + if(connErr != -1) { + d->errCond = connErr; + error(ErrNeg); + } + else { + if(strErr != -1) + d->errCond = strErr; + else + d->errCond = GenericStreamError; + error(ErrStream); + } + } + else if(c == CoreProtocol::ErrStartTLS) { + reset(); + d->errCond = TLSStart; + error(ErrTLS); + } + else if(c == CoreProtocol::ErrAuth) { + int x = d->client.errCond; + int r = GenericAuthError; + if(d->client.old) { + if(x == 401) // not authorized + r = NotAuthorized; + else if(x == 409) // conflict + r = GenericAuthError; + else if(x == 406) // not acceptable (this should NOT happen) + r = GenericAuthError; + } + else { + switch(x) { + case CoreProtocol::Aborted: { r = GenericAuthError; break; } // should NOT happen (we never send <abort/>) + case CoreProtocol::IncorrectEncoding: { r = GenericAuthError; break; } // should NOT happen + case CoreProtocol::InvalidAuthzid: { r = InvalidAuthzid; break; } + case CoreProtocol::InvalidMech: { r = InvalidMech; break; } + case CoreProtocol::MechTooWeak: { r = MechTooWeak; break; } + case CoreProtocol::NotAuthorized: { r = NotAuthorized; break; } + case CoreProtocol::TemporaryAuthFailure: { r = TemporaryAuthFailure; break; } + } + } + reset(); + d->errCond = r; + error(ErrAuth); + } + else if(c == CoreProtocol::ErrPlain) { + reset(); + d->errCond = NoMech; + error(ErrAuth); + } + else if(c == CoreProtocol::ErrBind) { + int r = -1; + if(d->client.errCond == CoreProtocol::BindBadRequest) { + // should NOT happen + } + else if(d->client.errCond == CoreProtocol::BindNotAllowed) { + r = BindNotAllowed; + } + else if(d->client.errCond == CoreProtocol::BindConflict) { + r = BindConflict; + } + + if(r != -1) { + reset(); + d->errCond = r; + error(ErrBind); + } + else { + reset(); + error(ErrProtocol); + } + } +} + +//---------------------------------------------------------------------------- +// Debug +//---------------------------------------------------------------------------- +Debug::~Debug() +{ +} + +#ifdef XMPP_TEST +TD::TD() +{ +} + +TD::~TD() +{ +} + +void TD::msg(const QString &s) +{ + if(debug_ptr) + debug_ptr->msg(s); +} + +void TD::outgoingTag(const QString &s) +{ + if(debug_ptr) + debug_ptr->outgoingTag(s); +} + +void TD::incomingTag(const QString &s) +{ + if(debug_ptr) + debug_ptr->incomingTag(s); +} + +void TD::outgoingXml(const QDomElement &e) +{ + if(debug_ptr) + debug_ptr->outgoingXml(e); +} + +void TD::incomingXml(const QDomElement &e) +{ + if(debug_ptr) + debug_ptr->incomingXml(e); +} +#endif |