Logo Search packages:      
Sourcecode: kadu version File versions

protocol.cpp

/*
 * 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 <QList>
#include <QByteArray>
#include <QtCrypto>

#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(int 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;
      compress_supported = 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(int 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.indexOf('.');
                  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;
                  {
                        QList<SendItem>::Iterator it = sendList.begin();
                        i = (*it);
                        sendList.erase(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;
      doCompress = true;
      doBinding = true;

      // input
      user = QString();
      host = QString();

      // status
      old = false;
      digest = false;
      tls_started = false;
      sasl_started = false;
      compress_started = false;
}

void CoreProtocol::reset()
{
      BasicProtocol::reset();
      init();
}

void CoreProtocol::startClientOut(const Jid &_jid, bool _oldOnly, bool tlsActive, bool _doAuth, bool _doCompress)
{
      jid_ = _jid;
      to = _jid.domain();
      oldOnly = _oldOnly;
      doAuth = _doAuth;
      doCompress = _doCompress;
      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;
}

const Jid& CoreProtocol::jid() const
{
      return jid_;
}

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 GetCompressProceed:
            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(QList<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.erase(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;
            {
                  QList<DBItem>::Iterator it = dbrequests.begin();
                  i = (*it);
                  dbrequests.erase(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(Jid(e.attribute("to")).domain());
                        Jid from(Jid(e.attribute("from")).domain());
                        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(Jid(e.attribute("to")).domain());
                        Jid from(Jid(e.attribute("from")).domain());
                        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;
            }

            // Should we go further ?
            if (!doAuth)
                  return loginComplete();
            
            // Deal with compression
            if (doCompress && !compress_started && features.compress_supported && features.compression_mechs.contains("zlib")) {
                  QDomElement e = doc.createElementNS(NS_COMPRESS_PROTOCOL, "compress");
                  QDomElement m = doc.createElementNS(NS_COMPRESS_PROTOCOL, "method");
                  m.appendChild(doc.createTextNode("zlib"));
                  e.appendChild(m);
                  send(e,true);
                  event = ESend;
                  step = GetCompressProceed;
                  return true;
            }

            // deal with SASL?
            if(!sasl_authed) {
                  if(!features.sasl_supported) {
                        // SASL MUST be supported
                        //event = EError;
                        //errorCode = ErrProtocol;
                        //return true;
                        
                        // Fall back on auth for non-compliant servers 
                        step = HandleAuthGet;
                        old = true;
                        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(QCA::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(QCA::Base64().arrayToString(stepData)));

                        writeElement(e, TypeElement, false, true);
                        event = ESend;
                        step = GetSASLResponse;
                        return true;
                  }
            }
            else {
                  // already authed?  then ignore last client step
                  //   (this happens if "additional data with success"
                  //   is used)
                  if(sasl_authed)
                  {
                        event = ESASLSuccess;
                        step = HandleSASLSuccess;
                        return true;
                  }

                  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(QCA::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");
                  QByteArray cs = id.toUtf8() + password.toUtf8();
                  p.appendChild(doc.createTextNode(QCA::Hash("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(int n = 0; n < l.count(); ++n)
                              f.sasl_mechs += l.item(n).toElement().text();
                  }
                  QDomElement c = e.elementsByTagNameNS(NS_COMPRESS_FEATURE, "compression").item(0).toElement();
                  if(!c.isNull()) {
                        f.compress_supported = true;
                        QDomNodeList l = c.elementsByTagNameNS(NS_COMPRESS_FEATURE, "method");
                        for(int n = 0; n < l.count(); ++n)
                              f.compression_mechs += l.item(n).toElement().text();
                  }
                  QDomElement b = e.elementsByTagNameNS(NS_BIND, "bind").item(0).toElement();
                  if(!b.isNull())
                        f.bind_supported = true;
                  QDomElement h = e.elementsByTagNameNS(NS_HOSTS, "hosts").item(0).toElement();
                  if(!h.isNull()) {
                        QDomNodeList l = h.elementsByTagNameNS(NS_HOSTS, "host");
                        for(int n = 0; n < l.count(); ++n)
                              f.hosts += l.item(n).toElement().text();
                        hosts += f.hosts;
                  }

                  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(f.compress_supported) {
#ifdef XMPP_TEST
                        QString s = "Compression mechs:";
                        for(QStringList::ConstIterator it = f.compression_mechs.begin(); it != f.compression_mechs.end(); ++it)
                              s += QString(" [%1]").arg((*it));
                        TD::msg(s);
#endif
                  }

                  event = EFeatures;
                  features = f;
                  step = HandleFeatures;
                  return true;
            }
            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 == GetCompressProceed) {
            // waiting for proceed to compression
            if(e.namespaceURI() == NS_COMPRESS_PROTOCOL) {
                  if(e.tagName() == "compressed") {
#ifdef XMPP_TEST
                        TD::msg("Server wants us to proceed with compression");
#endif
                        compress_started = true;
                        need = NCompress;
                        spare = resetStream();
                        step = Start;
                        return false;
                  }
                  else if(e.tagName() == "failure") {
                        event = EError;
                        errorCode = ErrCompress;
                        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 = QCA::Base64().stringToArray(e.text()).toByteArray();
#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") {
                        QString str = e.text();
                        // "additional data with success" ?
                        if(!str.isEmpty())
                        {
                              QByteArray a = QCA::Base64().stringToArray(str).toByteArray();
                              sasl_step = a;
                              sasl_authed = true;
                              need = NSASLNext;
                              step = GetSASLNext;
                              return false;
                        }

                        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(int 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().toLatin1().data(), e.tagName().toLatin1().data());
            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 = QCA::Base64().stringToArray(e.text()).toByteArray();
                        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 = Jid(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 = QCA::Base64().stringToArray(e.text()).toByteArray();
                  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;
}

Generated by  Doxygen 1.6.0   Back to index