2
* protocol.cpp - XMPP-Core protocol state machine
3
* Copyright (C) 2004 Justin Karneges
5
* This library is free software; you can redistribute it and/or
6
* modify it under the terms of the GNU Lesser General Public
7
* License as published by the Free Software Foundation; either
8
* version 2.1 of the License, or (at your option) any later version.
10
* This library is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
* Lesser General Public License for more details.
15
* You should have received a copy of the GNU Lesser General Public
16
* License along with this library; if not, write to the Free Software
17
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21
// TODO: let the app know if tls is required
22
// require mutual auth for server out/in
23
// report ErrProtocol if server uses wrong NS
24
// use send() instead of writeElement() in CoreProtocol
41
// This function prints out an array of bytes as latin characters, converting
42
// non-printable bytes into hex values as necessary. Useful for displaying
43
// QByteArrays for debugging purposes.
44
static QString printArray(const QByteArray &a)
47
for(int n = 0; n < a.size(); ++n) {
48
unsigned char c = (unsigned char)a[(int)n];
49
if(c < 32 || c >= 127) {
51
str.sprintf("[%02x]", c);
62
// Get an element's first child element
63
static QDomElement firstChildElement(const QDomElement &e)
65
for(QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) {
72
//----------------------------------------------------------------------------
74
//----------------------------------------------------------------------------
75
Version::Version(int maj, int min)
81
//----------------------------------------------------------------------------
83
//----------------------------------------------------------------------------
84
StreamFeatures::StreamFeatures()
86
tls_supported = false;
87
sasl_supported = false;
88
bind_supported = false;
90
compress_supported = false;
93
//----------------------------------------------------------------------------
95
//----------------------------------------------------------------------------
96
BasicProtocol::SASLCondEntry BasicProtocol::saslCondTable[] =
98
{ "aborted", Aborted },
99
{ "incorrect-encoding", IncorrectEncoding },
100
{ "invalid-authzid", InvalidAuthzid },
101
{ "invalid-mechanism", InvalidMech },
102
{ "mechanism-too-weak", MechTooWeak },
103
{ "not-authorized", NotAuthorized },
104
{ "temporary-auth-failure", TemporaryAuthFailure },
108
BasicProtocol::StreamCondEntry BasicProtocol::streamCondTable[] =
110
{ "bad-format", BadFormat },
111
{ "bad-namespace-prefix", BadNamespacePrefix },
112
{ "conflict", Conflict },
113
{ "connection-timeout", ConnectionTimeout },
114
{ "host-gone", HostGone },
115
{ "host-unknown", HostUnknown },
116
{ "improper-addressing", ImproperAddressing },
117
{ "internal-server-error", InternalServerError },
118
{ "invalid-from", InvalidFrom },
119
{ "invalid-id", InvalidId },
120
{ "invalid-namespace", InvalidNamespace },
121
{ "invalid-xml", InvalidXml },
122
{ "not-authorized", StreamNotAuthorized },
123
{ "policy-violation", PolicyViolation },
124
{ "remote-connection-failed", RemoteConnectionFailed },
125
{ "resource-constraint", ResourceConstraint },
126
{ "restricted-xml", RestrictedXml },
127
{ "see-other-host", SeeOtherHost },
128
{ "system-shutdown", SystemShutdown },
129
{ "undefined-condition", UndefinedCondition },
130
{ "unsupported-encoding", UnsupportedEncoding },
131
{ "unsupported-stanza-type", UnsupportedStanzaType },
132
{ "unsupported-version", UnsupportedVersion },
133
{ "xml-not-well-formed", XmlNotWellFormed },
137
BasicProtocol::BasicProtocol()
143
BasicProtocol::~BasicProtocol()
147
void BasicProtocol::init()
152
delayedError = false;
159
void BasicProtocol::reset()
161
XmlProtocol::reset();
168
version = Version(1,0);
170
errAppSpec = QDomElement();
171
otherHost = QString();
173
sasl_mech = QString();
174
sasl_mechlist.clear();
176
stanzaToRecv = QDomElement();
180
void BasicProtocol::sendStanza(const QDomElement &e)
187
void BasicProtocol::sendDirect(const QString &s)
194
void BasicProtocol::sendWhitespace()
197
i.doWhitespace = true;
201
QDomElement BasicProtocol::recvStanza()
203
QDomElement e = stanzaToRecv;
204
stanzaToRecv = QDomElement();
208
void BasicProtocol::shutdown()
213
void BasicProtocol::shutdownWithError(int cond, const QString &str)
216
delayErrorAndClose(cond);
219
bool BasicProtocol::isReady() const
224
void BasicProtocol::setReady(bool b)
229
QString BasicProtocol::saslMech() const
234
QByteArray BasicProtocol::saslStep() const
239
void BasicProtocol::setSASLMechList(const QStringList &list)
241
sasl_mechlist = list;
244
void BasicProtocol::setSASLFirst(const QString &mech, const QByteArray &step)
250
void BasicProtocol::setSASLNext(const QByteArray &step)
255
void BasicProtocol::setSASLAuthed()
260
int BasicProtocol::stringToSASLCond(const QString &s)
262
for(int n = 0; saslCondTable[n].str; ++n) {
263
if(s == saslCondTable[n].str)
264
return saslCondTable[n].cond;
269
int BasicProtocol::stringToStreamCond(const QString &s)
271
for(int n = 0; streamCondTable[n].str; ++n) {
272
if(s == streamCondTable[n].str)
273
return streamCondTable[n].cond;
278
QString BasicProtocol::saslCondToString(int x)
280
for(int n = 0; saslCondTable[n].str; ++n) {
281
if(x == saslCondTable[n].cond)
282
return saslCondTable[n].str;
287
QString BasicProtocol::streamCondToString(int x)
289
for(int n = 0; streamCondTable[n].str; ++n) {
290
if(x == streamCondTable[n].cond)
291
return streamCondTable[n].str;
296
void BasicProtocol::extractStreamError(const QDomElement &e)
301
QDomElement t = firstChildElement(e);
302
if(t.isNull() || t.namespaceURI() != NS_STREAMS) {
303
// probably old-style error
308
errCond = stringToStreamCond(t.tagName());
311
if(errCond == SeeOtherHost)
312
otherHost = t.text();
314
t = e.elementsByTagNameNS(NS_STREAMS, "text").item(0).toElement();
318
// find first non-standard namespaced element
319
QDomNodeList nl = e.childNodes();
320
for(int n = 0; n < nl.count(); ++n) {
321
QDomNode i = nl.item(n);
322
if(i.isElement() && i.namespaceURI() != NS_STREAMS) {
323
appSpec = i.toElement();
329
errAppSpec = appSpec;
333
void BasicProtocol::send(const QDomElement &e, bool clip)
335
writeElement(e, TypeElement, false, clip);
338
void BasicProtocol::sendStreamError(int cond, const QString &text, const QDomElement &appSpec)
340
QDomElement se = doc.createElementNS(NS_ETHERX, "stream:error");
341
QDomElement err = doc.createElementNS(NS_STREAMS, streamCondToString(cond));
342
if(!otherHost.isEmpty())
343
err.appendChild(doc.createTextNode(otherHost));
345
if(!text.isEmpty()) {
346
QDomElement te = doc.createElementNS(NS_STREAMS, "text");
347
te.setAttributeNS(NS_XML, "xml:lang", "en");
348
te.appendChild(doc.createTextNode(text));
351
se.appendChild(appSpec);
353
writeElement(se, 100, false);
356
void BasicProtocol::sendStreamError(const QString &text)
358
QDomElement se = doc.createElementNS(NS_ETHERX, "stream:error");
359
se.appendChild(doc.createTextNode(text));
361
writeElement(se, 100, false);
364
bool BasicProtocol::errorAndClose(int cond, const QString &text, const QDomElement &appSpec)
369
errAppSpec = appSpec;
370
sendStreamError(cond, text, appSpec);
374
bool BasicProtocol::error(int code)
381
void BasicProtocol::delayErrorAndClose(int cond, const QString &text, const QDomElement &appSpec)
383
errorCode = ErrStream;
386
errAppSpec = appSpec;
390
void BasicProtocol::delayError(int code)
396
QDomElement BasicProtocol::docElement()
398
// create the root element
399
QDomElement e = doc.createElementNS(NS_ETHERX, "stream:stream");
401
QString defns = defaultNamespace();
402
QStringList list = extraNamespaces();
404
// HACK: using attributes seems to be the only way to get additional namespaces in here
406
e.setAttribute("xmlns", defns);
407
for(QStringList::ConstIterator it = list.begin(); it != list.end();) {
408
QString prefix = *(it++);
409
QString uri = *(it++);
410
e.setAttribute(QString("xmlns:") + prefix, uri);
413
// additional attributes
414
if(!isIncoming() && !to.isEmpty())
415
e.setAttribute("to", to);
416
if(isIncoming() && !from.isEmpty())
417
e.setAttribute("from", from);
419
e.setAttribute("id", id);
421
e.setAttributeNS(NS_XML, "xml:lang", lang);
422
if(version.major > 0 || version.minor > 0)
423
e.setAttribute("version", QString::number(version.major) + '.' + QString::number(version.minor));
428
void BasicProtocol::handleDocOpen(const Parser::Event &pe)
431
if(xmlEncoding() != "UTF-8") {
432
delayErrorAndClose(UnsupportedEncoding);
437
if(pe.namespaceURI() == NS_ETHERX && pe.localName() == "stream") {
438
QXmlAttributes atts = pe.atts();
443
QString verstr = atts.value("version");
444
if(!verstr.isEmpty()) {
445
int n = verstr.indexOf('.');
447
major = verstr.mid(0, n).toInt();
448
minor = verstr.mid(n+1).toInt();
451
major = verstr.toInt();
455
version = Version(major, minor);
458
to = atts.value("to");
459
QString peerLang = atts.value(NS_XML, "lang");
460
if(!peerLang.isEmpty())
465
from = atts.value("from");
466
lang = atts.value(NS_XML, "lang");
467
id = atts.value("id");
470
handleStreamOpen(pe);
474
delayErrorAndClose(BadFormat);
476
delayError(ErrProtocol);
480
bool BasicProtocol::handleError()
483
return errorAndClose(XmlNotWellFormed);
485
return error(ErrParse);
488
bool BasicProtocol::handleCloseFinished()
492
errorCode = ErrStream;
493
// note: errCond and friends are already set at this point
500
bool BasicProtocol::doStep(const QDomElement &e)
502
// handle pending error
505
return errorAndClose(errCond, errText, errAppSpec);
507
return error(errorCode);
518
if(e.namespaceURI() == NS_ETHERX && e.tagName() == "error") {
519
extractStreamError(e);
520
return error(ErrStream);
526
if(stanzasWritten > 0) {
532
if(!sendList.isEmpty()) {
535
QList<SendItem>::Iterator it = sendList.begin();
541
if(!i.stanzaToSend.isNull()) {
543
writeElement(i.stanzaToSend, TypeStanza, true);
547
else if(!i.stringToSend.isEmpty()) {
548
writeString(i.stringToSend, TypeDirect, true);
551
// whitespace keepalive?
552
else if(i.doWhitespace) {
553
writeString("\n", TypePing, false);
559
// if we have pending outgoing stanzas, ask for write notification
568
void BasicProtocol::itemWritten(int id, int)
570
if(id == TypeStanza) {
576
QString BasicProtocol::defaultNamespace()
582
QStringList BasicProtocol::extraNamespaces()
585
return QStringList();
588
void BasicProtocol::handleStreamOpen(const Parser::Event &)
590
// default does nothing
593
//----------------------------------------------------------------------------
595
//----------------------------------------------------------------------------
596
CoreProtocol::CoreProtocol()
602
CoreProtocol::~CoreProtocol()
606
void CoreProtocol::init()
613
dialback_verify = false;
617
password = QString();
633
sasl_started = false;
634
compress_started = false;
637
void CoreProtocol::reset()
639
BasicProtocol::reset();
643
void CoreProtocol::startClientOut(const Jid &_jid, bool _oldOnly, bool tlsActive, bool _doAuth, bool _doCompress)
649
doCompress = _doCompress;
650
tls_started = tlsActive;
653
version = Version(0,0);
657
void CoreProtocol::startServerOut(const QString &_to)
664
void CoreProtocol::startDialbackOut(const QString &_to, const QString &_from)
673
void CoreProtocol::startDialbackVerifyOut(const QString &_to, const QString &_from, const QString &id, const QString &key)
677
dialback_verify = true;
685
void CoreProtocol::startClientIn(const QString &_id)
691
void CoreProtocol::startServerIn(const QString &_id)
698
void CoreProtocol::setLang(const QString &s)
703
void CoreProtocol::setAllowTLS(bool b)
708
void CoreProtocol::setAllowBind(bool b)
713
void CoreProtocol::setAllowPlain(bool b)
718
const Jid& CoreProtocol::jid() const
723
void CoreProtocol::setPassword(const QString &s)
728
void CoreProtocol::setFrom(const QString &s)
733
void CoreProtocol::setDialbackKey(const QString &s)
738
bool CoreProtocol::loginComplete()
747
int CoreProtocol::getOldErrorCode(const QDomElement &e)
749
QDomElement err = e.elementsByTagNameNS(NS_CLIENT, "error").item(0).toElement();
750
if(err.isNull() || !err.hasAttribute("code"))
752
return err.attribute("code").toInt();
755
/*QString CoreProtocol::xmlToString(const QDomElement &e, bool clip)
757
// determine an appropriate 'fakeNS' to use
759
if(e.prefix() == "stream")
761
else if(e.prefix() == "db")
765
return ::xmlToString(e, ns, "stream:stream", clip);
768
bool CoreProtocol::stepAdvancesParser() const
770
if(stepRequiresElement())
777
// all element-needing steps need to be registered here
778
bool CoreProtocol::stepRequiresElement() const
783
case GetCompressProceed:
784
case GetSASLChallenge:
785
case GetBindResponse:
786
case GetAuthGetResponse:
787
case GetAuthSetResponse:
789
case GetSASLResponse:
795
void CoreProtocol::stringSend(const QString &s)
802
void CoreProtocol::stringRecv(const QString &s)
809
QString CoreProtocol::defaultNamespace()
817
QStringList CoreProtocol::extraNamespaces()
827
void CoreProtocol::handleStreamOpen(const Parser::Event &pe)
830
QString ns = pe.nsprefix();
833
db = pe.nsprefix("db");
839
if((!server && ns != NS_CLIENT) || (server && ns != NS_SERVER) || (dialback && db != NS_DIALBACK)) {
840
delayErrorAndClose(InvalidNamespace);
845
if(version.major < 1 && !dialback) {
846
delayErrorAndClose(UnsupportedVersion);
852
if(version.major >= 1 && !oldOnly)
860
void CoreProtocol::elementSend(const QDomElement &e)
867
void CoreProtocol::elementRecv(const QDomElement &e)
874
bool CoreProtocol::doStep2(const QDomElement &e)
877
return dialbackStep(e);
879
return normalStep(e);
882
bool CoreProtocol::isValidStanza(const QDomElement &e) const
884
QString s = e.tagName();
885
if(e.namespaceURI() == (server ? NS_SERVER : NS_CLIENT) && (s == "message" || s == "presence" || s == "iq"))
891
bool CoreProtocol::grabPendingItem(const Jid &to, const Jid &from, int type, DBItem *item)
893
for(QList<DBItem>::Iterator it = dbpending.begin(); it != dbpending.end(); ++it) {
894
const DBItem &i = *it;
895
if(i.type == type && i.to.compare(to) && i.from.compare(from)) {
896
const DBItem &i = (*it);
905
bool CoreProtocol::dialbackStep(const QDomElement &e)
914
if(!dbrequests.isEmpty()) {
918
QList<DBItem>::Iterator it = dbrequests.begin();
920
dbrequests.erase(it);
924
if(i.type == DBItem::ResultRequest) {
925
r = doc.createElementNS(NS_DIALBACK, "db:result");
926
r.setAttribute("to", i.to.full());
927
r.setAttribute("from", i.from.full());
928
r.appendChild(doc.createTextNode(i.key));
931
else if(i.type == DBItem::ResultGrant) {
932
r = doc.createElementNS(NS_DIALBACK, "db:result");
933
r.setAttribute("to", i.to.full());
934
r.setAttribute("from", i.from.full());
935
r.setAttribute("type", i.ok ? "valid" : "invalid");
937
i.type = DBItem::Validated;
941
// TODO: disconnect after writing element
944
else if(i.type == DBItem::VerifyRequest) {
945
r = doc.createElementNS(NS_DIALBACK, "db:verify");
946
r.setAttribute("to", i.to.full());
947
r.setAttribute("from", i.from.full());
948
r.setAttribute("id", i.id);
949
r.appendChild(doc.createTextNode(i.key));
954
r = doc.createElementNS(NS_DIALBACK, "db:verify");
955
r.setAttribute("to", i.to.full());
956
r.setAttribute("from", i.from.full());
957
r.setAttribute("id", i.id);
958
r.setAttribute("type", i.ok ? "valid" : "invalid");
961
writeElement(r, TypeElement, false);
967
if(e.namespaceURI() == NS_DIALBACK) {
968
if(e.tagName() == "result") {
969
Jid to(Jid(e.attribute("to")).domain());
970
Jid from(Jid(e.attribute("from")).domain());
972
QString key = e.text();
973
// TODO: report event
976
bool ok = (e.attribute("type") == "valid") ? true: false;
978
if(grabPendingItem(from, to, DBItem::ResultRequest, &i)) {
980
i.type = DBItem::Validated;
983
// TODO: report event
986
// TODO: report event
991
else if(e.tagName() == "verify") {
992
Jid to(Jid(e.attribute("to")).domain());
993
Jid from(Jid(e.attribute("from")).domain());
994
QString id = e.attribute("id");
996
QString key = e.text();
997
// TODO: report event
1000
bool ok = (e.attribute("type") == "valid") ? true: false;
1002
if(grabPendingItem(from, to, DBItem::VerifyRequest, &i)) {
1004
// TODO: report event
1007
// TODO: report event
1015
if(isValidStanza(e)) {
1016
// TODO: disconnect if stanza is from unverified sender
1017
// TODO: ignore packets from receiving servers
1019
event = EStanzaReady;
1031
bool CoreProtocol::normalStep(const QDomElement &e)
1036
step = SendFeatures;
1042
step = HandleAuthGet;
1044
return loginComplete();
1049
return processStep();
1052
else if(step == HandleFeatures) {
1054
if(doTLS && !tls_started && !sasl_authed && features.tls_supported) {
1055
QDomElement e = doc.createElementNS(NS_TLS, "starttls");
1059
step = GetTLSProceed;
1063
// Should we go further ?
1065
return loginComplete();
1067
// Deal with compression
1068
if (doCompress && !compress_started && features.compress_supported && features.compression_mechs.contains("zlib")) {
1069
QDomElement e = doc.createElementNS(NS_COMPRESS_PROTOCOL, "compress");
1070
QDomElement m = doc.createElementNS(NS_COMPRESS_PROTOCOL, "method");
1071
m.appendChild(doc.createTextNode("zlib"));
1075
step = GetCompressProceed;
1081
if(!features.sasl_supported) {
1082
// SASL MUST be supported
1084
//errorCode = ErrProtocol;
1087
// Fall back on auth for non-compliant servers
1088
step = HandleAuthGet;
1094
TD::msg("starting SASL authentication...");
1097
step = GetSASLFirst;
1102
return loginComplete();
1106
return loginComplete();
1110
if(!features.bind_supported) {
1111
// bind MUST be supported
1113
errorCode = ErrProtocol;
1117
QDomElement e = doc.createElement("iq");
1118
e.setAttribute("type", "set");
1119
e.setAttribute("id", "bind_1");
1120
QDomElement b = doc.createElementNS(NS_BIND, "bind");
1122
// request specific resource?
1123
QString resource = jid_.resource();
1124
if(!resource.isEmpty()) {
1125
QDomElement r = doc.createElement("resource");
1126
r.appendChild(doc.createTextNode(jid_.resource()));
1134
step = GetBindResponse;
1137
else if(step == GetSASLFirst) {
1138
QDomElement e = doc.createElementNS(NS_SASL, "auth");
1139
e.setAttribute("mechanism", sasl_mech);
1140
if(!sasl_step.isEmpty()) {
1142
TD::msg(QString("SASL OUT: [%1]").arg(printArray(sasl_step)));
1144
e.appendChild(doc.createTextNode(QCA::Base64().arrayToString(sasl_step)));
1149
step = GetSASLChallenge;
1152
else if(step == GetSASLNext) {
1155
QDomElement e = doc.createElementNS(NS_SASL, "success");
1156
writeElement(e, TypeElement, false, true);
1158
step = IncHandleSASLSuccess;
1162
QByteArray stepData = sasl_step;
1163
QDomElement e = doc.createElementNS(NS_SASL, "challenge");
1164
if(!stepData.isEmpty())
1165
e.appendChild(doc.createTextNode(QCA::Base64().arrayToString(stepData)));
1167
writeElement(e, TypeElement, false, true);
1169
step = GetSASLResponse;
1174
// already authed? then ignore last client step
1175
// (this happens if "additional data with success"
1179
event = ESASLSuccess;
1180
step = HandleSASLSuccess;
1184
QByteArray stepData = sasl_step;
1186
TD::msg(QString("SASL OUT: [%1]").arg(printArray(sasl_step)));
1188
QDomElement e = doc.createElementNS(NS_SASL, "response");
1189
if(!stepData.isEmpty())
1190
e.appendChild(doc.createTextNode(QCA::Base64().arrayToString(stepData)));
1194
step = GetSASLChallenge;
1198
else if(step == HandleSASLSuccess) {
1200
spare = resetStream();
1204
else if(step == HandleAuthGet) {
1205
QDomElement e = doc.createElement("iq");
1206
e.setAttribute("to", to);
1207
e.setAttribute("type", "get");
1208
e.setAttribute("id", "auth_1");
1209
QDomElement q = doc.createElementNS("jabber:iq:auth", "query");
1210
QDomElement u = doc.createElement("username");
1211
u.appendChild(doc.createTextNode(jid_.node()));
1217
step = GetAuthGetResponse;
1220
else if(step == HandleAuthSet) {
1221
QDomElement e = doc.createElement("iq");
1222
e.setAttribute("to", to);
1223
e.setAttribute("type", "set");
1224
e.setAttribute("id", "auth_2");
1225
QDomElement q = doc.createElementNS("jabber:iq:auth", "query");
1226
QDomElement u = doc.createElement("username");
1227
u.appendChild(doc.createTextNode(jid_.node()));
1232
//if(!QCA::isSupported(QCA::CAP_SHA1))
1233
// QCA::insertProvider(createProviderHash());
1235
p = doc.createElement("digest");
1236
QByteArray cs = id.toUtf8() + password.toUtf8();
1237
p.appendChild(doc.createTextNode(QCA::Hash("sha1").hashToString(cs)));
1240
p = doc.createElement("password");
1241
p.appendChild(doc.createTextNode(password));
1244
QDomElement r = doc.createElement("resource");
1245
r.appendChild(doc.createTextNode(jid_.resource()));
1251
step = GetAuthSetResponse;
1255
else if(step == SendFeatures) {
1256
QDomElement f = doc.createElementNS(NS_ETHERX, "stream:features");
1257
if(!tls_started && !sasl_authed) { // don't offer tls if we are already sasl'd
1258
QDomElement tls = doc.createElementNS(NS_TLS, "starttls");
1264
QDomElement bind = doc.createElementNS(NS_BIND, "bind");
1265
f.appendChild(bind);
1269
QDomElement mechs = doc.createElementNS(NS_SASL, "mechanisms");
1270
for(QStringList::ConstIterator it = sasl_mechlist.begin(); it != sasl_mechlist.end(); ++it) {
1271
QDomElement m = doc.createElement("mechanism");
1272
m.appendChild(doc.createTextNode(*it));
1273
mechs.appendChild(m);
1275
f.appendChild(mechs);
1278
writeElement(f, TypeElement, false);
1284
else if(step == HandleTLS) {
1287
spare = resetStream();
1292
else if(step == IncHandleSASLSuccess) {
1293
event = ESASLSuccess;
1294
spare = resetStream();
1296
printf("sasl success\n");
1299
else if(step == GetFeatures) {
1300
// we are waiting for stream features
1301
if(e.namespaceURI() == NS_ETHERX && e.tagName() == "features") {
1304
QDomElement s = e.elementsByTagNameNS(NS_TLS, "starttls").item(0).toElement();
1306
f.tls_supported = true;
1307
f.tls_required = s.elementsByTagNameNS(NS_TLS, "required").count() > 0;
1309
QDomElement m = e.elementsByTagNameNS(NS_SASL, "mechanisms").item(0).toElement();
1311
f.sasl_supported = true;
1312
QDomNodeList l = m.elementsByTagNameNS(NS_SASL, "mechanism");
1313
for(int n = 0; n < l.count(); ++n)
1314
f.sasl_mechs += l.item(n).toElement().text();
1316
QDomElement c = e.elementsByTagNameNS(NS_COMPRESS_FEATURE, "compression").item(0).toElement();
1318
f.compress_supported = true;
1319
QDomNodeList l = c.elementsByTagNameNS(NS_COMPRESS_FEATURE, "method");
1320
for(int n = 0; n < l.count(); ++n)
1321
f.compression_mechs += l.item(n).toElement().text();
1323
QDomElement b = e.elementsByTagNameNS(NS_BIND, "bind").item(0).toElement();
1325
f.bind_supported = true;
1326
QDomElement h = e.elementsByTagNameNS(NS_HOSTS, "hosts").item(0).toElement();
1328
QDomNodeList l = h.elementsByTagNameNS(NS_HOSTS, "host");
1329
for(int n = 0; n < l.count(); ++n)
1330
f.hosts += l.item(n).toElement().text();
1334
if(f.tls_supported) {
1336
QString s = "STARTTLS is available";
1342
if(f.sasl_supported) {
1344
QString s = "SASL mechs:";
1345
for(QStringList::ConstIterator it = f.sasl_mechs.begin(); it != f.sasl_mechs.end(); ++it)
1346
s += QString(" [%1]").arg((*it));
1350
if(f.compress_supported) {
1352
QString s = "Compression mechs:";
1353
for(QStringList::ConstIterator it = f.compression_mechs.begin(); it != f.compression_mechs.end(); ++it)
1354
s += QString(" [%1]").arg((*it));
1361
step = HandleFeatures;
1368
else if(step == GetTLSProceed) {
1369
// waiting for proceed to starttls
1370
if(e.namespaceURI() == NS_TLS) {
1371
if(e.tagName() == "proceed") {
1373
TD::msg("Server wants us to proceed with ssl handshake");
1377
spare = resetStream();
1381
else if(e.tagName() == "failure") {
1383
errorCode = ErrStartTLS;
1388
errorCode = ErrProtocol;
1396
else if(step == GetCompressProceed) {
1397
// waiting for proceed to compression
1398
if(e.namespaceURI() == NS_COMPRESS_PROTOCOL) {
1399
if(e.tagName() == "compressed") {
1401
TD::msg("Server wants us to proceed with compression");
1403
compress_started = true;
1405
spare = resetStream();
1409
else if(e.tagName() == "failure") {
1411
errorCode = ErrCompress;
1416
errorCode = ErrProtocol;
1424
else if(step == GetSASLChallenge) {
1425
// waiting for sasl challenge/success/fail
1426
if(e.namespaceURI() == NS_SASL) {
1427
if(e.tagName() == "challenge") {
1428
QByteArray a = QCA::Base64().stringToArray(e.text()).toByteArray();
1430
TD::msg(QString("SASL IN: [%1]").arg(printArray(a)));
1437
else if(e.tagName() == "success") {
1438
QString str = e.text();
1439
// "additional data with success" ?
1442
QByteArray a = QCA::Base64().stringToArray(str).toByteArray();
1451
event = ESASLSuccess;
1452
step = HandleSASLSuccess;
1455
else if(e.tagName() == "failure") {
1456
QDomElement t = firstChildElement(e);
1457
if(t.isNull() || t.namespaceURI() != NS_SASL)
1460
errCond = stringToSASLCond(t.tagName());
1463
errorCode = ErrAuth;
1468
errorCode = ErrProtocol;
1473
else if(step == GetBindResponse) {
1474
if(e.namespaceURI() == NS_CLIENT && e.tagName() == "iq") {
1475
QString type(e.attribute("type"));
1476
QString id(e.attribute("id"));
1478
if(id == "bind_1" && (type == "result" || type == "error")) {
1479
if(type == "result") {
1480
QDomElement b = e.elementsByTagNameNS(NS_BIND, "bind").item(0).toElement();
1483
QDomElement je = e.elementsByTagName("jid").item(0).toElement();
1488
errorCode = ErrProtocol;
1492
return loginComplete();
1497
QDomElement err = e.elementsByTagNameNS(NS_CLIENT, "error").item(0).toElement();
1499
// get error condition
1500
QDomNodeList nl = err.childNodes();
1502
for(int n = 0; n < nl.count(); ++n) {
1503
QDomNode i = nl.item(n);
1509
if(!t.isNull() && t.namespaceURI() == NS_STANZAS) {
1510
QString cond = t.tagName();
1511
if(cond == "not-allowed")
1512
errCond = BindNotAllowed;
1513
else if(cond == "conflict")
1514
errCond = BindConflict;
1519
errorCode = ErrBind;
1531
else if(step == GetAuthGetResponse) {
1532
// waiting for an iq
1533
if(e.namespaceURI() == NS_CLIENT && e.tagName() == "iq") {
1534
Jid from(e.attribute("from"));
1535
QString type(e.attribute("type"));
1536
QString id(e.attribute("id"));
1538
bool okfrom = (from.isEmpty() || from.compare(Jid(to)));
1539
if(okfrom && id == "auth_1" && (type == "result" || type == "error")) {
1540
if(type == "result") {
1541
QDomElement q = e.elementsByTagNameNS("jabber:iq:auth", "query").item(0).toElement();
1542
if(q.isNull() || q.elementsByTagName("username").item(0).isNull() || q.elementsByTagName("resource").item(0).isNull()) {
1544
errorCode = ErrProtocol;
1547
bool plain_supported = !q.elementsByTagName("password").item(0).isNull();
1548
bool digest_supported = !q.elementsByTagName("digest").item(0).isNull();
1550
if(!digest_supported && !plain_supported) {
1552
errorCode = ErrProtocol;
1556
// plain text not allowed?
1557
if(!digest_supported && !allowPlain) {
1559
errorCode = ErrPlain;
1563
digest = digest_supported;
1565
step = HandleAuthSet;
1569
errCond = getOldErrorCode(e);
1572
errorCode = ErrAuth;
1584
else if(step == GetAuthSetResponse) {
1585
// waiting for an iq
1586
if(e.namespaceURI() == NS_CLIENT && e.tagName() == "iq") {
1587
Jid from(e.attribute("from"));
1588
QString type(e.attribute("type"));
1589
QString id(e.attribute("id"));
1591
bool okfrom = (from.isEmpty() || from.compare(Jid(to)));
1592
if(okfrom && id == "auth_2" && (type == "result" || type == "error")) {
1593
if(type == "result") {
1594
return loginComplete();
1597
errCond = getOldErrorCode(e);
1600
errorCode = ErrAuth;
1613
else if(step == GetRequest) {
1614
printf("get request: [%s], %s\n", e.namespaceURI().toLatin1().data(), e.tagName().toLatin1().data());
1615
if(e.namespaceURI() == NS_TLS && e.localName() == "starttls") {
1616
// TODO: don't let this be done twice
1618
QDomElement e = doc.createElementNS(NS_TLS, "proceed");
1619
writeElement(e, TypeElement, false, true);
1624
if(e.namespaceURI() == NS_SASL) {
1625
if(e.localName() == "auth") {
1632
sasl_started = true;
1633
sasl_mech = e.attribute("mechanism");
1634
// TODO: if child text missing, don't pass it
1635
sasl_step = QCA::Base64().stringToArray(e.text()).toByteArray();
1642
printf("unknown sasl tag\n");
1646
if(e.namespaceURI() == NS_CLIENT && e.tagName() == "iq") {
1647
QDomElement b = e.elementsByTagNameNS(NS_BIND, "bind").item(0).toElement();
1649
QDomElement res = b.elementsByTagName("resource").item(0).toElement();
1650
QString resource = res.text();
1652
QDomElement r = doc.createElement("iq");
1653
r.setAttribute("type", "result");
1654
r.setAttribute("id", e.attribute("id"));
1655
QDomElement bind = doc.createElementNS(NS_BIND, "bind");
1656
QDomElement jid = doc.createElement("jid");
1657
Jid j = user + '@' + host + '/' + resource;
1658
jid.appendChild(doc.createTextNode(j.full()));
1659
bind.appendChild(jid);
1660
r.appendChild(bind);
1662
writeElement(r, TypeElement, false);
1672
else if(step == GetSASLResponse) {
1673
if(e.namespaceURI() == NS_SASL && e.localName() == "response") {
1674
sasl_step = QCA::Base64().stringToArray(e.text()).toByteArray();
1682
if(!e.isNull() && isValidStanza(e)) {
1684
event = EStanzaReady;
1685
setIncomingAsExternal();