You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdenetwork/kopete/protocols/jabber/libiris/iris/xmpp-core/protocol.cpp

1596 lines
35 KiB

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