diff options
Diffstat (limited to 'kopete/protocols/jabber/libiris/iris/xmpp-core')
19 files changed, 8655 insertions, 0 deletions
diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/Makefile.am b/kopete/protocols/jabber/libiris/iris/xmpp-core/Makefile.am new file mode 100644 index 00000000..f35b1c68 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/Makefile.am @@ -0,0 +1,30 @@ +# The only Q_OBJECT lines are in securestream.{h,cpp} and we deal with them below. +# Give metasources a file with no Q_OBJECT line to stop unsermake assuming we want METASOURCES = AUTO +METASOURCES = ignore_this_warning.moc + +noinst_LTLIBRARIES = libiris_xmpp_core.la +AM_CPPFLAGS = $(IDN_CFLAGS) +INCLUDES = -I$(srcdir)/../include -I$(srcdir)/../xmpp-core -I$(srcdir)/../xmpp-im -I$(srcdir)/../../cutestuff/util -I$(srcdir)/../../cutestuff/network -I$(srcdir)/../../qca/src $(all_includes) + +libiris_xmpp_core_la_CPPFLAGS = $(IDN_CFLAGS) +libiris_xmpp_core_la_LDFLAGS = $(IDN_LIBS) +libiris_xmpp_core_la_SOURCES = \ + connector.cpp \ + jid.cpp \ + securestream.cpp \ + tlshandler.cpp \ + hash.cpp \ + protocol.cpp \ + stream.cpp \ + xmlprotocol.cpp \ + parser.cpp \ + simplesasl.cpp + +libiris_xmpp_core_la_COMPILE_FIRST = securestream.moc + +CLEANFILES = securestream.moc +securestream.moc: $(srcdir)/securestream.cpp $(srcdir)/securestream.h + ${MOC} $(srcdir)/securestream.h > $@ + ${MOC} $(srcdir)/securestream.cpp >> $@ + +KDE_OPTIONS = nofinal diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/connector.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/connector.cpp new file mode 100644 index 00000000..8ebc3ee8 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/connector.cpp @@ -0,0 +1,719 @@ +/* + * connector.cpp - establish a connection to an XMPP server + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + TODO: + + - Test and analyze all possible branches + + XMPP::AdvancedConnector is "good for now." The only real issue is that + most of what it provides is just to work around the old Jabber/XMPP 0.9 + connection behavior. When XMPP 1.0 has taken over the world, we can + greatly simplify this class. - Sep 3rd, 2003. +*/ + +#include"xmpp.h" + +#include<qguardedptr.h> +#include<qca.h> +#include"safedelete.h" + +#ifdef NO_NDNS +#include<qdns.h> +#else +#include"ndns.h" +#endif + +#include"srvresolver.h" +#include"bsocket.h" +#include"httpconnect.h" +#include"httppoll.h" +#include"socks.h" +#include"hash.h" + +//#define XMPP_DEBUG + +using namespace XMPP; + +//---------------------------------------------------------------------------- +// Connector +//---------------------------------------------------------------------------- +Connector::Connector(QObject *parent) +:QObject(parent) +{ + setUseSSL(false); + setPeerAddressNone(); +} + +Connector::~Connector() +{ +} + +bool Connector::useSSL() const +{ + return ssl; +} + +bool Connector::havePeerAddress() const +{ + return haveaddr; +} + +QHostAddress Connector::peerAddress() const +{ + return addr; +} + +Q_UINT16 Connector::peerPort() const +{ + return port; +} + +void Connector::setUseSSL(bool b) +{ + ssl = b; +} + +void Connector::setPeerAddressNone() +{ + haveaddr = false; + addr = QHostAddress(); + port = 0; +} + +void Connector::setPeerAddress(const QHostAddress &_addr, Q_UINT16 _port) +{ + haveaddr = true; + addr = _addr; + port = _port; +} + + +//---------------------------------------------------------------------------- +// AdvancedConnector::Proxy +//---------------------------------------------------------------------------- +AdvancedConnector::Proxy::Proxy() +{ + t = None; + v_poll = 30; +} + +AdvancedConnector::Proxy::~Proxy() +{ +} + +int AdvancedConnector::Proxy::type() const +{ + return t; +} + +QString AdvancedConnector::Proxy::host() const +{ + return v_host; +} + +Q_UINT16 AdvancedConnector::Proxy::port() const +{ + return v_port; +} + +QString AdvancedConnector::Proxy::url() const +{ + return v_url; +} + +QString AdvancedConnector::Proxy::user() const +{ + return v_user; +} + +QString AdvancedConnector::Proxy::pass() const +{ + return v_pass; +} + +int AdvancedConnector::Proxy::pollInterval() const +{ + return v_poll; +} + +void AdvancedConnector::Proxy::setHttpConnect(const QString &host, Q_UINT16 port) +{ + t = HttpConnect; + v_host = host; + v_port = port; +} + +void AdvancedConnector::Proxy::setHttpPoll(const QString &host, Q_UINT16 port, const QString &url) +{ + t = HttpPoll; + v_host = host; + v_port = port; + v_url = url; +} + +void AdvancedConnector::Proxy::setSocks(const QString &host, Q_UINT16 port) +{ + t = Socks; + v_host = host; + v_port = port; +} + +void AdvancedConnector::Proxy::setUserPass(const QString &user, const QString &pass) +{ + v_user = user; + v_pass = pass; +} + +void AdvancedConnector::Proxy::setPollInterval(int secs) +{ + v_poll = secs; +} + + +//---------------------------------------------------------------------------- +// AdvancedConnector +//---------------------------------------------------------------------------- +enum { Idle, Connecting, Connected }; +class AdvancedConnector::Private +{ +public: + int mode; + ByteStream *bs; +#ifdef NO_NDNS + QDns *qdns; +#else + NDns dns; +#endif + SrvResolver srv; + + QString server; + QString opt_host; + int opt_port; + bool opt_probe, opt_ssl; + Proxy proxy; + + QString host; + int port; + QValueList<QDns::Server> servers; + int errorCode; + + bool multi, using_srv; + bool will_be_ssl; + int probe_mode; + + bool aaaa; + SafeDelete sd; +}; + +AdvancedConnector::AdvancedConnector(QObject *parent) +:Connector(parent) +{ + d = new Private; + d->bs = 0; +#ifdef NO_NDNS + d->qdns = 0; +#else + connect(&d->dns, SIGNAL(resultsReady()), SLOT(dns_done())); +#endif + connect(&d->srv, SIGNAL(resultsReady()), SLOT(srv_done())); + d->opt_probe = false; + d->opt_ssl = false; + cleanup(); + d->errorCode = 0; +} + +AdvancedConnector::~AdvancedConnector() +{ + cleanup(); + delete d; +} + +void AdvancedConnector::cleanup() +{ + d->mode = Idle; + + // stop any dns +#ifdef NO_NDNS + if(d->qdns) { + d->qdns->disconnect(this); + d->qdns->deleteLater(); + //d->sd.deleteLater(d->qdns); + d->qdns = 0; + } +#else + if(d->dns.isBusy()) + d->dns.stop(); +#endif + if(d->srv.isBusy()) + d->srv.stop(); + + // destroy the bytestream, if there is one + delete d->bs; + d->bs = 0; + + d->multi = false; + d->using_srv = false; + d->will_be_ssl = false; + d->probe_mode = -1; + + setUseSSL(false); + setPeerAddressNone(); +} + +void AdvancedConnector::setProxy(const Proxy &proxy) +{ + if(d->mode != Idle) + return; + d->proxy = proxy; +} + +void AdvancedConnector::setOptHostPort(const QString &host, Q_UINT16 _port) +{ + if(d->mode != Idle) + return; + d->opt_host = host; + d->opt_port = _port; +} + +void AdvancedConnector::setOptProbe(bool b) +{ + if(d->mode != Idle) + return; + d->opt_probe = b; +} + +void AdvancedConnector::setOptSSL(bool b) +{ + if(d->mode != Idle) + return; + d->opt_ssl = b; +} + +void AdvancedConnector::connectToServer(const QString &server) +{ + if(d->mode != Idle) + return; + if(server.isEmpty()) + return; + + d->errorCode = 0; + d->server = server; + d->mode = Connecting; + d->aaaa = true; + + if(d->proxy.type() == Proxy::HttpPoll) { + // need SHA1 here + if(!QCA::isSupported(QCA::CAP_SHA1)) + QCA::insertProvider(createProviderHash()); + + HttpPoll *s = new HttpPoll; + d->bs = s; + connect(s, SIGNAL(connected()), SLOT(bs_connected())); + connect(s, SIGNAL(syncStarted()), SLOT(http_syncStarted())); + connect(s, SIGNAL(syncFinished()), SLOT(http_syncFinished())); + connect(s, SIGNAL(error(int)), SLOT(bs_error(int))); + if(!d->proxy.user().isEmpty()) + s->setAuth(d->proxy.user(), d->proxy.pass()); + s->setPollInterval(d->proxy.pollInterval()); + + if(d->proxy.host().isEmpty()) + s->connectToUrl(d->proxy.url()); + else + s->connectToHost(d->proxy.host(), d->proxy.port(), d->proxy.url()); + } + else { + if(!d->opt_host.isEmpty()) { + d->host = d->opt_host; + d->port = d->opt_port; + do_resolve(); + } + else { + d->multi = true; + + QGuardedPtr<QObject> self = this; + srvLookup(d->server); + if(!self) + return; + + d->srv.resolveSrvOnly(d->server, "xmpp-client", "tcp"); + } + } +} + +void AdvancedConnector::changePollInterval(int secs) +{ + if(d->bs && (d->bs->inherits("XMPP::HttpPoll") || d->bs->inherits("HttpPoll"))) { + HttpPoll *s = static_cast<HttpPoll*>(d->bs); + s->setPollInterval(secs); + } +} + +ByteStream *AdvancedConnector::stream() const +{ + if(d->mode == Connected) + return d->bs; + else + return 0; +} + +void AdvancedConnector::done() +{ + cleanup(); +} + +int AdvancedConnector::errorCode() const +{ + return d->errorCode; +} + +void AdvancedConnector::do_resolve() +{ +#ifdef NO_NDNS + printf("resolving (aaaa=%d)\n", d->aaaa); + d->qdns = new QDns; + connect(d->qdns, SIGNAL(resultsReady()), SLOT(dns_done())); + if(d->aaaa) + d->qdns->setRecordType(QDns::Aaaa); // IPv6 + else + d->qdns->setRecordType(QDns::A); // IPv4 + d->qdns->setLabel(d->host); +#else + d->dns.resolve(d->host); +#endif +} + +void AdvancedConnector::dns_done() +{ + bool failed = false; + QHostAddress addr; + +#ifdef NO_NDNS + //if(!d->qdns) + // return; + + // apparently we sometimes get this signal even though the results aren' t ready + //if(d->qdns->isWorking()) + // return; + + //SafeDeleteLock s(&d->sd); + + // grab the address list and destroy the qdns object + QValueList<QHostAddress> list = d->qdns->addresses(); + d->qdns->disconnect(this); + d->qdns->deleteLater(); + //d->sd.deleteLater(d->qdns); + d->qdns = 0; + + if(list.isEmpty()) { + if(d->aaaa) { + d->aaaa = false; + do_resolve(); + return; + } + //do_resolve(); + //return; + failed = true; + } + else + addr = list.first(); +#else + if(d->dns.result() == 0) + failed = true; + else + addr = QHostAddress(d->dns.result()); +#endif + + if(failed) { +#ifdef XMPP_DEBUG + printf("dns1\n"); +#endif + // using proxy? then try the unresolved host through the proxy + if(d->proxy.type() != Proxy::None) { +#ifdef XMPP_DEBUG + printf("dns1.1\n"); +#endif + do_connect(); + } + else if(d->using_srv) { +#ifdef XMPP_DEBUG + printf("dns1.2\n"); +#endif + if(d->servers.isEmpty()) { +#ifdef XMPP_DEBUG + printf("dns1.2.1\n"); +#endif + cleanup(); + d->errorCode = ErrConnectionRefused; + error(); + } + else { +#ifdef XMPP_DEBUG + printf("dns1.2.2\n"); +#endif + tryNextSrv(); + return; + } + } + else { +#ifdef XMPP_DEBUG + printf("dns1.3\n"); +#endif + cleanup(); + d->errorCode = ErrHostNotFound; + error(); + } + } + else { +#ifdef XMPP_DEBUG + printf("dns2\n"); +#endif + d->host = addr.toString(); + do_connect(); + } +} + +void AdvancedConnector::do_connect() +{ +#ifdef XMPP_DEBUG + printf("trying %s:%d\n", d->host.latin1(), d->port); +#endif + int t = d->proxy.type(); + if(t == Proxy::None) { +#ifdef XMPP_DEBUG + printf("do_connect1\n"); +#endif + BSocket *s = new BSocket; + d->bs = s; + connect(s, SIGNAL(connected()), SLOT(bs_connected())); + connect(s, SIGNAL(error(int)), SLOT(bs_error(int))); + s->connectToHost(d->host, d->port); + } + else if(t == Proxy::HttpConnect) { +#ifdef XMPP_DEBUG + printf("do_connect2\n"); +#endif + HttpConnect *s = new HttpConnect; + d->bs = s; + connect(s, SIGNAL(connected()), SLOT(bs_connected())); + connect(s, SIGNAL(error(int)), SLOT(bs_error(int))); + if(!d->proxy.user().isEmpty()) + s->setAuth(d->proxy.user(), d->proxy.pass()); + s->connectToHost(d->proxy.host(), d->proxy.port(), d->host, d->port); + } + else if(t == Proxy::Socks) { +#ifdef XMPP_DEBUG + printf("do_connect3\n"); +#endif + SocksClient *s = new SocksClient; + d->bs = s; + connect(s, SIGNAL(connected()), SLOT(bs_connected())); + connect(s, SIGNAL(error(int)), SLOT(bs_error(int))); + if(!d->proxy.user().isEmpty()) + s->setAuth(d->proxy.user(), d->proxy.pass()); + s->connectToHost(d->proxy.host(), d->proxy.port(), d->host, d->port); + } +} + +void AdvancedConnector::tryNextSrv() +{ +#ifdef XMPP_DEBUG + printf("trying next srv\n"); +#endif + d->host = d->servers.first().name; + d->port = d->servers.first().port; + d->servers.remove(d->servers.begin()); + do_resolve(); +} + +void AdvancedConnector::srv_done() +{ + QGuardedPtr<QObject> self = this; +#ifdef XMPP_DEBUG + printf("srv_done1\n"); +#endif + d->servers = d->srv.servers(); + if(d->servers.isEmpty()) { + srvResult(false); + if(!self) + return; + +#ifdef XMPP_DEBUG + printf("srv_done1.1\n"); +#endif + // fall back to A record + d->using_srv = false; + d->host = d->server; + if(d->opt_probe) { +#ifdef XMPP_DEBUG + printf("srv_done1.1.1\n"); +#endif + d->probe_mode = 0; + d->port = 5223; + d->will_be_ssl = true; + } + else { +#ifdef XMPP_DEBUG + printf("srv_done1.1.2\n"); +#endif + d->probe_mode = 1; + d->port = 5222; + } + do_resolve(); + return; + } + + srvResult(true); + if(!self) + return; + + d->using_srv = true; + tryNextSrv(); +} + +void AdvancedConnector::bs_connected() +{ + if(d->proxy.type() == Proxy::None) { + QHostAddress h = (static_cast<BSocket*>(d->bs))->peerAddress(); + int p = (static_cast<BSocket*>(d->bs))->peerPort(); + setPeerAddress(h, p); + } + + // only allow ssl override if proxy==poll or host:port + if((d->proxy.type() == Proxy::HttpPoll || !d->opt_host.isEmpty()) && d->opt_ssl) + setUseSSL(true); + else if(d->will_be_ssl) + setUseSSL(true); + + d->mode = Connected; + connected(); +} + +void AdvancedConnector::bs_error(int x) +{ + if(d->mode == Connected) { + d->errorCode = ErrStream; + error(); + return; + } + + bool proxyError = false; + int err = ErrConnectionRefused; + int t = d->proxy.type(); + +#ifdef XMPP_DEBUG + printf("bse1\n"); +#endif + + // figure out the error + if(t == Proxy::None) { + if(x == BSocket::ErrHostNotFound) + err = ErrHostNotFound; + else + err = ErrConnectionRefused; + } + else if(t == Proxy::HttpConnect) { + if(x == HttpConnect::ErrConnectionRefused) + err = ErrConnectionRefused; + else if(x == HttpConnect::ErrHostNotFound) + err = ErrHostNotFound; + else { + proxyError = true; + if(x == HttpConnect::ErrProxyAuth) + err = ErrProxyAuth; + else if(x == HttpConnect::ErrProxyNeg) + err = ErrProxyNeg; + else + err = ErrProxyConnect; + } + } + else if(t == Proxy::HttpPoll) { + if(x == HttpPoll::ErrConnectionRefused) + err = ErrConnectionRefused; + else if(x == HttpPoll::ErrHostNotFound) + err = ErrHostNotFound; + else { + proxyError = true; + if(x == HttpPoll::ErrProxyAuth) + err = ErrProxyAuth; + else if(x == HttpPoll::ErrProxyNeg) + err = ErrProxyNeg; + else + err = ErrProxyConnect; + } + } + else if(t == Proxy::Socks) { + if(x == SocksClient::ErrConnectionRefused) + err = ErrConnectionRefused; + else if(x == SocksClient::ErrHostNotFound) + err = ErrHostNotFound; + else { + proxyError = true; + if(x == SocksClient::ErrProxyAuth) + err = ErrProxyAuth; + else if(x == SocksClient::ErrProxyNeg) + err = ErrProxyNeg; + else + err = ErrProxyConnect; + } + } + + // no-multi or proxy error means we quit + if(!d->multi || proxyError) { + cleanup(); + d->errorCode = err; + error(); + return; + } + + if(d->using_srv && !d->servers.isEmpty()) { +#ifdef XMPP_DEBUG + printf("bse1.1\n"); +#endif + tryNextSrv(); + } + else if(!d->using_srv && d->opt_probe && d->probe_mode == 0) { +#ifdef XMPP_DEBUG + printf("bse1.2\n"); +#endif + d->probe_mode = 1; + d->port = 5222; + d->will_be_ssl = false; + do_connect(); + } + else { +#ifdef XMPP_DEBUG + printf("bse1.3\n"); +#endif + cleanup(); + d->errorCode = ErrConnectionRefused; + error(); + } +} + +void AdvancedConnector::http_syncStarted() +{ + httpSyncStarted(); +} + +void AdvancedConnector::http_syncFinished() +{ + httpSyncFinished(); +} diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/hash.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/hash.cpp new file mode 100644 index 00000000..4d7f9e41 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/hash.cpp @@ -0,0 +1,670 @@ +/* + * hash.cpp - hashing functions for SHA1 and MD5 + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"hash.h" + +namespace XMPP +{ + +static bool bigEndian; +static bool haveEndian = false; + +static void ensureEndian() +{ + if(!haveEndian) { + haveEndian = true; + int wordSize; + qSysInfo(&wordSize, &bigEndian); + } +} + +//---------------------------------------------------------------------------- +// MD5 +//---------------------------------------------------------------------------- + +/* NOTE: the following code was modified to not need BYTE_ORDER -- Justin */ + +/* + Copyright (C) 1999, 2000, 2002 Aladdin Enterprises. All rights reserved. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + L. Peter Deutsch + ghost@aladdin.com + + */ +/* $Id$ */ +/* + Independent implementation of MD5 (RFC 1321). + + This code implements the MD5 Algorithm defined in RFC 1321, whose + text is available at + http://www.ietf.org/rfc/rfc1321.txt + The code is derived from the text of the RFC, including the test suite + (section A.5) but excluding the rest of Appendix A. It does not include + any code or documentation that is identified in the RFC as being + copyrighted. + + The original and principal author of md5.c is L. Peter Deutsch + <ghost@aladdin.com>. Other authors are noted in the change history + that follows (in reverse chronological order): + + 2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order + either statically or dynamically; added missing #include <string.h> + in library. + 2002-03-11 lpd Corrected argument list for main(), and added int return + type, in test program and T value program. + 2002-02-21 lpd Added missing #include <stdio.h> in test program. + 2000-07-03 lpd Patched to eliminate warnings about "constant is + unsigned in ANSI C, signed in traditional"; made test program + self-checking. + 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. + 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5). + 1999-05-03 lpd Original version. + */ + +/* + * This package supports both compile-time and run-time determination of CPU + * byte order. If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be + * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is + * defined as non-zero, the code will be compiled to run only on big-endian + * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to + * run on either big- or little-endian CPUs, but will run slightly less + * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined. + */ + +typedef Q_UINT8 md5_byte_t; /* 8-bit byte */ +typedef Q_UINT32 md5_word_t; /* 32-bit word */ + +/* Define the state of the MD5 Algorithm. */ +typedef struct md5_state_s { + md5_word_t count[2]; /* message length in bits, lsw first */ + md5_word_t abcd[4]; /* digest buffer */ + md5_byte_t buf[64]; /* accumulate block */ +} md5_state_t; + +/* Initialize the algorithm. */ +void md5_init(md5_state_t *pms); + +/* Append a string to the message. */ +void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes); + +/* Finish the message and return the digest. */ +void md5_finish(md5_state_t *pms, md5_byte_t digest[16]); + +#define T_MASK ((md5_word_t)~0) +#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87) +#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9) +#define T3 0x242070db +#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111) +#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050) +#define T6 0x4787c62a +#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec) +#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe) +#define T9 0x698098d8 +#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850) +#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e) +#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841) +#define T13 0x6b901122 +#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c) +#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71) +#define T16 0x49b40821 +#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d) +#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf) +#define T19 0x265e5a51 +#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855) +#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2) +#define T22 0x02441453 +#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e) +#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437) +#define T25 0x21e1cde6 +#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829) +#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278) +#define T28 0x455a14ed +#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa) +#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07) +#define T31 0x676f02d9 +#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375) +#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd) +#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e) +#define T35 0x6d9d6122 +#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3) +#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb) +#define T38 0x4bdecfa9 +#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f) +#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f) +#define T41 0x289b7ec6 +#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805) +#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a) +#define T44 0x04881d05 +#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6) +#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a) +#define T47 0x1fa27cf8 +#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a) +#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb) +#define T50 0x432aff97 +#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58) +#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6) +#define T53 0x655b59c3 +#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d) +#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82) +#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e) +#define T57 0x6fa87e4f +#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f) +#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb) +#define T60 0x4e0811a1 +#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d) +#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca) +#define T63 0x2ad7d2bb +#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e) + + +static void +md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/) +{ + md5_word_t + a = pms->abcd[0], b = pms->abcd[1], + c = pms->abcd[2], d = pms->abcd[3]; + md5_word_t t; + + /* Define storage for little-endian or both types of CPUs. */ + md5_word_t xbuf[16]; + const md5_word_t *X; + + { + if(bigEndian) + { + /* + * On big-endian machines, we must arrange the bytes in the + * right order. + */ + const md5_byte_t *xp = data; + int i; + + X = xbuf; /* (dynamic only) */ + + for (i = 0; i < 16; ++i, xp += 4) + xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24); + } + else /* dynamic big-endian */ + { + /* + * On little-endian machines, we can process properly aligned + * data without copying it. + */ + if (!((data - (const md5_byte_t *)0) & 3)) { + /* data are properly aligned */ + X = (const md5_word_t *)data; + } else { + /* not aligned */ + memcpy(xbuf, data, 64); + X = xbuf; + } + } + } + +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) + + /* Round 1. */ + /* Let [abcd k s i] denote the operation + a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */ +#define F(x, y, z) (((x) & (y)) | (~(x) & (z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + F(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 0, 7, T1); + SET(d, a, b, c, 1, 12, T2); + SET(c, d, a, b, 2, 17, T3); + SET(b, c, d, a, 3, 22, T4); + SET(a, b, c, d, 4, 7, T5); + SET(d, a, b, c, 5, 12, T6); + SET(c, d, a, b, 6, 17, T7); + SET(b, c, d, a, 7, 22, T8); + SET(a, b, c, d, 8, 7, T9); + SET(d, a, b, c, 9, 12, T10); + SET(c, d, a, b, 10, 17, T11); + SET(b, c, d, a, 11, 22, T12); + SET(a, b, c, d, 12, 7, T13); + SET(d, a, b, c, 13, 12, T14); + SET(c, d, a, b, 14, 17, T15); + SET(b, c, d, a, 15, 22, T16); +#undef SET + + /* Round 2. */ + /* Let [abcd k s i] denote the operation + a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */ +#define G(x, y, z) (((x) & (z)) | ((y) & ~(z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + G(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 1, 5, T17); + SET(d, a, b, c, 6, 9, T18); + SET(c, d, a, b, 11, 14, T19); + SET(b, c, d, a, 0, 20, T20); + SET(a, b, c, d, 5, 5, T21); + SET(d, a, b, c, 10, 9, T22); + SET(c, d, a, b, 15, 14, T23); + SET(b, c, d, a, 4, 20, T24); + SET(a, b, c, d, 9, 5, T25); + SET(d, a, b, c, 14, 9, T26); + SET(c, d, a, b, 3, 14, T27); + SET(b, c, d, a, 8, 20, T28); + SET(a, b, c, d, 13, 5, T29); + SET(d, a, b, c, 2, 9, T30); + SET(c, d, a, b, 7, 14, T31); + SET(b, c, d, a, 12, 20, T32); +#undef SET + + /* Round 3. */ + /* Let [abcd k s t] denote the operation + a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */ +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + H(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 5, 4, T33); + SET(d, a, b, c, 8, 11, T34); + SET(c, d, a, b, 11, 16, T35); + SET(b, c, d, a, 14, 23, T36); + SET(a, b, c, d, 1, 4, T37); + SET(d, a, b, c, 4, 11, T38); + SET(c, d, a, b, 7, 16, T39); + SET(b, c, d, a, 10, 23, T40); + SET(a, b, c, d, 13, 4, T41); + SET(d, a, b, c, 0, 11, T42); + SET(c, d, a, b, 3, 16, T43); + SET(b, c, d, a, 6, 23, T44); + SET(a, b, c, d, 9, 4, T45); + SET(d, a, b, c, 12, 11, T46); + SET(c, d, a, b, 15, 16, T47); + SET(b, c, d, a, 2, 23, T48); +#undef SET + + /* Round 4. */ + /* Let [abcd k s t] denote the operation + a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */ +#define I(x, y, z) ((y) ^ ((x) | ~(z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + I(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 0, 6, T49); + SET(d, a, b, c, 7, 10, T50); + SET(c, d, a, b, 14, 15, T51); + SET(b, c, d, a, 5, 21, T52); + SET(a, b, c, d, 12, 6, T53); + SET(d, a, b, c, 3, 10, T54); + SET(c, d, a, b, 10, 15, T55); + SET(b, c, d, a, 1, 21, T56); + SET(a, b, c, d, 8, 6, T57); + SET(d, a, b, c, 15, 10, T58); + SET(c, d, a, b, 6, 15, T59); + SET(b, c, d, a, 13, 21, T60); + SET(a, b, c, d, 4, 6, T61); + SET(d, a, b, c, 11, 10, T62); + SET(c, d, a, b, 2, 15, T63); + SET(b, c, d, a, 9, 21, T64); +#undef SET + + /* Then perform the following additions. (That is increment each + of the four registers by the value it had before this block + was started.) */ + pms->abcd[0] += a; + pms->abcd[1] += b; + pms->abcd[2] += c; + pms->abcd[3] += d; +} + +void +md5_init(md5_state_t *pms) +{ + pms->count[0] = pms->count[1] = 0; + pms->abcd[0] = 0x67452301; + pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476; + pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301; + pms->abcd[3] = 0x10325476; +} + +void +md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes) +{ + const md5_byte_t *p = data; + int left = nbytes; + int offset = (pms->count[0] >> 3) & 63; + md5_word_t nbits = (md5_word_t)(nbytes << 3); + + if (nbytes <= 0) + return; + + /* Update the message length. */ + pms->count[1] += nbytes >> 29; + pms->count[0] += nbits; + if (pms->count[0] < nbits) + pms->count[1]++; + + /* Process an initial partial block. */ + if (offset) { + int copy = (offset + nbytes > 64 ? 64 - offset : nbytes); + + memcpy(pms->buf + offset, p, copy); + if (offset + copy < 64) + return; + p += copy; + left -= copy; + md5_process(pms, pms->buf); + } + + /* Process full blocks. */ + for (; left >= 64; p += 64, left -= 64) + md5_process(pms, p); + + /* Process a final partial block. */ + if (left) + memcpy(pms->buf, p, left); +} + +void +md5_finish(md5_state_t *pms, md5_byte_t digest[16]) +{ + static const md5_byte_t pad[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + md5_byte_t data[8]; + int i; + + /* Save the length before padding. */ + for (i = 0; i < 8; ++i) + data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3)); + /* Pad to 56 bytes mod 64. */ + md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1); + /* Append the length. */ + md5_append(pms, data, 8); + for (i = 0; i < 16; ++i) + digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3)); +} + + +//---------------------------------------------------------------------------- +// SHA1 - from a public domain implementation by Steve Reid (steve@edmweb.com) +//---------------------------------------------------------------------------- + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) +#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15]^block->l[(i+2)&15]^block->l[i&15],1)) + +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); +#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); +#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); + +struct SHA1_CONTEXT +{ + Q_UINT32 state[5]; + Q_UINT32 count[2]; + unsigned char buffer[64]; +}; + +typedef union { + unsigned char c[64]; + Q_UINT32 l[16]; +} CHAR64LONG16; + +class SHA1Context : public QCA_HashContext +{ +public: + SHA1_CONTEXT _context; + CHAR64LONG16* block; + + SHA1Context() + { + reset(); + } + + QCA_HashContext *clone() + { + return new SHA1Context(*this); + } + + void reset() + { + sha1_init(&_context); + } + + void update(const char *in, unsigned int len) + { + sha1_update(&_context, (unsigned char *)in, (unsigned int)len); + } + + void final(QByteArray *out) + { + QByteArray b(20); + sha1_final((unsigned char *)b.data(), &_context); + *out = b; + } + + unsigned long blk0(Q_UINT32 i) + { + if(bigEndian) + return block->l[i]; + else + return (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) | (rol(block->l[i],8)&0x00FF00FF)); + } + + // Hash a single 512-bit block. This is the core of the algorithm. + void transform(Q_UINT32 state[5], unsigned char buffer[64]) + { + Q_UINT32 a, b, c, d, e; + + block = (CHAR64LONG16*)buffer; + + // Copy context->state[] to working vars + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + + // 4 rounds of 20 operations each. Loop unrolled. + R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); + R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); + R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); + R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); + R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); + R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); + R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); + R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); + R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); + R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); + R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); + R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); + R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); + R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); + R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); + R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); + R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); + R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); + R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); + R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); + + // Add the working vars back into context.state[] + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + + // Wipe variables + a = b = c = d = e = 0; + } + + // SHA1Init - Initialize new context + void sha1_init(SHA1_CONTEXT* context) + { + // SHA1 initialization constants + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; + } + + // Run your data through this + void sha1_update(SHA1_CONTEXT* context, unsigned char* data, Q_UINT32 len) + { + Q_UINT32 i, j; + + j = (context->count[0] >> 3) & 63; + if((context->count[0] += len << 3) < (len << 3)) + context->count[1]++; + + context->count[1] += (len >> 29); + + if((j + len) > 63) { + memcpy(&context->buffer[j], data, (i = 64-j)); + transform(context->state, context->buffer); + for ( ; i + 63 < len; i += 64) { + transform(context->state, &data[i]); + } + j = 0; + } + else i = 0; + memcpy(&context->buffer[j], &data[i], len - i); + } + + // Add padding and return the message digest + void sha1_final(unsigned char digest[20], SHA1_CONTEXT* context) + { + Q_UINT32 i, j; + unsigned char finalcount[8]; + + for (i = 0; i < 8; i++) { + finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] + >> ((3-(i & 3)) * 8) ) & 255); // Endian independent + } + sha1_update(context, (unsigned char *)"\200", 1); + while ((context->count[0] & 504) != 448) { + sha1_update(context, (unsigned char *)"\0", 1); + } + sha1_update(context, finalcount, 8); // Should cause a transform() + for (i = 0; i < 20; i++) { + digest[i] = (unsigned char) ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + } + + // Wipe variables + i = j = 0; + memset(context->buffer, 0, 64); + memset(context->state, 0, 20); + memset(context->count, 0, 8); + memset(&finalcount, 0, 8); + } +}; + +class MD5Context : public QCA_HashContext +{ +public: + MD5Context() + { + reset(); + } + + QCA_HashContext *clone() + { + return new MD5Context(*this); + } + + void reset() + { + md5_init(&md5); + } + + void update(const char *in, unsigned int len) + { + md5_append(&md5, (const md5_byte_t *)in, len); + } + + void final(QByteArray *out) + { + QByteArray b(16); + md5_finish(&md5, (md5_byte_t *)b.data()); + *out = b; + } + + md5_state_t md5; +}; + +class HashProvider : public QCAProvider +{ +public: + HashProvider() {} + ~HashProvider() {} + + void init() + { + ensureEndian(); + } + + int qcaVersion() const + { + return QCA_PLUGIN_VERSION; + } + + int capabilities() const + { + return (QCA::CAP_SHA1 | QCA::CAP_MD5); + } + + void *context(int cap) + { + if(cap == QCA::CAP_SHA1) + return new SHA1Context; + if(cap == QCA::CAP_MD5) + return new MD5Context; + return 0; + } +}; + +QCAProvider *createProviderHash() +{ + return (new HashProvider); +} + +} diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/hash.h b/kopete/protocols/jabber/libiris/iris/xmpp-core/hash.h new file mode 100644 index 00000000..a4d2eea8 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/hash.h @@ -0,0 +1,31 @@ +/* + * hash.h - hashing functions for SHA1 and MD5 + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef HASH_H +#define HASH_H + +#include"qcaprovider.h" + +namespace XMPP +{ + QCAProvider *createProviderHash(); +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/jid.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/jid.cpp new file mode 100644 index 00000000..29932513 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/jid.cpp @@ -0,0 +1,409 @@ +/* + * jid.cpp - class for verifying and manipulating Jabber IDs + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"xmpp.h" + +#include<qdict.h> +#include<stringprep.h> + +using namespace XMPP; + +//---------------------------------------------------------------------------- +// StringPrepCache +//---------------------------------------------------------------------------- +class StringPrepCache +{ +public: + static bool nameprep(const QString &in, int maxbytes, QString *out) + { + if(in.isEmpty()) + { + if(out) + *out = QString(); + return true; + } + + StringPrepCache *that = get_instance(); + + Result *r = that->nameprep_table.find(in); + if(r) + { + if(!r->norm) + return false; + if(out) + *out = *(r->norm); + return true; + } + + QCString cs = in.utf8(); + cs.resize(maxbytes); + if(stringprep(cs.data(), maxbytes, (Stringprep_profile_flags)0, stringprep_nameprep) != 0) + { + that->nameprep_table.insert(in, new Result); + return false; + } + + QString norm = QString::fromUtf8(cs); + that->nameprep_table.insert(in, new Result(norm)); + if(out) + *out = norm; + return true; + } + + static bool nodeprep(const QString &in, int maxbytes, QString *out) + { + if(in.isEmpty()) + { + if(out) + *out = QString(); + return true; + } + + StringPrepCache *that = get_instance(); + + Result *r = that->nodeprep_table.find(in); + if(r) + { + if(!r->norm) + return false; + if(out) + *out = *(r->norm); + return true; + } + + QCString cs = in.utf8(); + cs.resize(maxbytes); + if(stringprep(cs.data(), maxbytes, (Stringprep_profile_flags)0, stringprep_xmpp_nodeprep) != 0) + { + that->nodeprep_table.insert(in, new Result); + return false; + } + + QString norm = QString::fromUtf8(cs); + that->nodeprep_table.insert(in, new Result(norm)); + if(out) + *out = norm; + return true; + } + + static bool resourceprep(const QString &in, int maxbytes, QString *out) + { + if(in.isEmpty()) + { + if(out) + *out = QString(); + return true; + } + + StringPrepCache *that = get_instance(); + + Result *r = that->resourceprep_table.find(in); + if(r) + { + if(!r->norm) + return false; + if(out) + *out = *(r->norm); + return true; + } + + QCString cs = in.utf8(); + cs.resize(maxbytes); + if(stringprep(cs.data(), maxbytes, (Stringprep_profile_flags)0, stringprep_xmpp_resourceprep) != 0) + { + that->resourceprep_table.insert(in, new Result); + return false; + } + + QString norm = QString::fromUtf8(cs); + that->resourceprep_table.insert(in, new Result(norm)); + if(out) + *out = norm; + return true; + } + +private: + class Result + { + public: + QString *norm; + + Result() : norm(0) + { + } + + Result(const QString &s) : norm(new QString(s)) + { + } + + ~Result() + { + delete norm; + } + }; + + QDict<Result> nameprep_table; + QDict<Result> nodeprep_table; + QDict<Result> resourceprep_table; + + static StringPrepCache *instance; + + static StringPrepCache *get_instance() + { + if(!instance) + instance = new StringPrepCache; + return instance; + } + + StringPrepCache() + { + nameprep_table.setAutoDelete(true); + nodeprep_table.setAutoDelete(true); + resourceprep_table.setAutoDelete(true); + } +}; + +StringPrepCache *StringPrepCache::instance = 0; + +//---------------------------------------------------------------------------- +// Jid +//---------------------------------------------------------------------------- +Jid::Jid() +{ + valid = false; +} + +Jid::~Jid() +{ +} + +Jid::Jid(const QString &s) +{ + set(s); +} + +Jid::Jid(const char *s) +{ + set(QString(s)); +} + +Jid & Jid::operator=(const QString &s) +{ + set(s); + return *this; +} + +Jid & Jid::operator=(const char *s) +{ + set(QString(s)); + return *this; +} + +void Jid::reset() +{ + f = QString(); + b = QString(); + d = QString(); + n = QString(); + r = QString(); + valid = false; +} + +void Jid::update() +{ + // build 'bare' and 'full' jids + if(n.isEmpty()) + b = d; + else + b = n + '@' + d; + + b=b.lower(); // JID are not case sensitive + + if(r.isEmpty()) + f = b; + else + f = b + '/' + r; + if(f.isEmpty()) + valid = false; +} + +void Jid::set(const QString &s) +{ + QString rest, domain, node, resource; + QString norm_domain, norm_node, norm_resource; + int x = s.find('/'); + if(x != -1) { + rest = s.mid(0, x); + resource = s.mid(x+1); + } + else { + rest = s; + resource = QString(); + } + if(!validResource(resource, &norm_resource)) { + reset(); + return; + } + + x = rest.find('@'); + if(x != -1) { + node = rest.mid(0, x); + domain = rest.mid(x+1); + } + else { + node = QString(); + domain = rest; + } + if(!validDomain(domain, &norm_domain) || !validNode(node, &norm_node)) { + reset(); + return; + } + + valid = true; + d = norm_domain; + n = norm_node; + r = norm_resource; + update(); +} + +void Jid::set(const QString &domain, const QString &node, const QString &resource) +{ + QString norm_domain, norm_node, norm_resource; + if(!validDomain(domain, &norm_domain) || !validNode(node, &norm_node) || !validResource(resource, &norm_resource)) { + reset(); + return; + } + valid = true; + d = norm_domain; + n = norm_node; + r = norm_resource; + update(); +} + +void Jid::setDomain(const QString &s) +{ + if(!valid) + return; + QString norm; + if(!validDomain(s, &norm)) { + reset(); + return; + } + d = norm; + update(); +} + +void Jid::setNode(const QString &s) +{ + if(!valid) + return; + QString norm; + if(!validNode(s, &norm)) { + reset(); + return; + } + n = norm; + update(); +} + +void Jid::setResource(const QString &s) +{ + if(!valid) + return; + QString norm; + if(!validResource(s, &norm)) { + reset(); + return; + } + r = norm; + update(); +} + +Jid Jid::withNode(const QString &s) const +{ + Jid j = *this; + j.setNode(s); + return j; +} + +Jid Jid::withResource(const QString &s) const +{ + Jid j = *this; + j.setResource(s); + return j; +} + +bool Jid::isValid() const +{ + return valid; +} + +bool Jid::isEmpty() const +{ + return f.isEmpty(); +} + +bool Jid::compare(const Jid &a, bool compareRes) const +{ + // only compare valid jids + if(!valid || !a.valid) + return false; + + if(compareRes ? (f != a.f) : (b != a.b)) + return false; + + return true; +} + +bool Jid::validDomain(const QString &s, QString *norm) +{ + /*QCString cs = s.utf8(); + cs.resize(1024); + if(stringprep(cs.data(), 1024, (Stringprep_profile_flags)0, stringprep_nameprep) != 0) + return false; + if(norm) + *norm = QString::fromUtf8(cs); + return true;*/ + return StringPrepCache::nameprep(s, 1024, norm); +} + +bool Jid::validNode(const QString &s, QString *norm) +{ + /*QCString cs = s.utf8(); + cs.resize(1024); + if(stringprep(cs.data(), 1024, (Stringprep_profile_flags)0, stringprep_xmpp_nodeprep) != 0) + return false; + if(norm) + *norm = QString::fromUtf8(cs); + return true;*/ + return StringPrepCache::nodeprep(s, 1024, norm); +} + +bool Jid::validResource(const QString &s, QString *norm) +{ + /*QCString cs = s.utf8(); + cs.resize(1024); + if(stringprep(cs.data(), 1024, (Stringprep_profile_flags)0, stringprep_xmpp_resourceprep) != 0) + return false; + if(norm) + *norm = QString::fromUtf8(cs); + return true;*/ + return StringPrepCache::resourceprep(s, 1024, norm); +} diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/parser.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/parser.cpp new file mode 100644 index 00000000..e1a64532 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/parser.cpp @@ -0,0 +1,798 @@ +/* + * parser.cpp - parse an XMPP "document" + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + TODO: + + For XMPP::Parser to be "perfect", some things must be solved/changed in the + Qt library: + + - Fix weird QDomElement::haveAttributeNS() bug (patch submitted to + Trolltech on Aug 31st, 2003). + - Fix weird behavior in QXmlSimpleReader of reporting endElement() when + the '/' character of a self-closing tag is reached, instead of when + the final '>' is reached. + - Fix incremental parsing bugs in QXmlSimpleReader. At the moment, the + only bug I've found is related to attribute parsing, but there might + be more (search for '###' in $QTDIR/src/xml/qxml.cpp). + + We have workarounds for all of the above problems in the code below. + + - Deal with the <?xml?> processing instruction as an event type, so that we + can feed it back to the application properly. Right now it is completely + untrackable and is simply tacked into the first event's actualString. We + can't easily do this because QXmlSimpleReader eats an extra byte beyond + the processing instruction before reporting it. + + - Make QXmlInputSource capable of accepting data incrementally, to ensure + proper text encoding detection and processing over a network. This is + technically not a bug, as we have our own subclass below to do it, but + it would be nice if Qt had this already. +*/ + +#include"parser.h" + +#include<qtextcodec.h> +#include<qptrlist.h> +#include<string.h> + +using namespace XMPP; + +static bool qt_bug_check = false; +static bool qt_bug_have; + +//---------------------------------------------------------------------------- +// StreamInput +//---------------------------------------------------------------------------- +class StreamInput : public QXmlInputSource +{ +public: + StreamInput() + { + dec = 0; + reset(); + } + + ~StreamInput() + { + delete dec; + } + + void reset() + { + delete dec; + dec = 0; + in.resize(0); + out = ""; + at = 0; + paused = false; + mightChangeEncoding = true; + checkBad = true; + last = QChar(); + v_encoding = ""; + resetLastData(); + } + + void resetLastData() + { + last_string = ""; + } + + QString lastString() const + { + return last_string; + } + + void appendData(const QByteArray &a) + { + int oldsize = in.size(); + in.resize(oldsize + a.size()); + memcpy(in.data() + oldsize, a.data(), a.size()); + processBuf(); + } + + QChar lastRead() + { + return last; + } + + QChar next() + { + if(paused) + return EndOfData; + else + return readNext(); + } + + // NOTE: setting 'peek' to true allows the same char to be read again, + // however this still advances the internal byte processing. + QChar readNext(bool peek=false) + { + QChar c; + if(mightChangeEncoding) + c = EndOfData; + else { + if(out.isEmpty()) { + QString s; + if(!tryExtractPart(&s)) + c = EndOfData; + else { + out = s; + c = out[0]; + } + } + else + c = out[0]; + if(!peek) + out.remove(0, 1); + } + if(c == EndOfData) { +#ifdef XMPP_PARSER_DEBUG + printf("next() = EOD\n"); +#endif + } + else { +#ifdef XMPP_PARSER_DEBUG + printf("next() = [%c]\n", c.latin1()); +#endif + last = c; + } + + return c; + } + + QByteArray unprocessed() const + { + QByteArray a(in.size() - at); + memcpy(a.data(), in.data() + at, a.size()); + return a; + } + + void pause(bool b) + { + paused = b; + } + + bool isPaused() + { + return paused; + } + + QString encoding() const + { + return v_encoding; + } + +private: + QTextDecoder *dec; + QByteArray in; + QString out; + int at; + bool paused; + bool mightChangeEncoding; + QChar last; + QString v_encoding; + QString last_string; + bool checkBad; + + void processBuf() + { +#ifdef XMPP_PARSER_DEBUG + printf("processing. size=%d, at=%d\n", in.size(), at); +#endif + if(!dec) { + QTextCodec *codec = 0; + uchar *p = (uchar *)in.data() + at; + int size = in.size() - at; + + // do we have enough information to determine the encoding? + if(size == 0) + return; + bool utf16 = false; + if(p[0] == 0xfe || p[0] == 0xff) { + // probably going to be a UTF-16 byte order mark + if(size < 2) + return; + if((p[0] == 0xfe && p[1] == 0xff) || (p[0] == 0xff && p[1] == 0xfe)) { + // ok it is UTF-16 + utf16 = true; + } + } + if(utf16) + codec = QTextCodec::codecForMib(1000); // UTF-16 + else + codec = QTextCodec::codecForMib(106); // UTF-8 + + v_encoding = codec->name(); + dec = codec->makeDecoder(); + + // for utf16, put in the byte order mark + if(utf16) { + out += dec->toUnicode((const char *)p, 2); + at += 2; + } + } + + if(mightChangeEncoding) { + while(1) { + int n = out.find('<'); + if(n != -1) { + // we need a closing bracket + int n2 = out.find('>', n); + if(n2 != -1) { + ++n2; + QString h = out.mid(n, n2-n); + QString enc = processXmlHeader(h); + QTextCodec *codec = 0; + if(!enc.isEmpty()) + codec = QTextCodec::codecForName(enc.latin1()); + + // changing codecs + if(codec) { + v_encoding = codec->name(); + delete dec; + dec = codec->makeDecoder(); + } + mightChangeEncoding = false; + out.truncate(0); + at = 0; + resetLastData(); + break; + } + } + QString s; + if(!tryExtractPart(&s)) + break; + if(checkBad && checkForBadChars(s)) { + // go to the parser + mightChangeEncoding = false; + out.truncate(0); + at = 0; + resetLastData(); + break; + } + out += s; + } + } + } + + QString processXmlHeader(const QString &h) + { + if(h.left(5) != "<?xml") + return ""; + + int endPos = h.find(">"); + int startPos = h.find("encoding"); + if(startPos < endPos && startPos != -1) { + QString encoding; + do { + startPos++; + if(startPos > endPos) { + return ""; + } + } while(h[startPos] != '"' && h[startPos] != '\''); + startPos++; + while(h[startPos] != '"' && h[startPos] != '\'') { + encoding += h[startPos]; + startPos++; + if(startPos > endPos) { + return ""; + } + } + return encoding; + } + else + return ""; + } + + bool tryExtractPart(QString *s) + { + int size = in.size() - at; + if(size == 0) + return false; + uchar *p = (uchar *)in.data() + at; + QString nextChars; + while(1) { + nextChars = dec->toUnicode((const char *)p, 1); + ++p; + ++at; + if(!nextChars.isEmpty()) + break; + if(at == (int)in.size()) + return false; + } + last_string += nextChars; + *s = nextChars; + + // free processed data? + if(at >= 1024) { + char *p = in.data(); + int size = in.size() - at; + memmove(p, p + at, size); + in.resize(size); + at = 0; + } + + return true; + } + + bool checkForBadChars(const QString &s) + { + int len = s.find('<'); + if(len == -1) + len = s.length(); + else + checkBad = false; + for(int n = 0; n < len; ++n) { + if(!s.at(n).isSpace()) + return true; + } + return false; + } +}; + + +//---------------------------------------------------------------------------- +// ParserHandler +//---------------------------------------------------------------------------- +namespace XMPP +{ + class ParserHandler : public QXmlDefaultHandler + { + public: + ParserHandler(StreamInput *_in, QDomDocument *_doc) + { + in = _in; + doc = _doc; + needMore = false; + } + + ~ParserHandler() + { + eventList.setAutoDelete(true); + eventList.clear(); + } + + bool startDocument() + { + depth = 0; + return true; + } + + bool endDocument() + { + return true; + } + + bool startPrefixMapping(const QString &prefix, const QString &uri) + { + if(depth == 0) { + nsnames += prefix; + nsvalues += uri; + } + return true; + } + + bool startElement(const QString &namespaceURI, const QString &localName, const QString &qName, const QXmlAttributes &atts) + { + if(depth == 0) { + Parser::Event *e = new Parser::Event; + QXmlAttributes a; + for(int n = 0; n < atts.length(); ++n) { + QString uri = atts.uri(n); + QString ln = atts.localName(n); + if(a.index(uri, ln) == -1) + a.append(atts.qName(n), uri, ln, atts.value(n)); + } + e->setDocumentOpen(namespaceURI, localName, qName, a, nsnames, nsvalues); + nsnames.clear(); + nsvalues.clear(); + e->setActualString(in->lastString()); + + in->resetLastData(); + eventList.append(e); + in->pause(true); + } + else { + QDomElement e = doc->createElementNS(namespaceURI, qName); + for(int n = 0; n < atts.length(); ++n) { + QString uri = atts.uri(n); + QString ln = atts.localName(n); + bool have; + if(!uri.isEmpty()) { + have = e.hasAttributeNS(uri, ln); + if(qt_bug_have) + have = !have; + } + else + have = e.hasAttribute(ln); + if(!have) + e.setAttributeNS(uri, atts.qName(n), atts.value(n)); + } + + if(depth == 1) { + elem = e; + current = e; + } + else { + current.appendChild(e); + current = e; + } + } + ++depth; + return true; + } + + bool endElement(const QString &namespaceURI, const QString &localName, const QString &qName) + { + --depth; + if(depth == 0) { + Parser::Event *e = new Parser::Event; + e->setDocumentClose(namespaceURI, localName, qName); + e->setActualString(in->lastString()); + in->resetLastData(); + eventList.append(e); + in->pause(true); + } + else { + // done with a depth 1 element? + if(depth == 1) { + Parser::Event *e = new Parser::Event; + e->setElement(elem); + e->setActualString(in->lastString()); + in->resetLastData(); + eventList.append(e); + in->pause(true); + + elem = QDomElement(); + current = QDomElement(); + } + else + current = current.parentNode().toElement(); + } + + if(in->lastRead() == '/') + checkNeedMore(); + + return true; + } + + bool characters(const QString &str) + { + if(depth >= 1) { + QString content = str; + if(content.isEmpty()) + return true; + + if(!current.isNull()) { + QDomText text = doc->createTextNode(content); + current.appendChild(text); + } + } + return true; + } + + /*bool processingInstruction(const QString &target, const QString &data) + { + printf("Processing: [%s], [%s]\n", target.latin1(), data.latin1()); + in->resetLastData(); + return true; + }*/ + + void checkNeedMore() + { + // Here we will work around QXmlSimpleReader strangeness and self-closing tags. + // The problem is that endElement() is called when the '/' is read, not when + // the final '>' is read. This is a potential problem when obtaining unprocessed + // bytes from StreamInput after this event, as the '>' character will end up + // in the unprocessed chunk. To work around this, we need to advance StreamInput's + // internal byte processing, but not the xml character data. This way, the '>' + // will get processed and will no longer be in the unprocessed return, but + // QXmlSimpleReader can still read it. To do this, we call StreamInput::readNext + // with 'peek' mode. + QChar c = in->readNext(true); // peek + if(c == QXmlInputSource::EndOfData) { + needMore = true; + } + else { + // We'll assume the next char is a '>'. If it isn't, then + // QXmlSimpleReader will deal with that problem on the next + // parse. We don't need to take any action here. + needMore = false; + + // there should have been a pending event + Parser::Event *e = eventList.getFirst(); + if(e) { + e->setActualString(e->actualString() + '>'); + in->resetLastData(); + } + } + } + + Parser::Event *takeEvent() + { + if(needMore) + return 0; + if(eventList.isEmpty()) + return 0; + + Parser::Event *e = eventList.getFirst(); + eventList.removeRef(e); + in->pause(false); + return e; + } + + StreamInput *in; + QDomDocument *doc; + int depth; + QStringList nsnames, nsvalues; + QDomElement elem, current; + QPtrList<Parser::Event> eventList; + bool needMore; + }; +} + + +//---------------------------------------------------------------------------- +// Event +//---------------------------------------------------------------------------- +class Parser::Event::Private +{ +public: + int type; + QString ns, ln, qn; + QXmlAttributes a; + QDomElement e; + QString str; + QStringList nsnames, nsvalues; +}; + +Parser::Event::Event() +{ + d = 0; +} + +Parser::Event::Event(const Event &from) +{ + d = 0; + *this = from; +} + +Parser::Event & Parser::Event::operator=(const Event &from) +{ + delete d; + d = 0; + if(from.d) + d = new Private(*from.d); + return *this; +} + +Parser::Event::~Event() +{ + delete d; +} + +bool Parser::Event::isNull() const +{ + return (d ? false: true); +} + +int Parser::Event::type() const +{ + if(isNull()) + return -1; + return d->type; +} + +QString Parser::Event::nsprefix(const QString &s) const +{ + QStringList::ConstIterator it = d->nsnames.begin(); + QStringList::ConstIterator it2 = d->nsvalues.begin(); + for(; it != d->nsnames.end(); ++it) { + if((*it) == s) + return (*it2); + ++it2; + } + return QString::null; +} + +QString Parser::Event::namespaceURI() const +{ + return d->ns; +} + +QString Parser::Event::localName() const +{ + return d->ln; +} + +QString Parser::Event::qName() const +{ + return d->qn; +} + +QXmlAttributes Parser::Event::atts() const +{ + return d->a; +} + +QString Parser::Event::actualString() const +{ + return d->str; +} + +QDomElement Parser::Event::element() const +{ + return d->e; +} + +void Parser::Event::setDocumentOpen(const QString &namespaceURI, const QString &localName, const QString &qName, const QXmlAttributes &atts, const QStringList &nsnames, const QStringList &nsvalues) +{ + if(!d) + d = new Private; + d->type = DocumentOpen; + d->ns = namespaceURI; + d->ln = localName; + d->qn = qName; + d->a = atts; + d->nsnames = nsnames; + d->nsvalues = nsvalues; +} + +void Parser::Event::setDocumentClose(const QString &namespaceURI, const QString &localName, const QString &qName) +{ + if(!d) + d = new Private; + d->type = DocumentClose; + d->ns = namespaceURI; + d->ln = localName; + d->qn = qName; +} + +void Parser::Event::setElement(const QDomElement &elem) +{ + if(!d) + d = new Private; + d->type = Element; + d->e = elem; +} + +void Parser::Event::setError() +{ + if(!d) + d = new Private; + d->type = Error; +} + +void Parser::Event::setActualString(const QString &str) +{ + d->str = str; +} + +//---------------------------------------------------------------------------- +// Parser +//---------------------------------------------------------------------------- +class Parser::Private +{ +public: + Private() + { + doc = 0; + in = 0; + handler = 0; + reader = 0; + reset(); + } + + ~Private() + { + reset(false); + } + + void reset(bool create=true) + { + delete reader; + delete handler; + delete in; + delete doc; + + if(create) { + doc = new QDomDocument; + in = new StreamInput; + handler = new ParserHandler(in, doc); + reader = new QXmlSimpleReader; + reader->setContentHandler(handler); + + // initialize the reader + in->pause(true); + reader->parse(in, true); + in->pause(false); + } + } + + QDomDocument *doc; + StreamInput *in; + ParserHandler *handler; + QXmlSimpleReader *reader; +}; + +Parser::Parser() +{ + d = new Private; + + // check for evil bug in Qt <= 3.2.1 + if(!qt_bug_check) { + qt_bug_check = true; + QDomElement e = d->doc->createElementNS("someuri", "somename"); + if(e.hasAttributeNS("someuri", "somename")) + qt_bug_have = true; + else + qt_bug_have = false; + } +} + +Parser::~Parser() +{ + delete d; +} + +void Parser::reset() +{ + d->reset(); +} + +void Parser::appendData(const QByteArray &a) +{ + d->in->appendData(a); + + // if handler was waiting for more, give it a kick + if(d->handler->needMore) + d->handler->checkNeedMore(); +} + +Parser::Event Parser::readNext() +{ + Event e; + if(d->handler->needMore) + return e; + Event *ep = d->handler->takeEvent(); + if(!ep) { + if(!d->reader->parseContinue()) { + e.setError(); + return e; + } + ep = d->handler->takeEvent(); + if(!ep) + return e; + } + e = *ep; + delete ep; + return e; +} + +QByteArray Parser::unprocessed() const +{ + return d->in->unprocessed(); +} + +QString Parser::encoding() const +{ + return d->in->encoding(); +} diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/parser.h b/kopete/protocols/jabber/libiris/iris/xmpp-core/parser.h new file mode 100644 index 00000000..808b6c3d --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/parser.h @@ -0,0 +1,86 @@ +/* + * parser.h - parse an XMPP "document" + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef PARSER_H +#define PARSER_H + +#include<qdom.h> +#include<qxml.h> + +namespace XMPP +{ + class Parser + { + public: + Parser(); + ~Parser(); + + class Event + { + public: + enum Type { DocumentOpen, DocumentClose, Element, Error }; + Event(); + Event(const Event &); + Event & operator=(const Event &); + ~Event(); + + bool isNull() const; + int type() const; + + // for document open + QString nsprefix(const QString &s=QString::null) const; + + // for document open / close + QString namespaceURI() const; + QString localName() const; + QString qName() const; + QXmlAttributes atts() const; + + // for element + QDomElement element() const; + + // for any + QString actualString() const; + + // setup + void setDocumentOpen(const QString &namespaceURI, const QString &localName, const QString &qName, const QXmlAttributes &atts, const QStringList &nsnames, const QStringList &nsvalues); + void setDocumentClose(const QString &namespaceURI, const QString &localName, const QString &qName); + void setElement(const QDomElement &elem); + void setError(); + void setActualString(const QString &); + + private: + class Private; + Private *d; + }; + + void reset(); + void appendData(const QByteArray &a); + Event readNext(); + QByteArray unprocessed() const; + QString encoding() const; + + private: + class Private; + Private *d; + }; +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/protocol.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/protocol.cpp new file mode 100644 index 00000000..dfd3253c --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/protocol.cpp @@ -0,0 +1,1595 @@ +/* + * protocol.cpp - XMPP-Core protocol state machine + * Copyright (C) 2004 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +// TODO: let the app know if tls is required +// require mutual auth for server out/in +// report ErrProtocol if server uses wrong NS +// use send() instead of writeElement() in CoreProtocol + +#include"protocol.h" + +#include<qca.h> +#include"base64.h" +#include"hash.h" + +#ifdef XMPP_TEST +#include"td.h" +#endif + +using namespace XMPP; + +// printArray +// +// This function prints out an array of bytes as latin characters, converting +// non-printable bytes into hex values as necessary. Useful for displaying +// QByteArrays for debugging purposes. +static QString printArray(const QByteArray &a) +{ + QString s; + for(uint n = 0; n < a.size(); ++n) { + unsigned char c = (unsigned char)a[(int)n]; + if(c < 32 || c >= 127) { + QString str; + str.sprintf("[%02x]", c); + s += str; + } + else + s += c; + } + return s; +} + +// firstChildElement +// +// Get an element's first child element +static QDomElement firstChildElement(const QDomElement &e) +{ + for(QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { + if(n.isElement()) + return n.toElement(); + } + return QDomElement(); +} + +//---------------------------------------------------------------------------- +// Version +//---------------------------------------------------------------------------- +Version::Version(int maj, int min) +{ + major = maj; + minor = min; +} + +//---------------------------------------------------------------------------- +// StreamFeatures +//---------------------------------------------------------------------------- +StreamFeatures::StreamFeatures() +{ + tls_supported = false; + sasl_supported = false; + bind_supported = false; + tls_required = false; +} + +//---------------------------------------------------------------------------- +// BasicProtocol +//---------------------------------------------------------------------------- +BasicProtocol::SASLCondEntry BasicProtocol::saslCondTable[] = +{ + { "aborted", Aborted }, + { "incorrect-encoding", IncorrectEncoding }, + { "invalid-authzid", InvalidAuthzid }, + { "invalid-mechanism", InvalidMech }, + { "mechanism-too-weak", MechTooWeak }, + { "not-authorized", NotAuthorized }, + { "temporary-auth-failure", TemporaryAuthFailure }, + { 0, 0 }, +}; + +BasicProtocol::StreamCondEntry BasicProtocol::streamCondTable[] = +{ + { "bad-format", BadFormat }, + { "bad-namespace-prefix", BadNamespacePrefix }, + { "conflict", Conflict }, + { "connection-timeout", ConnectionTimeout }, + { "host-gone", HostGone }, + { "host-unknown", HostUnknown }, + { "improper-addressing", ImproperAddressing }, + { "internal-server-error", InternalServerError }, + { "invalid-from", InvalidFrom }, + { "invalid-id", InvalidId }, + { "invalid-namespace", InvalidNamespace }, + { "invalid-xml", InvalidXml }, + { "not-authorized", StreamNotAuthorized }, + { "policy-violation", PolicyViolation }, + { "remote-connection-failed", RemoteConnectionFailed }, + { "resource-constraint", ResourceConstraint }, + { "restricted-xml", RestrictedXml }, + { "see-other-host", SeeOtherHost }, + { "system-shutdown", SystemShutdown }, + { "undefined-condition", UndefinedCondition }, + { "unsupported-encoding", UnsupportedEncoding }, + { "unsupported-stanza-type", UnsupportedStanzaType }, + { "unsupported-version", UnsupportedVersion }, + { "xml-not-well-formed", XmlNotWellFormed }, + { 0, 0 }, +}; + +BasicProtocol::BasicProtocol() +:XmlProtocol() +{ + init(); +} + +BasicProtocol::~BasicProtocol() +{ +} + +void BasicProtocol::init() +{ + errCond = -1; + sasl_authed = false; + doShutdown = false; + delayedError = false; + closeError = false; + ready = false; + stanzasPending = 0; + stanzasWritten = 0; +} + +void BasicProtocol::reset() +{ + XmlProtocol::reset(); + init(); + + to = QString(); + from = QString(); + id = QString(); + lang = QString(); + version = Version(1,0); + errText = QString(); + errAppSpec = QDomElement(); + otherHost = QString(); + spare.resize(0); + sasl_mech = QString(); + sasl_mechlist.clear(); + sasl_step.resize(0); + stanzaToRecv = QDomElement(); + sendList.clear(); +} + +void BasicProtocol::sendStanza(const QDomElement &e) +{ + SendItem i; + i.stanzaToSend = e; + sendList += i; +} + +void BasicProtocol::sendDirect(const QString &s) +{ + SendItem i; + i.stringToSend = s; + sendList += i; +} + +void BasicProtocol::sendWhitespace() +{ + SendItem i; + i.doWhitespace = true; + sendList += i; +} + +QDomElement BasicProtocol::recvStanza() +{ + QDomElement e = stanzaToRecv; + stanzaToRecv = QDomElement(); + return e; +} + +void BasicProtocol::shutdown() +{ + doShutdown = true; +} + +void BasicProtocol::shutdownWithError(int cond, const QString &str) +{ + otherHost = str; + delayErrorAndClose(cond); +} + +bool BasicProtocol::isReady() const +{ + return ready; +} + +void BasicProtocol::setReady(bool b) +{ + ready = b; +} + +QString BasicProtocol::saslMech() const +{ + return sasl_mech; +} + +QByteArray BasicProtocol::saslStep() const +{ + return sasl_step; +} + +void BasicProtocol::setSASLMechList(const QStringList &list) +{ + sasl_mechlist = list; +} + +void BasicProtocol::setSASLFirst(const QString &mech, const QByteArray &step) +{ + sasl_mech = mech; + sasl_step = step; +} + +void BasicProtocol::setSASLNext(const QByteArray &step) +{ + sasl_step = step; +} + +void BasicProtocol::setSASLAuthed() +{ + sasl_authed = true; +} + +int BasicProtocol::stringToSASLCond(const QString &s) +{ + for(int n = 0; saslCondTable[n].str; ++n) { + if(s == saslCondTable[n].str) + return saslCondTable[n].cond; + } + return -1; +} + +int BasicProtocol::stringToStreamCond(const QString &s) +{ + for(int n = 0; streamCondTable[n].str; ++n) { + if(s == streamCondTable[n].str) + return streamCondTable[n].cond; + } + return -1; +} + +QString BasicProtocol::saslCondToString(int x) +{ + for(int n = 0; saslCondTable[n].str; ++n) { + if(x == saslCondTable[n].cond) + return saslCondTable[n].str; + } + return QString(); +} + +QString BasicProtocol::streamCondToString(int x) +{ + for(int n = 0; streamCondTable[n].str; ++n) { + if(x == streamCondTable[n].cond) + return streamCondTable[n].str; + } + return QString(); +} + +void BasicProtocol::extractStreamError(const QDomElement &e) +{ + QString text; + QDomElement appSpec; + + QDomElement t = firstChildElement(e); + if(t.isNull() || t.namespaceURI() != NS_STREAMS) { + // probably old-style error + errCond = -1; + errText = e.text(); + } + else + errCond = stringToStreamCond(t.tagName()); + + if(errCond != -1) { + if(errCond == SeeOtherHost) + otherHost = t.text(); + + t = e.elementsByTagNameNS(NS_STREAMS, "text").item(0).toElement(); + if(!t.isNull()) + text = t.text(); + + // find first non-standard namespaced element + QDomNodeList nl = e.childNodes(); + for(uint n = 0; n < nl.count(); ++n) { + QDomNode i = nl.item(n); + if(i.isElement() && i.namespaceURI() != NS_STREAMS) { + appSpec = i.toElement(); + break; + } + } + + errText = text; + errAppSpec = appSpec; + } +} + +void BasicProtocol::send(const QDomElement &e, bool clip) +{ + writeElement(e, TypeElement, false, clip); +} + +void BasicProtocol::sendStreamError(int cond, const QString &text, const QDomElement &appSpec) +{ + QDomElement se = doc.createElementNS(NS_ETHERX, "stream:error"); + QDomElement err = doc.createElementNS(NS_STREAMS, streamCondToString(cond)); + if(!otherHost.isEmpty()) + err.appendChild(doc.createTextNode(otherHost)); + se.appendChild(err); + if(!text.isEmpty()) { + QDomElement te = doc.createElementNS(NS_STREAMS, "text"); + te.setAttributeNS(NS_XML, "xml:lang", "en"); + te.appendChild(doc.createTextNode(text)); + se.appendChild(te); + } + se.appendChild(appSpec); + + writeElement(se, 100, false); +} + +void BasicProtocol::sendStreamError(const QString &text) +{ + QDomElement se = doc.createElementNS(NS_ETHERX, "stream:error"); + se.appendChild(doc.createTextNode(text)); + + writeElement(se, 100, false); +} + +bool BasicProtocol::errorAndClose(int cond, const QString &text, const QDomElement &appSpec) +{ + closeError = true; + errCond = cond; + errText = text; + errAppSpec = appSpec; + sendStreamError(cond, text, appSpec); + return close(); +} + +bool BasicProtocol::error(int code) +{ + event = EError; + errorCode = code; + return true; +} + +void BasicProtocol::delayErrorAndClose(int cond, const QString &text, const QDomElement &appSpec) +{ + errorCode = ErrStream; + errCond = cond; + errText = text; + errAppSpec = appSpec; + delayedError = true; +} + +void BasicProtocol::delayError(int code) +{ + errorCode = code; + delayedError = true; +} + +QDomElement BasicProtocol::docElement() +{ + // create the root element + QDomElement e = doc.createElementNS(NS_ETHERX, "stream:stream"); + + QString defns = defaultNamespace(); + QStringList list = extraNamespaces(); + + // HACK: using attributes seems to be the only way to get additional namespaces in here + if(!defns.isEmpty()) + e.setAttribute("xmlns", defns); + for(QStringList::ConstIterator it = list.begin(); it != list.end();) { + QString prefix = *(it++); + QString uri = *(it++); + e.setAttribute(QString("xmlns:") + prefix, uri); + } + + // additional attributes + if(!isIncoming() && !to.isEmpty()) + e.setAttribute("to", to); + if(isIncoming() && !from.isEmpty()) + e.setAttribute("from", from); + if(!id.isEmpty()) + e.setAttribute("id", id); + if(!lang.isEmpty()) + e.setAttributeNS(NS_XML, "xml:lang", lang); + if(version.major > 0 || version.minor > 0) + e.setAttribute("version", QString::number(version.major) + '.' + QString::number(version.minor)); + + return e; +} + +void BasicProtocol::handleDocOpen(const Parser::Event &pe) +{ + if(isIncoming()) { + if(xmlEncoding() != "UTF-8") { + delayErrorAndClose(UnsupportedEncoding); + return; + } + } + + if(pe.namespaceURI() == NS_ETHERX && pe.localName() == "stream") { + QXmlAttributes atts = pe.atts(); + + // grab the version + int major = 0; + int minor = 0; + QString verstr = atts.value("version"); + if(!verstr.isEmpty()) { + int n = verstr.find('.'); + if(n != -1) { + major = verstr.mid(0, n).toInt(); + minor = verstr.mid(n+1).toInt(); + } + else { + major = verstr.toInt(); + minor = 0; + } + } + version = Version(major, minor); + + if(isIncoming()) { + to = atts.value("to"); + QString peerLang = atts.value(NS_XML, "lang"); + if(!peerLang.isEmpty()) + lang = peerLang; + } + // outgoing + else { + from = atts.value("from"); + lang = atts.value(NS_XML, "lang"); + id = atts.value("id"); + } + + handleStreamOpen(pe); + } + else { + if(isIncoming()) + delayErrorAndClose(BadFormat); + else + delayError(ErrProtocol); + } +} + +bool BasicProtocol::handleError() +{ + if(isIncoming()) + return errorAndClose(XmlNotWellFormed); + else + return error(ErrParse); +} + +bool BasicProtocol::handleCloseFinished() +{ + if(closeError) { + event = EError; + errorCode = ErrStream; + // note: errCond and friends are already set at this point + } + else + event = EClosed; + return true; +} + +bool BasicProtocol::doStep(const QDomElement &e) +{ + // handle pending error + if(delayedError) { + if(isIncoming()) + return errorAndClose(errCond, errText, errAppSpec); + else + return error(errorCode); + } + + // shutdown? + if(doShutdown) { + doShutdown = false; + return close(); + } + + if(!e.isNull()) { + // check for error + if(e.namespaceURI() == NS_ETHERX && e.tagName() == "error") { + extractStreamError(e); + return error(ErrStream); + } + } + + if(ready) { + // stanzas written? + if(stanzasWritten > 0) { + --stanzasWritten; + event = EStanzaSent; + return true; + } + // send items? + if(!sendList.isEmpty()) { + SendItem i; + { + QValueList<SendItem>::Iterator it = sendList.begin(); + i = (*it); + sendList.remove(it); + } + + // outgoing stanza? + if(!i.stanzaToSend.isNull()) { + ++stanzasPending; + writeElement(i.stanzaToSend, TypeStanza, true); + event = ESend; + } + // direct send? + else if(!i.stringToSend.isEmpty()) { + writeString(i.stringToSend, TypeDirect, true); + event = ESend; + } + // whitespace keepalive? + else if(i.doWhitespace) { + writeString("\n", TypePing, false); + event = ESend; + } + return true; + } + else { + // if we have pending outgoing stanzas, ask for write notification + if(stanzasPending) + notify |= NSend; + } + } + + return doStep2(e); +} + +void BasicProtocol::itemWritten(int id, int) +{ + if(id == TypeStanza) { + --stanzasPending; + ++stanzasWritten; + } +} + +QString BasicProtocol::defaultNamespace() +{ + // default none + return QString(); +} + +QStringList BasicProtocol::extraNamespaces() +{ + // default none + return QStringList(); +} + +void BasicProtocol::handleStreamOpen(const Parser::Event &) +{ + // default does nothing +} + +//---------------------------------------------------------------------------- +// CoreProtocol +//---------------------------------------------------------------------------- +CoreProtocol::CoreProtocol() +:BasicProtocol() +{ + init(); +} + +CoreProtocol::~CoreProtocol() +{ +} + +void CoreProtocol::init() +{ + step = Start; + + // ?? + server = false; + dialback = false; + dialback_verify = false; + + // settings + jid = Jid(); + password = QString(); + oldOnly = false; + allowPlain = false; + doTLS = true; + doAuth = true; + doBinding = true; + + // input + user = QString(); + host = QString(); + + // status + old = false; + digest = false; + tls_started = false; + sasl_started = false; +} + +void CoreProtocol::reset() +{ + BasicProtocol::reset(); + init(); +} + +void CoreProtocol::startClientOut(const Jid &_jid, bool _oldOnly, bool tlsActive, bool _doAuth) +{ + jid = _jid; + to = _jid.domain(); + oldOnly = _oldOnly; + doAuth = _doAuth; + tls_started = tlsActive; + + if(oldOnly) + version = Version(0,0); + startConnect(); +} + +void CoreProtocol::startServerOut(const QString &_to) +{ + server = true; + to = _to; + startConnect(); +} + +void CoreProtocol::startDialbackOut(const QString &_to, const QString &_from) +{ + server = true; + dialback = true; + to = _to; + self_from = _from; + startConnect(); +} + +void CoreProtocol::startDialbackVerifyOut(const QString &_to, const QString &_from, const QString &id, const QString &key) +{ + server = true; + dialback = true; + dialback_verify = true; + to = _to; + self_from = _from; + dialback_id = id; + dialback_key = key; + startConnect(); +} + +void CoreProtocol::startClientIn(const QString &_id) +{ + id = _id; + startAccept(); +} + +void CoreProtocol::startServerIn(const QString &_id) +{ + server = true; + id = _id; + startAccept(); +} + +void CoreProtocol::setLang(const QString &s) +{ + lang = s; +} + +void CoreProtocol::setAllowTLS(bool b) +{ + doTLS = b; +} + +void CoreProtocol::setAllowBind(bool b) +{ + doBinding = b; +} + +void CoreProtocol::setAllowPlain(bool b) +{ + allowPlain = b; +} + +void CoreProtocol::setPassword(const QString &s) +{ + password = s; +} + +void CoreProtocol::setFrom(const QString &s) +{ + from = s; +} + +void CoreProtocol::setDialbackKey(const QString &s) +{ + dialback_key = s; +} + +bool CoreProtocol::loginComplete() +{ + setReady(true); + + event = EReady; + step = Done; + return true; +} + +int CoreProtocol::getOldErrorCode(const QDomElement &e) +{ + QDomElement err = e.elementsByTagNameNS(NS_CLIENT, "error").item(0).toElement(); + if(err.isNull() || !err.hasAttribute("code")) + return -1; + return err.attribute("code").toInt(); +} + +/*QString CoreProtocol::xmlToString(const QDomElement &e, bool clip) +{ + // determine an appropriate 'fakeNS' to use + QString ns; + if(e.prefix() == "stream") + ns = NS_ETHERX; + else if(e.prefix() == "db") + ns = NS_DIALBACK; + else + ns = NS_CLIENT; + return ::xmlToString(e, ns, "stream:stream", clip); +}*/ + +bool CoreProtocol::stepAdvancesParser() const +{ + if(stepRequiresElement()) + return true; + else if(isReady()) + return true; + return false; +} + +// all element-needing steps need to be registered here +bool CoreProtocol::stepRequiresElement() const +{ + switch(step) { + case GetFeatures: + case GetTLSProceed: + case GetSASLChallenge: + case GetBindResponse: + case GetAuthGetResponse: + case GetAuthSetResponse: + case GetRequest: + case GetSASLResponse: + return true; + } + return false; +} + +void CoreProtocol::stringSend(const QString &s) +{ +#ifdef XMPP_TEST + TD::outgoingTag(s); +#endif +} + +void CoreProtocol::stringRecv(const QString &s) +{ +#ifdef XMPP_TEST + TD::incomingTag(s); +#endif +} + +QString CoreProtocol::defaultNamespace() +{ + if(server) + return NS_SERVER; + else + return NS_CLIENT; +} + +QStringList CoreProtocol::extraNamespaces() +{ + QStringList list; + if(dialback) { + list += "db"; + list += NS_DIALBACK; + } + return list; +} + +void CoreProtocol::handleStreamOpen(const Parser::Event &pe) +{ + if(isIncoming()) { + QString ns = pe.nsprefix(); + QString db; + if(server) { + db = pe.nsprefix("db"); + if(!db.isEmpty()) + dialback = true; + } + + // verify namespace + if((!server && ns != NS_CLIENT) || (server && ns != NS_SERVER) || (dialback && db != NS_DIALBACK)) { + delayErrorAndClose(InvalidNamespace); + return; + } + + // verify version + if(version.major < 1 && !dialback) { + delayErrorAndClose(UnsupportedVersion); + return; + } + } + else { + if(!dialback) { + if(version.major >= 1 && !oldOnly) + old = false; + else + old = true; + } + } +} + +void CoreProtocol::elementSend(const QDomElement &e) +{ +#ifdef XMPP_TEST + TD::outgoingXml(e); +#endif +} + +void CoreProtocol::elementRecv(const QDomElement &e) +{ +#ifdef XMPP_TEST + TD::incomingXml(e); +#endif +} + +bool CoreProtocol::doStep2(const QDomElement &e) +{ + if(dialback) + return dialbackStep(e); + else + return normalStep(e); +} + +bool CoreProtocol::isValidStanza(const QDomElement &e) const +{ + QString s = e.tagName(); + if(e.namespaceURI() == (server ? NS_SERVER : NS_CLIENT) && (s == "message" || s == "presence" || s == "iq")) + return true; + else + return false; +} + +bool CoreProtocol::grabPendingItem(const Jid &to, const Jid &from, int type, DBItem *item) +{ + for(QValueList<DBItem>::Iterator it = dbpending.begin(); it != dbpending.end(); ++it) { + const DBItem &i = *it; + if(i.type == type && i.to.compare(to) && i.from.compare(from)) { + const DBItem &i = (*it); + *item = i; + dbpending.remove(it); + return true; + } + } + return false; +} + +bool CoreProtocol::dialbackStep(const QDomElement &e) +{ + if(step == Start) { + setReady(true); + step = Done; + event = EReady; + return true; + } + + if(!dbrequests.isEmpty()) { + // process a request + DBItem i; + { + QValueList<DBItem>::Iterator it = dbrequests.begin(); + i = (*it); + dbrequests.remove(it); + } + + QDomElement r; + if(i.type == DBItem::ResultRequest) { + r = doc.createElementNS(NS_DIALBACK, "db:result"); + r.setAttribute("to", i.to.full()); + r.setAttribute("from", i.from.full()); + r.appendChild(doc.createTextNode(i.key)); + dbpending += i; + } + else if(i.type == DBItem::ResultGrant) { + r = doc.createElementNS(NS_DIALBACK, "db:result"); + r.setAttribute("to", i.to.full()); + r.setAttribute("from", i.from.full()); + r.setAttribute("type", i.ok ? "valid" : "invalid"); + if(i.ok) { + i.type = DBItem::Validated; + dbvalidated += i; + } + else { + // TODO: disconnect after writing element + } + } + else if(i.type == DBItem::VerifyRequest) { + r = doc.createElementNS(NS_DIALBACK, "db:verify"); + r.setAttribute("to", i.to.full()); + r.setAttribute("from", i.from.full()); + r.setAttribute("id", i.id); + r.appendChild(doc.createTextNode(i.key)); + dbpending += i; + } + // VerifyGrant + else { + r = doc.createElementNS(NS_DIALBACK, "db:verify"); + r.setAttribute("to", i.to.full()); + r.setAttribute("from", i.from.full()); + r.setAttribute("id", i.id); + r.setAttribute("type", i.ok ? "valid" : "invalid"); + } + + writeElement(r, TypeElement, false); + event = ESend; + return true; + } + + if(!e.isNull()) { + if(e.namespaceURI() == NS_DIALBACK) { + if(e.tagName() == "result") { + Jid to, from; + to.set(e.attribute("to"), ""); + from.set(e.attribute("from"), ""); + if(isIncoming()) { + QString key = e.text(); + // TODO: report event + } + else { + bool ok = (e.attribute("type") == "valid") ? true: false; + DBItem i; + if(grabPendingItem(from, to, DBItem::ResultRequest, &i)) { + if(ok) { + i.type = DBItem::Validated; + i.ok = true; + dbvalidated += i; + // TODO: report event + } + else { + // TODO: report event + } + } + } + } + else if(e.tagName() == "verify") { + Jid to, from; + to.set(e.attribute("to"), ""); + from.set(e.attribute("from"), ""); + QString id = e.attribute("id"); + if(isIncoming()) { + QString key = e.text(); + // TODO: report event + } + else { + bool ok = (e.attribute("type") == "valid") ? true: false; + DBItem i; + if(grabPendingItem(from, to, DBItem::VerifyRequest, &i)) { + if(ok) { + // TODO: report event + } + else { + // TODO: report event + } + } + } + } + } + else { + if(isReady()) { + if(isValidStanza(e)) { + // TODO: disconnect if stanza is from unverified sender + // TODO: ignore packets from receiving servers + stanzaToRecv = e; + event = EStanzaReady; + return true; + } + } + } + } + + need = NNotify; + notify |= NRecv; + return false; +} + +bool CoreProtocol::normalStep(const QDomElement &e) +{ + if(step == Start) { + if(isIncoming()) { + need = NSASLMechs; + step = SendFeatures; + return false; + } + else { + if(old) { + if(doAuth) + step = HandleAuthGet; + else + return loginComplete(); + } + else + step = GetFeatures; + + return processStep(); + } + } + else if(step == HandleFeatures) { + // deal with TLS? + if(doTLS && !tls_started && !sasl_authed && features.tls_supported) { + QDomElement e = doc.createElementNS(NS_TLS, "starttls"); + + send(e, true); + event = ESend; + step = GetTLSProceed; + return true; + } + + // deal with SASL? + if(!sasl_authed) { + if(!features.sasl_supported) { + // SASL MUST be supported + event = EError; + errorCode = ErrProtocol; + return true; + } + +#ifdef XMPP_TEST + TD::msg("starting SASL authentication..."); +#endif + need = NSASLFirst; + step = GetSASLFirst; + return false; + } + + if(server) { + return loginComplete(); + } + else { + if(!doBinding) + return loginComplete(); + } + + // deal with bind + if(!features.bind_supported) { + // bind MUST be supported + event = EError; + errorCode = ErrProtocol; + return true; + } + + QDomElement e = doc.createElement("iq"); + e.setAttribute("type", "set"); + e.setAttribute("id", "bind_1"); + QDomElement b = doc.createElementNS(NS_BIND, "bind"); + + // request specific resource? + QString resource = jid.resource(); + if(!resource.isEmpty()) { + QDomElement r = doc.createElement("resource"); + r.appendChild(doc.createTextNode(jid.resource())); + b.appendChild(r); + } + + e.appendChild(b); + + send(e); + event = ESend; + step = GetBindResponse; + return true; + } + else if(step == GetSASLFirst) { + QDomElement e = doc.createElementNS(NS_SASL, "auth"); + e.setAttribute("mechanism", sasl_mech); + if(!sasl_step.isEmpty()) { +#ifdef XMPP_TEST + TD::msg(QString("SASL OUT: [%1]").arg(printArray(sasl_step))); +#endif + e.appendChild(doc.createTextNode(Base64::arrayToString(sasl_step))); + } + + send(e, true); + event = ESend; + step = GetSASLChallenge; + return true; + } + else if(step == GetSASLNext) { + if(isIncoming()) { + if(sasl_authed) { + QDomElement e = doc.createElementNS(NS_SASL, "success"); + writeElement(e, TypeElement, false, true); + event = ESend; + step = IncHandleSASLSuccess; + return true; + } + else { + QByteArray stepData = sasl_step; + QDomElement e = doc.createElementNS(NS_SASL, "challenge"); + if(!stepData.isEmpty()) + e.appendChild(doc.createTextNode(Base64::arrayToString(stepData))); + + writeElement(e, TypeElement, false, true); + event = ESend; + step = GetSASLResponse; + return true; + } + } + else { + QByteArray stepData = sasl_step; +#ifdef XMPP_TEST + TD::msg(QString("SASL OUT: [%1]").arg(printArray(sasl_step))); +#endif + QDomElement e = doc.createElementNS(NS_SASL, "response"); + if(!stepData.isEmpty()) + e.appendChild(doc.createTextNode(Base64::arrayToString(stepData))); + + send(e, true); + event = ESend; + step = GetSASLChallenge; + return true; + } + } + else if(step == HandleSASLSuccess) { + need = NSASLLayer; + spare = resetStream(); + step = Start; + return false; + } + else if(step == HandleAuthGet) { + QDomElement e = doc.createElement("iq"); + e.setAttribute("to", to); + e.setAttribute("type", "get"); + e.setAttribute("id", "auth_1"); + QDomElement q = doc.createElementNS("jabber:iq:auth", "query"); + QDomElement u = doc.createElement("username"); + u.appendChild(doc.createTextNode(jid.node())); + q.appendChild(u); + e.appendChild(q); + + send(e); + event = ESend; + step = GetAuthGetResponse; + return true; + } + else if(step == HandleAuthSet) { + QDomElement e = doc.createElement("iq"); + e.setAttribute("to", to); + e.setAttribute("type", "set"); + e.setAttribute("id", "auth_2"); + QDomElement q = doc.createElementNS("jabber:iq:auth", "query"); + QDomElement u = doc.createElement("username"); + u.appendChild(doc.createTextNode(jid.node())); + q.appendChild(u); + QDomElement p; + if(digest) { + // need SHA1 here + if(!QCA::isSupported(QCA::CAP_SHA1)) + QCA::insertProvider(createProviderHash()); + + p = doc.createElement("digest"); + QCString cs = id.utf8() + password.utf8(); + p.appendChild(doc.createTextNode(QCA::SHA1::hashToString(cs))); + } + else { + p = doc.createElement("password"); + p.appendChild(doc.createTextNode(password)); + } + q.appendChild(p); + QDomElement r = doc.createElement("resource"); + r.appendChild(doc.createTextNode(jid.resource())); + q.appendChild(r); + e.appendChild(q); + + send(e, true); + event = ESend; + step = GetAuthSetResponse; + return true; + } + // server + else if(step == SendFeatures) { + QDomElement f = doc.createElementNS(NS_ETHERX, "stream:features"); + if(!tls_started && !sasl_authed) { // don't offer tls if we are already sasl'd + QDomElement tls = doc.createElementNS(NS_TLS, "starttls"); + f.appendChild(tls); + } + + if(sasl_authed) { + if(!server) { + QDomElement bind = doc.createElementNS(NS_BIND, "bind"); + f.appendChild(bind); + } + } + else { + QDomElement mechs = doc.createElementNS(NS_SASL, "mechanisms"); + for(QStringList::ConstIterator it = sasl_mechlist.begin(); it != sasl_mechlist.end(); ++it) { + QDomElement m = doc.createElement("mechanism"); + m.appendChild(doc.createTextNode(*it)); + mechs.appendChild(m); + } + f.appendChild(mechs); + } + + writeElement(f, TypeElement, false); + event = ESend; + step = GetRequest; + return true; + } + // server + else if(step == HandleTLS) { + tls_started = true; + need = NStartTLS; + spare = resetStream(); + step = Start; + return false; + } + // server + else if(step == IncHandleSASLSuccess) { + event = ESASLSuccess; + spare = resetStream(); + step = Start; + printf("sasl success\n"); + return true; + } + else if(step == GetFeatures) { + // we are waiting for stream features + if(e.namespaceURI() == NS_ETHERX && e.tagName() == "features") { + // extract features + StreamFeatures f; + QDomElement s = e.elementsByTagNameNS(NS_TLS, "starttls").item(0).toElement(); + if(!s.isNull()) { + f.tls_supported = true; + f.tls_required = s.elementsByTagNameNS(NS_TLS, "required").count() > 0; + } + QDomElement m = e.elementsByTagNameNS(NS_SASL, "mechanisms").item(0).toElement(); + if(!m.isNull()) { + f.sasl_supported = true; + QDomNodeList l = m.elementsByTagNameNS(NS_SASL, "mechanism"); + for(uint n = 0; n < l.count(); ++n) + f.sasl_mechs += l.item(n).toElement().text(); + } + QDomElement b = e.elementsByTagNameNS(NS_BIND, "bind").item(0).toElement(); + if(!b.isNull()) + f.bind_supported = true; + + if(f.tls_supported) { +#ifdef XMPP_TEST + QString s = "STARTTLS is available"; + if(f.tls_required) + s += " (required)"; + TD::msg(s); +#endif + } + if(f.sasl_supported) { +#ifdef XMPP_TEST + QString s = "SASL mechs:"; + for(QStringList::ConstIterator it = f.sasl_mechs.begin(); it != f.sasl_mechs.end(); ++it) + s += QString(" [%1]").arg((*it)); + TD::msg(s); +#endif + } + + if(doAuth) { + event = EFeatures; + features = f; + step = HandleFeatures; + return true; + } + else + return loginComplete(); + } + else { + // ignore + } + } + else if(step == GetTLSProceed) { + // waiting for proceed to starttls + if(e.namespaceURI() == NS_TLS) { + if(e.tagName() == "proceed") { +#ifdef XMPP_TEST + TD::msg("Server wants us to proceed with ssl handshake"); +#endif + tls_started = true; + need = NStartTLS; + spare = resetStream(); + step = Start; + return false; + } + else if(e.tagName() == "failure") { + event = EError; + errorCode = ErrStartTLS; + return true; + } + else { + event = EError; + errorCode = ErrProtocol; + return true; + } + } + else { + // ignore + } + } + else if(step == GetSASLChallenge) { + // waiting for sasl challenge/success/fail + if(e.namespaceURI() == NS_SASL) { + if(e.tagName() == "challenge") { + QByteArray a = Base64::stringToArray(e.text()); +#ifdef XMPP_TEST + TD::msg(QString("SASL IN: [%1]").arg(printArray(a))); +#endif + sasl_step = a; + need = NSASLNext; + step = GetSASLNext; + return false; + } + else if(e.tagName() == "success") { + sasl_authed = true; + event = ESASLSuccess; + step = HandleSASLSuccess; + return true; + } + else if(e.tagName() == "failure") { + QDomElement t = firstChildElement(e); + if(t.isNull() || t.namespaceURI() != NS_SASL) + errCond = -1; + else + errCond = stringToSASLCond(t.tagName()); + + event = EError; + errorCode = ErrAuth; + return true; + } + else { + event = EError; + errorCode = ErrProtocol; + return true; + } + } + } + else if(step == GetBindResponse) { + if(e.namespaceURI() == NS_CLIENT && e.tagName() == "iq") { + QString type(e.attribute("type")); + QString id(e.attribute("id")); + + if(id == "bind_1" && (type == "result" || type == "error")) { + if(type == "result") { + QDomElement b = e.elementsByTagNameNS(NS_BIND, "bind").item(0).toElement(); + Jid j; + if(!b.isNull()) { + QDomElement je = e.elementsByTagName("jid").item(0).toElement(); + j = je.text(); + } + if(!j.isValid()) { + event = EError; + errorCode = ErrProtocol; + return true; + } + jid = j; + return loginComplete(); + } + else { + errCond = -1; + + QDomElement err = e.elementsByTagNameNS(NS_CLIENT, "error").item(0).toElement(); + if(!err.isNull()) { + // get error condition + QDomNodeList nl = err.childNodes(); + QDomElement t; + for(uint n = 0; n < nl.count(); ++n) { + QDomNode i = nl.item(n); + if(i.isElement()) { + t = i.toElement(); + break; + } + } + if(!t.isNull() && t.namespaceURI() == NS_STANZAS) { + QString cond = t.tagName(); + if(cond == "not-allowed") + errCond = BindNotAllowed; + else if(cond == "conflict") + errCond = BindConflict; + } + } + + event = EError; + errorCode = ErrBind; + return true; + } + } + else { + // ignore + } + } + else { + // ignore + } + } + else if(step == GetAuthGetResponse) { + // waiting for an iq + if(e.namespaceURI() == NS_CLIENT && e.tagName() == "iq") { + Jid from(e.attribute("from")); + QString type(e.attribute("type")); + QString id(e.attribute("id")); + + bool okfrom = (from.isEmpty() || from.compare(Jid(to))); + if(okfrom && id == "auth_1" && (type == "result" || type == "error")) { + if(type == "result") { + QDomElement q = e.elementsByTagNameNS("jabber:iq:auth", "query").item(0).toElement(); + if(q.isNull() || q.elementsByTagName("username").item(0).isNull() || q.elementsByTagName("resource").item(0).isNull()) { + event = EError; + errorCode = ErrProtocol; + return true; + } + bool plain_supported = !q.elementsByTagName("password").item(0).isNull(); + bool digest_supported = !q.elementsByTagName("digest").item(0).isNull(); + + if(!digest_supported && !plain_supported) { + event = EError; + errorCode = ErrProtocol; + return true; + } + + // plain text not allowed? + if(!digest_supported && !allowPlain) { + event = EError; + errorCode = ErrPlain; + return true; + } + + digest = digest_supported; + need = NPassword; + step = HandleAuthSet; + return false; + } + else { + errCond = getOldErrorCode(e); + + event = EError; + errorCode = ErrAuth; + return true; + } + } + else { + // ignore + } + } + else { + // ignore + } + } + else if(step == GetAuthSetResponse) { + // waiting for an iq + if(e.namespaceURI() == NS_CLIENT && e.tagName() == "iq") { + Jid from(e.attribute("from")); + QString type(e.attribute("type")); + QString id(e.attribute("id")); + + bool okfrom = (from.isEmpty() || from.compare(Jid(to))); + if(okfrom && id == "auth_2" && (type == "result" || type == "error")) { + if(type == "result") { + return loginComplete(); + } + else { + errCond = getOldErrorCode(e); + + event = EError; + errorCode = ErrAuth; + return true; + } + } + else { + // ignore + } + } + else { + // ignore + } + } + // server + else if(step == GetRequest) { + printf("get request: [%s], %s\n", e.namespaceURI().latin1(), e.tagName().latin1()); + if(e.namespaceURI() == NS_TLS && e.localName() == "starttls") { + // TODO: don't let this be done twice + + QDomElement e = doc.createElementNS(NS_TLS, "proceed"); + writeElement(e, TypeElement, false, true); + event = ESend; + step = HandleTLS; + return true; + } + if(e.namespaceURI() == NS_SASL) { + if(e.localName() == "auth") { + if(sasl_started) { + // TODO + printf("error\n"); + return false; + } + + sasl_started = true; + sasl_mech = e.attribute("mechanism"); + // TODO: if child text missing, don't pass it + sasl_step = Base64::stringToArray(e.text()); + need = NSASLFirst; + step = GetSASLNext; + return false; + } + else { + // TODO + printf("unknown sasl tag\n"); + return false; + } + } + if(e.namespaceURI() == NS_CLIENT && e.tagName() == "iq") { + QDomElement b = e.elementsByTagNameNS(NS_BIND, "bind").item(0).toElement(); + if(!b.isNull()) { + QDomElement res = b.elementsByTagName("resource").item(0).toElement(); + QString resource = res.text(); + + QDomElement r = doc.createElement("iq"); + r.setAttribute("type", "result"); + r.setAttribute("id", e.attribute("id")); + QDomElement bind = doc.createElementNS(NS_BIND, "bind"); + QDomElement jid = doc.createElement("jid"); + Jid j = user + '@' + host + '/' + resource; + jid.appendChild(doc.createTextNode(j.full())); + bind.appendChild(jid); + r.appendChild(bind); + + writeElement(r, TypeElement, false); + event = ESend; + // TODO + return true; + } + else { + // TODO + } + } + } + else if(step == GetSASLResponse) { + if(e.namespaceURI() == NS_SASL && e.localName() == "response") { + sasl_step = Base64::stringToArray(e.text()); + need = NSASLNext; + step = GetSASLNext; + return false; + } + } + + if(isReady()) { + if(!e.isNull() && isValidStanza(e)) { + stanzaToRecv = e; + event = EStanzaReady; + setIncomingAsExternal(); + return true; + } + } + + need = NNotify; + notify |= NRecv; + return false; +} diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/protocol.h b/kopete/protocols/jabber/libiris/iris/xmpp-core/protocol.h new file mode 100644 index 00000000..8511ce32 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/protocol.h @@ -0,0 +1,355 @@ +/* + * protocol.h - 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 + * + */ + +#ifndef PROTOCOL_H +#define PROTOCOL_H + +#include<qpair.h> +#include"xmlprotocol.h" +#include"xmpp.h" + +#define NS_ETHERX "http://etherx.jabber.org/streams" +#define NS_CLIENT "jabber:client" +#define NS_SERVER "jabber:server" +#define NS_DIALBACK "jabber:server:dialback" +#define NS_STREAMS "urn:ietf:params:xml:ns:xmpp-streams" +#define NS_TLS "urn:ietf:params:xml:ns:xmpp-tls" +#define NS_SASL "urn:ietf:params:xml:ns:xmpp-sasl" +#define NS_SESSION "urn:ietf:params:xml:ns:xmpp-session" +#define NS_STANZAS "urn:ietf:params:xml:ns:xmpp-stanzas" +#define NS_BIND "urn:ietf:params:xml:ns:xmpp-bind" +#define NS_XHTML_IM "http://jabber.org/protocol/xhtml-im" +#define NS_XHTML "http://www.w3.org/1999/xhtml" +#define NS_CHATSTATES "http://jabber.org/protocol/chatstates" + +namespace XMPP +{ + class Version + { + public: + Version(int maj=0, int min=0); + + int major, minor; + }; + + class StreamFeatures + { + public: + StreamFeatures(); + + bool tls_supported, sasl_supported, bind_supported; + bool tls_required; + QStringList sasl_mechs; + }; + + class BasicProtocol : public XmlProtocol + { + public: + // xmpp 1.0 error conditions + enum SASLCond { + Aborted, + IncorrectEncoding, + InvalidAuthzid, + InvalidMech, + MechTooWeak, + NotAuthorized, + TemporaryAuthFailure + }; + enum StreamCond { + BadFormat, + BadNamespacePrefix, + Conflict, + ConnectionTimeout, + HostGone, + HostUnknown, + ImproperAddressing, + InternalServerError, + InvalidFrom, + InvalidId, + InvalidNamespace, + InvalidXml, + StreamNotAuthorized, + PolicyViolation, + RemoteConnectionFailed, + ResourceConstraint, + RestrictedXml, + SeeOtherHost, + SystemShutdown, + UndefinedCondition, + UnsupportedEncoding, + UnsupportedStanzaType, + UnsupportedVersion, + XmlNotWellFormed + }; + enum BindCond { + BindBadRequest, + BindNotAllowed, + BindConflict + }; + + // extend the XmlProtocol enums + enum Need { + NSASLMechs = XmlProtocol::NCustom, // need SASL mechlist + NStartTLS, // need to switch on TLS layer + NSASLFirst, // need SASL first step + NSASLNext, // need SASL next step + NSASLLayer, // need to switch on SASL layer + NCustom = XmlProtocol::NCustom+10 + }; + enum Event { + EFeatures = XmlProtocol::ECustom, // breakpoint after features packet is received + ESASLSuccess, // breakpoint after successful sasl auth + EStanzaReady, // a stanza was received + EStanzaSent, // a stanza was sent + EReady, // stream is ready for stanza use + ECustom = XmlProtocol::ECustom+10 + }; + enum Error { + ErrProtocol = XmlProtocol::ErrCustom, // there was an error in the xmpp-core protocol exchange + ErrStream, // <stream:error>, see errCond, errText, and errAppSpec for details + ErrStartTLS, // server refused starttls + ErrAuth, // authorization error. errCond holds sasl condition (or numeric code for old-protocol) + ErrBind, // server refused resource bind + ErrCustom = XmlProtocol::ErrCustom+10 + }; + + BasicProtocol(); + ~BasicProtocol(); + + void reset(); + + // for outgoing xml + QDomDocument doc; + + // sasl-related + QString saslMech() const; + QByteArray saslStep() const; + void setSASLMechList(const QStringList &list); + void setSASLFirst(const QString &mech, const QByteArray &step); + void setSASLNext(const QByteArray &step); + void setSASLAuthed(); + + // send / recv + void sendStanza(const QDomElement &e); + void sendDirect(const QString &s); + void sendWhitespace(); + QDomElement recvStanza(); + + // shutdown + void shutdown(); + void shutdownWithError(int cond, const QString &otherHost=""); + + // <stream> information + QString to, from, id, lang; + Version version; + + // error output + int errCond; + QString errText; + QDomElement errAppSpec; + QString otherHost; + + QByteArray spare; // filled with unprocessed data on NStartTLS and NSASLLayer + + bool isReady() const; + + enum { TypeElement, TypeStanza, TypeDirect, TypePing }; + + protected: + static int stringToSASLCond(const QString &s); + static int stringToStreamCond(const QString &s); + static QString saslCondToString(int); + static QString streamCondToString(int); + + void send(const QDomElement &e, bool clip=false); + void sendStreamError(int cond, const QString &text="", const QDomElement &appSpec=QDomElement()); + void sendStreamError(const QString &text); // old-style + + bool errorAndClose(int cond, const QString &text="", const QDomElement &appSpec=QDomElement()); + bool error(int code); + void delayErrorAndClose(int cond, const QString &text="", const QDomElement &appSpec=QDomElement()); + void delayError(int code); + + // reimplemented + QDomElement docElement(); + void handleDocOpen(const Parser::Event &pe); + bool handleError(); + bool handleCloseFinished(); + bool doStep(const QDomElement &e); + void itemWritten(int id, int size); + + virtual QString defaultNamespace(); + virtual QStringList extraNamespaces(); // stringlist: prefix,uri,prefix,uri, [...] + virtual void handleStreamOpen(const Parser::Event &pe); + virtual bool doStep2(const QDomElement &e)=0; + + void setReady(bool b); + + QString sasl_mech; + QStringList sasl_mechlist; + QByteArray sasl_step; + bool sasl_authed; + + QDomElement stanzaToRecv; + + private: + struct SASLCondEntry + { + const char *str; + int cond; + }; + static SASLCondEntry saslCondTable[]; + + struct StreamCondEntry + { + const char *str; + int cond; + }; + static StreamCondEntry streamCondTable[]; + + struct SendItem + { + QDomElement stanzaToSend; + QString stringToSend; + bool doWhitespace; + }; + QValueList<SendItem> sendList; + + bool doShutdown, delayedError, closeError, ready; + int stanzasPending, stanzasWritten; + + void init(); + void extractStreamError(const QDomElement &e); + }; + + class CoreProtocol : public BasicProtocol + { + public: + enum { + NPassword = NCustom, // need password for old-mode + EDBVerify = ECustom, // breakpoint after db:verify request + ErrPlain = ErrCustom // server only supports plain, but allowPlain is false locally + }; + + CoreProtocol(); + ~CoreProtocol(); + + void reset(); + + void startClientOut(const Jid &jid, bool oldOnly, bool tlsActive, bool doAuth); + void startServerOut(const QString &to); + void startDialbackOut(const QString &to, const QString &from); + void startDialbackVerifyOut(const QString &to, const QString &from, const QString &id, const QString &key); + void startClientIn(const QString &id); + void startServerIn(const QString &id); + + void setLang(const QString &s); + void setAllowTLS(bool b); + void setAllowBind(bool b); + void setAllowPlain(bool b); // old-mode + + void setPassword(const QString &s); + void setFrom(const QString &s); + void setDialbackKey(const QString &s); + + // input + QString user, host; + + // status + bool old; + + StreamFeatures features; + + //static QString xmlToString(const QDomElement &e, bool clip=false); + + class DBItem + { + public: + enum { ResultRequest, ResultGrant, VerifyRequest, VerifyGrant, Validated }; + int type; + Jid to, from; + QString key, id; + bool ok; + }; + + private: + enum Step { + Start, + Done, + SendFeatures, + GetRequest, + HandleTLS, + GetSASLResponse, + IncHandleSASLSuccess, + GetFeatures, // read features packet + HandleFeatures, // act on features, by initiating tls, sasl, or bind + GetTLSProceed, // read <proceed/> tls response + GetSASLFirst, // perform sasl first step using provided data + GetSASLChallenge, // read server sasl challenge + GetSASLNext, // perform sasl next step using provided data + HandleSASLSuccess, // handle what must be done after reporting sasl success + GetBindResponse, // read bind response + HandleAuthGet, // send old-protocol auth-get + GetAuthGetResponse, // read auth-get response + HandleAuthSet, // send old-protocol auth-set + GetAuthSetResponse // read auth-set response + }; + + QValueList<DBItem> dbrequests, dbpending, dbvalidated; + + bool server, dialback, dialback_verify; + int step; + + bool digest; + bool tls_started, sasl_started; + + Jid jid; + bool oldOnly; + bool allowPlain; + bool doTLS, doAuth, doBinding; + QString password; + + QString dialback_id, dialback_key; + QString self_from; + + void init(); + static int getOldErrorCode(const QDomElement &e); + bool loginComplete(); + + bool isValidStanza(const QDomElement &e) const; + bool grabPendingItem(const Jid &to, const Jid &from, int type, DBItem *item); + bool normalStep(const QDomElement &e); + bool dialbackStep(const QDomElement &e); + + // reimplemented + bool stepAdvancesParser() const; + bool stepRequiresElement() const; + void stringSend(const QString &s); + void stringRecv(const QString &s); + QString defaultNamespace(); + QStringList extraNamespaces(); + void handleStreamOpen(const Parser::Event &pe); + bool doStep2(const QDomElement &e); + void elementSend(const QDomElement &e); + void elementRecv(const QDomElement &e); + }; +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/qcaprovider.h b/kopete/protocols/jabber/libiris/iris/xmpp-core/qcaprovider.h new file mode 100644 index 00000000..a7f1805b --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/qcaprovider.h @@ -0,0 +1,191 @@ +/* + * qcaprovider.h - QCA Plugin API + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef QCAPROVIDER_H +#define QCAPROVIDER_H + +#include<qglobal.h> +#include<qstring.h> +#include<qdatetime.h> +#include<qobject.h> +#include<qhostaddress.h> +#include"qca.h" + +#define QCA_PLUGIN_VERSION 1 + +class QCAProvider +{ +public: + QCAProvider() {} + virtual ~QCAProvider() {} + + virtual void init()=0; + virtual int qcaVersion() const=0; + virtual int capabilities() const=0; + virtual void *context(int cap)=0; +}; + +class QCA_HashContext +{ +public: + virtual ~QCA_HashContext() {} + + virtual QCA_HashContext *clone()=0; + virtual void reset()=0; + virtual void update(const char *in, unsigned int len)=0; + virtual void final(QByteArray *out)=0; +}; + +class QCA_CipherContext +{ +public: + virtual ~QCA_CipherContext() {} + + virtual QCA_CipherContext *clone()=0; + virtual int keySize()=0; + virtual int blockSize()=0; + virtual bool generateKey(char *out, int keysize=-1)=0; + virtual bool generateIV(char *out)=0; + + virtual bool setup(int dir, int mode, const char *key, int keysize, const char *iv, bool pad)=0; + virtual bool update(const char *in, unsigned int len)=0; + virtual bool final(QByteArray *out)=0; +}; + +class QCA_RSAKeyContext +{ +public: + virtual ~QCA_RSAKeyContext() {} + + virtual QCA_RSAKeyContext *clone() const=0; + virtual bool isNull() const=0; + virtual bool havePublic() const=0; + virtual bool havePrivate() const=0; + virtual bool createFromDER(const char *in, unsigned int len)=0; + virtual bool createFromPEM(const char *in, unsigned int len)=0; + virtual bool createFromNative(void *in)=0; + virtual bool generate(unsigned int bits)=0; + virtual bool toDER(QByteArray *out, bool publicOnly)=0; + virtual bool toPEM(QByteArray *out, bool publicOnly)=0; + + virtual bool encrypt(const QByteArray &in, QByteArray *out, bool oaep)=0; + virtual bool decrypt(const QByteArray &in, QByteArray *out, bool oaep)=0; +}; + +struct QCA_CertProperty +{ + QString var; + QString val; +}; + +class QCA_CertContext +{ +public: + virtual ~QCA_CertContext() {} + + virtual QCA_CertContext *clone() const=0; + virtual bool isNull() const=0; + virtual bool createFromDER(const char *in, unsigned int len)=0; + virtual bool createFromPEM(const char *in, unsigned int len)=0; + virtual bool toDER(QByteArray *out)=0; + virtual bool toPEM(QByteArray *out)=0; + + virtual QString serialNumber() const=0; + virtual QString subjectString() const=0; + virtual QString issuerString() const=0; + virtual QValueList<QCA_CertProperty> subject() const=0; + virtual QValueList<QCA_CertProperty> issuer() const=0; + virtual QDateTime notBefore() const=0; + virtual QDateTime notAfter() const=0; + virtual bool matchesAddress(const QString &realHost) const=0; +}; + +class QCA_TLSContext +{ +public: + enum Result { Success, Error, Continue }; + virtual ~QCA_TLSContext() {} + + virtual void reset()=0; + virtual bool startClient(const QPtrList<QCA_CertContext> &store, const QCA_CertContext &cert, const QCA_RSAKeyContext &key)=0; + virtual bool startServer(const QPtrList<QCA_CertContext> &store, const QCA_CertContext &cert, const QCA_RSAKeyContext &key)=0; + + virtual int handshake(const QByteArray &in, QByteArray *out)=0; + virtual int shutdown(const QByteArray &in, QByteArray *out)=0; + virtual bool encode(const QByteArray &plain, QByteArray *to_net, int *encoded)=0; + virtual bool decode(const QByteArray &from_net, QByteArray *plain, QByteArray *to_net)=0; + virtual bool eof() const=0; + virtual QByteArray unprocessed()=0; + + virtual QCA_CertContext *peerCertificate() const=0; + virtual int validityResult() const=0; +}; + +struct QCA_SASLHostPort +{ + QHostAddress addr; + Q_UINT16 port; +}; + +struct QCA_SASLNeedParams +{ + bool user, authzid, pass, realm; +}; + +class QCA_SASLContext +{ +public: + enum Result { Success, Error, NeedParams, AuthCheck, Continue }; + virtual ~QCA_SASLContext() {} + + // common + virtual void reset()=0; + virtual void setCoreProps(const QString &service, const QString &host, QCA_SASLHostPort *local, QCA_SASLHostPort *remote)=0; + virtual void setSecurityProps(bool noPlain, bool noActive, bool noDict, bool noAnon, bool reqForward, bool reqCreds, bool reqMutual, int ssfMin, int ssfMax, const QString &_ext_authid, int _ext_ssf)=0; + virtual int security() const=0; + virtual int errorCond() const=0; + + // init / first step + virtual bool clientStart(const QStringList &mechlist)=0; + virtual int clientFirstStep(bool allowClientSendFirst)=0; + virtual bool serverStart(const QString &realm, QStringList *mechlist, const QString &name)=0; + virtual int serverFirstStep(const QString &mech, const QByteArray *in)=0; + + // get / set params + virtual QCA_SASLNeedParams clientParamsNeeded() const=0; + virtual void setClientParams(const QString *user, const QString *authzid, const QString *pass, const QString *realm)=0; + virtual QString username() const=0; + virtual QString authzid() const=0; + + // continue steps + virtual int nextStep(const QByteArray &in)=0; + virtual int tryAgain()=0; + + // results + virtual QString mech() const=0; + virtual const QByteArray *clientInit() const=0; + virtual QByteArray result() const=0; + + // security layer + virtual bool encode(const QByteArray &in, QByteArray *out)=0; + virtual bool decode(const QByteArray &in, QByteArray *out)=0; +}; + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/securestream.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/securestream.cpp new file mode 100644 index 00000000..6bd902d9 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/securestream.cpp @@ -0,0 +1,589 @@ +/* + * securestream.cpp - combines a ByteStream with TLS and SASL + * 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 + * + */ + +/* + Note: SecureStream depends on the underlying security layers to signal + plain-to-encrypted results immediately (as opposed to waiting for the + event loop) so that the user cannot add/remove security layers during + this conversion moment. QCA::TLS and QCA::SASL behave as expected, + but future layers might not. +*/ + +#include"securestream.h" + +#include<qguardedptr.h> +#include<qvaluelist.h> +#include<qtimer.h> + +#ifdef USE_TLSHANDLER +#include"xmpp.h" +#endif + +//---------------------------------------------------------------------------- +// LayerTracker +//---------------------------------------------------------------------------- +class LayerTracker +{ +public: + struct Item + { + int plain; + int encoded; + }; + + LayerTracker(); + + void reset(); + void addPlain(int plain); + void specifyEncoded(int encoded, int plain); + int finished(int encoded); + + int p; + QValueList<Item> list; +}; + +LayerTracker::LayerTracker() +{ + p = 0; +} + +void LayerTracker::reset() +{ + p = 0; + list.clear(); +} + +void LayerTracker::addPlain(int plain) +{ + p += plain; +} + +void LayerTracker::specifyEncoded(int encoded, int plain) +{ + // can't specify more bytes than we have + if(plain > p) + plain = p; + p -= plain; + Item i; + i.plain = plain; + i.encoded = encoded; + list += i; +} + +int LayerTracker::finished(int encoded) +{ + int plain = 0; + for(QValueList<Item>::Iterator it = list.begin(); it != list.end();) { + Item &i = *it; + + // not enough? + if(encoded < i.encoded) { + i.encoded -= encoded; + break; + } + + encoded -= i.encoded; + plain += i.plain; + it = list.remove(it); + } + return plain; +} + +//---------------------------------------------------------------------------- +// SecureStream +//---------------------------------------------------------------------------- +class SecureLayer : public QObject +{ + Q_OBJECT +public: + enum { TLS, SASL, TLSH }; + int type; + union { + QCA::TLS *tls; + QCA::SASL *sasl; +#ifdef USE_TLSHANDLER + XMPP::TLSHandler *tlsHandler; +#endif + } p; + LayerTracker layer; + bool tls_done; + int prebytes; + + SecureLayer(QCA::TLS *t) + { + type = TLS; + p.tls = t; + init(); + connect(p.tls, SIGNAL(handshaken()), SLOT(tls_handshaken())); + connect(p.tls, SIGNAL(readyRead()), SLOT(tls_readyRead())); + connect(p.tls, SIGNAL(readyReadOutgoing(int)), SLOT(tls_readyReadOutgoing(int))); + connect(p.tls, SIGNAL(closed()), SLOT(tls_closed())); + connect(p.tls, SIGNAL(error(int)), SLOT(tls_error(int))); + } + + SecureLayer(QCA::SASL *s) + { + type = SASL; + p.sasl = s; + init(); + connect(p.sasl, SIGNAL(readyRead()), SLOT(sasl_readyRead())); + connect(p.sasl, SIGNAL(readyReadOutgoing(int)), SLOT(sasl_readyReadOutgoing(int))); + connect(p.sasl, SIGNAL(error(int)), SLOT(sasl_error(int))); + } + +#ifdef USE_TLSHANDLER + SecureLayer(XMPP::TLSHandler *t) + { + type = TLSH; + p.tlsHandler = t; + init(); + connect(p.tlsHandler, SIGNAL(success()), SLOT(tlsHandler_success())); + connect(p.tlsHandler, SIGNAL(fail()), SLOT(tlsHandler_fail())); + connect(p.tlsHandler, SIGNAL(closed()), SLOT(tlsHandler_closed())); + connect(p.tlsHandler, SIGNAL(readyRead(const QByteArray &)), SLOT(tlsHandler_readyRead(const QByteArray &))); + connect(p.tlsHandler, SIGNAL(readyReadOutgoing(const QByteArray &, int)), SLOT(tlsHandler_readyReadOutgoing(const QByteArray &, int))); + } +#endif + + void init() + { + tls_done = false; + prebytes = 0; + } + + void write(const QByteArray &a) + { + layer.addPlain(a.size()); + switch(type) { + case TLS: { p.tls->write(a); break; } + case SASL: { p.sasl->write(a); break; } +#ifdef USE_TLSHANDLER + case TLSH: { p.tlsHandler->write(a); break; } +#endif + } + } + + void writeIncoming(const QByteArray &a) + { + switch(type) { + case TLS: { p.tls->writeIncoming(a); break; } + case SASL: { p.sasl->writeIncoming(a); break; } +#ifdef USE_TLSHANDLER + case TLSH: { p.tlsHandler->writeIncoming(a); break; } +#endif + } + } + + int finished(int plain) + { + int written = 0; + + // deal with prebytes (bytes sent prior to this security layer) + if(prebytes > 0) { + if(prebytes >= plain) { + written += plain; + prebytes -= plain; + plain = 0; + } + else { + written += prebytes; + plain -= prebytes; + prebytes = 0; + } + } + + // put remainder into the layer tracker + if(type == SASL || tls_done) + written += layer.finished(plain); + + return written; + } + +signals: + void tlsHandshaken(); + void tlsClosed(const QByteArray &); + void readyRead(const QByteArray &); + void needWrite(const QByteArray &); + void error(int); + +private slots: + void tls_handshaken() + { + tls_done = true; + tlsHandshaken(); + } + + void tls_readyRead() + { + QByteArray a = p.tls->read(); + readyRead(a); + } + + void tls_readyReadOutgoing(int plainBytes) + { + QByteArray a = p.tls->readOutgoing(); + if(tls_done) + layer.specifyEncoded(a.size(), plainBytes); + needWrite(a); + } + + void tls_closed() + { + QByteArray a = p.tls->readUnprocessed(); + tlsClosed(a); + } + + void tls_error(int x) + { + error(x); + } + + void sasl_readyRead() + { + QByteArray a = p.sasl->read(); + readyRead(a); + } + + void sasl_readyReadOutgoing(int plainBytes) + { + QByteArray a = p.sasl->readOutgoing(); + layer.specifyEncoded(a.size(), plainBytes); + needWrite(a); + } + + void sasl_error(int x) + { + error(x); + } + +#ifdef USE_TLSHANDLER + void tlsHandler_success() + { + tls_done = true; + tlsHandshaken(); + } + + void tlsHandler_fail() + { + error(0); + } + + void tlsHandler_closed() + { + tlsClosed(QByteArray()); + } + + void tlsHandler_readyRead(const QByteArray &a) + { + readyRead(a); + } + + void tlsHandler_readyReadOutgoing(const QByteArray &a, int plainBytes) + { + if(tls_done) + layer.specifyEncoded(a.size(), plainBytes); + needWrite(a); + } +#endif +}; + +#include"securestream.moc" + +class SecureStream::Private +{ +public: + ByteStream *bs; + QPtrList<SecureLayer> layers; + int pending; + int errorCode; + bool active; + bool topInProgress; + + bool haveTLS() const + { + QPtrListIterator<SecureLayer> it(layers); + for(SecureLayer *s; (s = it.current()); ++it) { + if(s->type == SecureLayer::TLS +#ifdef USE_TLSHANDLER + || s->type == SecureLayer::TLSH +#endif + ) { + return true; + } + } + return false; + } + + bool haveSASL() const + { + QPtrListIterator<SecureLayer> it(layers); + for(SecureLayer *s; (s = it.current()); ++it) { + if(s->type == SecureLayer::SASL) + return true; + } + return false; + } +}; + +SecureStream::SecureStream(ByteStream *s) +:ByteStream(0) +{ + d = new Private; + + d->bs = s; + connect(d->bs, SIGNAL(readyRead()), SLOT(bs_readyRead())); + connect(d->bs, SIGNAL(bytesWritten(int)), SLOT(bs_bytesWritten(int))); + + d->layers.setAutoDelete(true); + d->pending = 0; + d->active = true; + d->topInProgress = false; +} + +SecureStream::~SecureStream() +{ + delete d; +} + +void SecureStream::linkLayer(QObject *s) +{ + connect(s, SIGNAL(tlsHandshaken()), SLOT(layer_tlsHandshaken())); + connect(s, SIGNAL(tlsClosed(const QByteArray &)), SLOT(layer_tlsClosed(const QByteArray &))); + connect(s, SIGNAL(readyRead(const QByteArray &)), SLOT(layer_readyRead(const QByteArray &))); + connect(s, SIGNAL(needWrite(const QByteArray &)), SLOT(layer_needWrite(const QByteArray &))); + connect(s, SIGNAL(error(int)), SLOT(layer_error(int))); +} + +int SecureStream::calcPrebytes() const +{ + int x = 0; + QPtrListIterator<SecureLayer> it(d->layers); + for(SecureLayer *s; (s = it.current()); ++it) + x += s->prebytes; + return (d->pending - x); +} + +void SecureStream::startTLSClient(QCA::TLS *t, const QByteArray &spare) +{ + if(!d->active || d->topInProgress || d->haveTLS()) + return; + + SecureLayer *s = new SecureLayer(t); + s->prebytes = calcPrebytes(); + linkLayer(s); + d->layers.append(s); + d->topInProgress = true; + + insertData(spare); +} + +void SecureStream::startTLSServer(QCA::TLS *t, const QByteArray &spare) +{ + if(!d->active || d->topInProgress || d->haveTLS()) + return; + + SecureLayer *s = new SecureLayer(t); + s->prebytes = calcPrebytes(); + linkLayer(s); + d->layers.append(s); + d->topInProgress = true; + + insertData(spare); +} + +void SecureStream::setLayerSASL(QCA::SASL *sasl, const QByteArray &spare) +{ + if(!d->active || d->topInProgress || d->haveSASL()) + return; + + SecureLayer *s = new SecureLayer(sasl); + s->prebytes = calcPrebytes(); + linkLayer(s); + d->layers.append(s); + + insertData(spare); +} + +#ifdef USE_TLSHANDLER +void SecureStream::startTLSClient(XMPP::TLSHandler *t, const QString &server, const QByteArray &spare) +{ + if(!d->active || d->topInProgress || d->haveTLS()) + return; + + SecureLayer *s = new SecureLayer(t); + s->prebytes = calcPrebytes(); + linkLayer(s); + d->layers.append(s); + d->topInProgress = true; + + // unlike QCA::TLS, XMPP::TLSHandler has no return value + s->p.tlsHandler->startClient(server); + + insertData(spare); +} +#endif + +void SecureStream::closeTLS() +{ + SecureLayer *s = d->layers.getLast(); + if(s) { + if(s->type == SecureLayer::TLS) + s->p.tls->close(); + } +} + +int SecureStream::errorCode() const +{ + return d->errorCode; +} + +bool SecureStream::isOpen() const +{ + return d->active; +} + +void SecureStream::write(const QByteArray &a) +{ + if(!isOpen()) + return; + + d->pending += a.size(); + + // send to the last layer + SecureLayer *s = d->layers.getLast(); + if(s) + s->write(a); + else + writeRawData(a); +} + +int SecureStream::bytesToWrite() const +{ + return d->pending; +} + +void SecureStream::bs_readyRead() +{ + QByteArray a = d->bs->read(); + + // send to the first layer + SecureLayer *s = d->layers.getFirst(); + if(s) + s->writeIncoming(a); + else + incomingData(a); +} + +void SecureStream::bs_bytesWritten(int bytes) +{ + QPtrListIterator<SecureLayer> it(d->layers); + for(SecureLayer *s; (s = it.current()); ++it) + bytes = s->finished(bytes); + + if(bytes > 0) { + d->pending -= bytes; + bytesWritten(bytes); + } +} + +void SecureStream::layer_tlsHandshaken() +{ + d->topInProgress = false; + tlsHandshaken(); +} + +void SecureStream::layer_tlsClosed(const QByteArray &) +{ + d->active = false; + d->layers.clear(); + tlsClosed(); +} + +void SecureStream::layer_readyRead(const QByteArray &a) +{ + SecureLayer *s = (SecureLayer *)sender(); + QPtrListIterator<SecureLayer> it(d->layers); + while(it.current() != s) + ++it; + + // pass upwards + ++it; + s = it.current(); + if(s) + s->writeIncoming(a); + else + incomingData(a); +} + +void SecureStream::layer_needWrite(const QByteArray &a) +{ + SecureLayer *s = (SecureLayer *)sender(); + QPtrListIterator<SecureLayer> it(d->layers); + while(it.current() != s) + ++it; + + // pass downwards + --it; + s = it.current(); + if(s) + s->write(a); + else + writeRawData(a); +} + +void SecureStream::layer_error(int x) +{ + SecureLayer *s = (SecureLayer *)sender(); + int type = s->type; + d->errorCode = x; + d->active = false; + d->layers.clear(); + if(type == SecureLayer::TLS) + error(ErrTLS); + else if(type == SecureLayer::SASL) + error(ErrSASL); +#ifdef USE_TLSHANDLER + else if(type == SecureLayer::TLSH) + error(ErrTLS); +#endif +} + +void SecureStream::insertData(const QByteArray &a) +{ + if(!a.isEmpty()) { + SecureLayer *s = d->layers.getLast(); + if(s) + s->writeIncoming(a); + else + incomingData(a); + } +} + +void SecureStream::writeRawData(const QByteArray &a) +{ + d->bs->write(a); +} + +void SecureStream::incomingData(const QByteArray &a) +{ + appendRead(a); + if(bytesAvailable()) + readyRead(); +} diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/securestream.h b/kopete/protocols/jabber/libiris/iris/xmpp-core/securestream.h new file mode 100644 index 00000000..c5787a2b --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/securestream.h @@ -0,0 +1,84 @@ +/* + * securestream.h - combines a ByteStream with TLS and SASL + * 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 + * + */ + +#ifndef SECURESTREAM_H +#define SECURESTREAM_H + +#include<qca.h> +#include"bytestream.h" + +#define USE_TLSHANDLER + +#ifdef USE_TLSHANDLER +namespace XMPP +{ + class TLSHandler; +} +#endif + +class SecureStream : public ByteStream +{ + Q_OBJECT +public: + enum Error { ErrTLS = ErrCustom, ErrSASL }; + SecureStream(ByteStream *s); + ~SecureStream(); + + void startTLSClient(QCA::TLS *t, const QByteArray &spare=QByteArray()); + void startTLSServer(QCA::TLS *t, const QByteArray &spare=QByteArray()); + void setLayerSASL(QCA::SASL *s, const QByteArray &spare=QByteArray()); +#ifdef USE_TLSHANDLER + void startTLSClient(XMPP::TLSHandler *t, const QString &server, const QByteArray &spare=QByteArray()); +#endif + + void closeTLS(); + int errorCode() const; + + // reimplemented + bool isOpen() const; + void write(const QByteArray &); + int bytesToWrite() const; + +signals: + void tlsHandshaken(); + void tlsClosed(); + +private slots: + void bs_readyRead(); + void bs_bytesWritten(int); + + void layer_tlsHandshaken(); + void layer_tlsClosed(const QByteArray &); + void layer_readyRead(const QByteArray &); + void layer_needWrite(const QByteArray &); + void layer_error(int); + +private: + void linkLayer(QObject *); + int calcPrebytes() const; + void insertData(const QByteArray &a); + void writeRawData(const QByteArray &a); + void incomingData(const QByteArray &a); + + class Private; + Private *d; +}; + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/simplesasl.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/simplesasl.cpp new file mode 100644 index 00000000..54c4f405 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/simplesasl.cpp @@ -0,0 +1,459 @@ +/* + * simplesasl.cpp - Simple SASL implementation + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"simplesasl.h" + +#include<qhostaddress.h> +#include<qstringlist.h> +#include<qptrlist.h> +#include<qvaluelist.h> +#include<qca.h> +#include<stdlib.h> +#include"base64.h" + +namespace XMPP +{ + +struct Prop +{ + QCString var, val; +}; + +class PropList : public QValueList<Prop> +{ +public: + PropList() : QValueList<Prop>() + { + } + + void set(const QCString &var, const QCString &val) + { + Prop p; + p.var = var; + p.val = val; + append(p); + } + + QCString get(const QCString &var) + { + for(ConstIterator it = begin(); it != end(); ++it) { + if((*it).var == var) + return (*it).val; + } + return QCString(); + } + + QCString toString() const + { + QCString str; + bool first = true; + for(ConstIterator it = begin(); it != end(); ++it) { + if(!first) + str += ','; + str += (*it).var + "=\"" + (*it).val + '\"'; + first = false; + } + return str; + } + + bool fromString(const QCString &str) + { + PropList list; + int at = 0; + while(1) { + int n = str.find('=', at); + if(n == -1) + break; + QCString var, val; + var = str.mid(at, n-at); + at = n + 1; + if(str[at] == '\"') { + ++at; + n = str.find('\"', at); + if(n == -1) + break; + val = str.mid(at, n-at); + at = n + 1; + } + else { + n = str.find(',', at); + if(n != -1) { + val = str.mid(at, n-at); + at = n; + } + else { + val = str.mid(at); + at = str.length()-1; + } + } + Prop prop; + prop.var = var; + prop.val = val; + list.append(prop); + + if(str[at] != ',') + break; + ++at; + } + + // integrity check + if(list.varCount("nonce") != 1) + return false; + if(list.varCount("algorithm") != 1) + return false; + *this = list; + return true; + } + + int varCount(const QCString &var) + { + int n = 0; + for(ConstIterator it = begin(); it != end(); ++it) { + if((*it).var == var) + ++n; + } + return n; + } + + QStringList getValues(const QCString &var) + { + QStringList list; + for(ConstIterator it = begin(); it != end(); ++it) { + if((*it).var == var) + list += (*it).val; + } + return list; + } +}; + +class SimpleSASLContext : public QCA_SASLContext +{ +public: + // core props + QString service, host; + + // state + int step; + QByteArray in_buf; + QString out_mech; + QByteArray out_buf; + bool capable; + int err; + + QCA_SASLNeedParams need; + QCA_SASLNeedParams have; + QString user, authz, pass, realm; + + SimpleSASLContext() + { + reset(); + } + + ~SimpleSASLContext() + { + reset(); + } + + void reset() + { + resetState(); + resetParams(); + } + + void resetState() + { + out_mech = QString(); + out_buf.resize(0); + err = -1; + } + + void resetParams() + { + capable = true; + need.user = false; + need.authzid = false; + need.pass = false; + need.realm = false; + have.user = false; + have.authzid = false; + have.pass = false; + have.realm = false; + user = QString(); + authz = QString(); + pass = QString(); + realm = QString(); + } + + void setCoreProps(const QString &_service, const QString &_host, QCA_SASLHostPort *, QCA_SASLHostPort *) + { + service = _service; + host = _host; + } + + void setSecurityProps(bool, bool, bool, bool, bool reqForward, bool reqCreds, bool reqMutual, int ssfMin, int, const QString &, int) + { + if(reqForward || reqCreds || reqMutual || ssfMin > 0) + capable = false; + else + capable = true; + } + + int security() const + { + return 0; + } + + int errorCond() const + { + return err; + } + + bool clientStart(const QStringList &mechlist) + { + bool haveMech = false; + for(QStringList::ConstIterator it = mechlist.begin(); it != mechlist.end(); ++it) { + if((*it) == "DIGEST-MD5") { + haveMech = true; + break; + } + } + if(!capable || !haveMech) { + err = QCA::SASL::NoMech; + return false; + } + + resetState(); + step = 0; + return true; + } + + int clientFirstStep(bool) + { + return clientTryAgain(); + } + + bool serverStart(const QString &, QStringList *, const QString &) + { + return false; + } + + int serverFirstStep(const QString &, const QByteArray *) + { + return Error; + } + + QCA_SASLNeedParams clientParamsNeeded() const + { + return need; + } + + void setClientParams(const QString *_user, const QString *_authzid, const QString *_pass, const QString *_realm) + { + if(_user) { + user = *_user; + need.user = false; + have.user = true; + } + if(_authzid) { + authz = *_authzid; + need.authzid = false; + have.authzid = true; + } + if(_pass) { + pass = *_pass; + need.pass = false; + have.pass = true; + } + if(_realm) { + realm = *_realm; + need.realm = false; + have.realm = true; + } + } + + QString username() const + { + return QString(); + } + + QString authzid() const + { + return QString(); + } + + int nextStep(const QByteArray &in) + { + in_buf = in.copy(); + return tryAgain(); + } + + int tryAgain() + { + return clientTryAgain(); + } + + QString mech() const + { + return out_mech; + } + + const QByteArray *clientInit() const + { + return 0; + } + + QByteArray result() const + { + return out_buf; + } + + int clientTryAgain() + { + if(step == 0) { + out_mech = "DIGEST-MD5"; + ++step; + return Continue; + } + else if(step == 1) { + // if we still need params, then the app has failed us! + if(need.user || need.authzid || need.pass || need.realm) { + err = -1; + return Error; + } + + // see if some params are needed + if(!have.user) + need.user = true; + if(!have.authzid) + need.authzid = true; + if(!have.pass) + need.pass = true; + if(need.user || need.authzid || need.pass) + return NeedParams; + + // get props + QCString cs(in_buf.data(), in_buf.size()+1); + PropList in; + if(!in.fromString(cs)) { + err = QCA::SASL::BadProto; + return Error; + } + + // make a cnonce + QByteArray a(32); + for(int n = 0; n < (int)a.size(); ++n) + a[n] = (char)(256.0*rand()/(RAND_MAX+1.0)); + QCString cnonce = Base64::arrayToString(a).latin1(); + + // make other variables + realm = host; + QCString nonce = in.get("nonce"); + QCString nc = "00000001"; + QCString uri = service.utf8() + '/' + host.utf8(); + QCString qop = "auth"; + + // build 'response' + QCString X = user.utf8() + ':' + realm.utf8() + ':' + pass.utf8(); + QByteArray Y = QCA::MD5::hash(X); + QCString tmp = QCString(":") + nonce + ':' + cnonce + ':' + authz.utf8(); + QByteArray A1(Y.size() + tmp.length()); + memcpy(A1.data(), Y.data(), Y.size()); + memcpy(A1.data() + Y.size(), tmp.data(), tmp.length()); + QCString A2 = "AUTHENTICATE:" + uri; + QCString HA1 = QCA::MD5::hashToString(A1).latin1(); + QCString HA2 = QCA::MD5::hashToString(A2).latin1(); + QCString KD = HA1 + ':' + nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + HA2; + QCString Z = QCA::MD5::hashToString(KD).latin1(); + + // build output + PropList out; + out.set("username", user.utf8()); + out.set("realm", host.utf8()); + out.set("nonce", nonce); + out.set("cnonce", cnonce); + out.set("nc", nc); + out.set("serv-type", service.utf8()); + out.set("host", host.utf8()); + out.set("digest-uri", uri); + out.set("qop", qop); + out.set("response", Z); + out.set("charset", "utf-8"); + out.set("authzid", authz.utf8()); + QCString s = out.toString(); + + // done + out_buf.resize(s.length()); + memcpy(out_buf.data(), s.data(), out_buf.size()); + ++step; + return Continue; + } + else { + out_buf.resize(0); + return Success; + } + } + + bool encode(const QByteArray &a, QByteArray *b) + { + *b = a.copy(); + return true; + } + + bool decode(const QByteArray &a, QByteArray *b) + { + *b = a.copy(); + return true; + } +}; + +class QCASimpleSASL : public QCAProvider +{ +public: + QCASimpleSASL() {} + ~QCASimpleSASL() {} + + void init() + { + } + + int qcaVersion() const + { + return QCA_PLUGIN_VERSION; + } + + int capabilities() const + { + return QCA::CAP_SASL; + } + + void *context(int cap) + { + if(cap == QCA::CAP_SASL) + return new SimpleSASLContext; + return 0; + } +}; + +QCAProvider *createProviderSimpleSASL() +{ + return (new QCASimpleSASL); +} + +} diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/simplesasl.h b/kopete/protocols/jabber/libiris/iris/xmpp-core/simplesasl.h new file mode 100644 index 00000000..12a08c0e --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/simplesasl.h @@ -0,0 +1,31 @@ +/* + * simplesasl.h - Simple SASL implementation + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef SIMPLESASL_H +#define SIMPLESASL_H + +#include"qcaprovider.h" + +namespace XMPP +{ + QCAProvider *createProviderSimpleSASL(); +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/stream.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/stream.cpp new file mode 100644 index 00000000..bfcc218c --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/stream.cpp @@ -0,0 +1,1762 @@ +/* + * stream.cpp - handles a client stream + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + Notes: + - For Non-SASL auth (JEP-0078), username and resource fields are required. + + TODO: + - sasl needParams is totally jacked? PLAIN requires authzid, etc + - server error handling + - reply with protocol errors if the client send something wrong + - don't necessarily disconnect on protocol error. prepare for more. + - server function + - deal with stream 'to' attribute dynamically + - flag tls/sasl/binding support dynamically (have the ability to specify extra stream:features) + - inform the caller about the user authentication information + - sasl security settings + - resource-binding interaction + - timeouts + - allow exchanges of non-standard stanzas + - send </stream:stream> even if we close prematurely? + - ensure ClientStream and child classes are fully deletable after signals + - xml:lang in root (<stream>) element + - sasl external + - sasl anonymous +*/ + +#include"xmpp.h" + +#include<qtextstream.h> +#include<qguardedptr.h> +#include<qtimer.h> +#include<qca.h> +#include<stdlib.h> +#include"bytestream.h" +#include"base64.h" +#include"hash.h" +#include"simplesasl.h" +#include"securestream.h" +#include"protocol.h" + +#ifdef XMPP_TEST +#include"td.h" +#endif + +//#define XMPP_DEBUG + +using namespace XMPP; + +static Debug *debug_ptr = 0; +void XMPP::setDebug(Debug *p) +{ + debug_ptr = p; +} + +static QByteArray randomArray(int size) +{ + QByteArray a(size); + for(int n = 0; n < size; ++n) + a[n] = (char)(256.0*rand()/(RAND_MAX+1.0)); + return a; +} + +static QString genId() +{ + // need SHA1 here + if(!QCA::isSupported(QCA::CAP_SHA1)) + QCA::insertProvider(createProviderHash()); + + return QCA::SHA1::hashToString(randomArray(128)); +} + +//---------------------------------------------------------------------------- +// Stanza +//---------------------------------------------------------------------------- +Stanza::Error::Error(int _type, int _condition, const QString &_text, const QDomElement &_appSpec) +{ + type = _type; + condition = _condition; + text = _text; + appSpec = _appSpec; +} + +class Stanza::Private +{ +public: + struct ErrorTypeEntry + { + const char *str; + int type; + }; + static ErrorTypeEntry errorTypeTable[]; + + struct ErrorCondEntry + { + const char *str; + int cond; + }; + static ErrorCondEntry errorCondTable[]; + + static int stringToKind(const QString &s) + { + if(s == "message") + return Message; + else if(s == "presence") + return Presence; + else if(s == "iq") + return IQ; + else + return -1; + } + + static QString kindToString(Kind k) + { + if(k == Message) + return "message"; + else if(k == Presence) + return "presence"; + else + return "iq"; + } + + static int stringToErrorType(const QString &s) + { + for(int n = 0; errorTypeTable[n].str; ++n) { + if(s == errorTypeTable[n].str) + return errorTypeTable[n].type; + } + return -1; + } + + static QString errorTypeToString(int x) + { + for(int n = 0; errorTypeTable[n].str; ++n) { + if(x == errorTypeTable[n].type) + return errorTypeTable[n].str; + } + return QString(); + } + + static int stringToErrorCond(const QString &s) + { + for(int n = 0; errorCondTable[n].str; ++n) { + if(s == errorCondTable[n].str) + return errorCondTable[n].cond; + } + return -1; + } + + static QString errorCondToString(int x) + { + for(int n = 0; errorCondTable[n].str; ++n) { + if(x == errorCondTable[n].cond) + return errorCondTable[n].str; + } + return QString(); + } + + Stream *s; + QDomElement e; +}; + +Stanza::Private::ErrorTypeEntry Stanza::Private::errorTypeTable[] = +{ + { "cancel", Cancel }, + { "continue", Continue }, + { "modify", Modify }, + { "auth", Auth }, + { "wait", Wait }, + { 0, 0 }, +}; + +Stanza::Private::ErrorCondEntry Stanza::Private::errorCondTable[] = +{ + { "bad-request", BadRequest }, + { "conflict", Conflict }, + { "feature-not-implemented", FeatureNotImplemented }, + { "forbidden", Forbidden }, + { "internal-server-error", InternalServerError }, + { "item-not-found", ItemNotFound }, + { "jid-malformed", JidMalformed }, + { "not-allowed", NotAllowed }, + { "payment-required", PaymentRequired }, + { "recipient-unavailable", RecipientUnavailable }, + { "registration-required", RegistrationRequired }, + { "remote-server-not-found", ServerNotFound }, + { "remote-server-timeout", ServerTimeout }, + { "resource-constraint", ResourceConstraint }, + { "service-unavailable", ServiceUnavailable }, + { "subscription-required", SubscriptionRequired }, + { "undefined-condition", UndefinedCondition }, + { "unexpected-request", UnexpectedRequest }, + { 0, 0 }, +}; + +Stanza::Stanza() +{ + d = 0; +} + +Stanza::Stanza(Stream *s, Kind k, const Jid &to, const QString &type, const QString &id) +{ + d = new Private; + + Kind kind; + if(k == Message || k == Presence || k == IQ) + kind = k; + else + kind = Message; + + d->s = s; + d->e = d->s->doc().createElementNS(s->baseNS(), Private::kindToString(kind)); + if(to.isValid()) + setTo(to); + if(!type.isEmpty()) + setType(type); + if(!id.isEmpty()) + setId(id); +} + +Stanza::Stanza(Stream *s, const QDomElement &e) +{ + d = 0; + if(e.namespaceURI() != s->baseNS()) + return; + int x = Private::stringToKind(e.tagName()); + if(x == -1) + return; + d = new Private; + d->s = s; + d->e = e; +} + +Stanza::Stanza(const Stanza &from) +{ + d = 0; + *this = from; +} + +Stanza & Stanza::operator=(const Stanza &from) +{ + delete d; + d = 0; + if(from.d) + d = new Private(*from.d); + return *this; +} + +Stanza::~Stanza() +{ + delete d; +} + +bool Stanza::isNull() const +{ + return (d ? false: true); +} + +QDomElement Stanza::element() const +{ + return d->e; +} + +QString Stanza::toString() const +{ + return Stream::xmlToString(d->e); +} + +QDomDocument & Stanza::doc() const +{ + return d->s->doc(); +} + +QString Stanza::baseNS() const +{ + return d->s->baseNS(); +} + +QString Stanza::xhtmlImNS() const +{ + return d->s->xhtmlImNS(); +} + +QString Stanza::xhtmlNS() const +{ + return d->s->xhtmlNS(); +} + +QDomElement Stanza::createElement(const QString &ns, const QString &tagName) +{ + return d->s->doc().createElementNS(ns, tagName); +} + +QDomElement Stanza::createTextElement(const QString &ns, const QString &tagName, const QString &text) +{ + QDomElement e = d->s->doc().createElementNS(ns, tagName); + e.appendChild(d->s->doc().createTextNode(text)); + return e; +} + +QDomElement Stanza::createXHTMLElement(const QString &xHTML) +{ + QDomDocument doc; + + doc.setContent(xHTML, true); + QDomElement root = doc.documentElement(); + //QDomElement e; + return (root); +} + +void Stanza::appendChild(const QDomElement &e) +{ + d->e.appendChild(e); +} + +Stanza::Kind Stanza::kind() const +{ + return (Kind)Private::stringToKind(d->e.tagName()); +} + +void Stanza::setKind(Kind k) +{ + d->e.setTagName(Private::kindToString(k)); +} + +Jid Stanza::to() const +{ + return Jid(d->e.attribute("to")); +} + +Jid Stanza::from() const +{ + return Jid(d->e.attribute("from")); +} + +QString Stanza::id() const +{ + return d->e.attribute("id"); +} + +QString Stanza::type() const +{ + return d->e.attribute("type"); +} + +QString Stanza::lang() const +{ + return d->e.attributeNS(NS_XML, "lang", QString()); +} + +void Stanza::setTo(const Jid &j) +{ + d->e.setAttribute("to", j.full()); +} + +void Stanza::setFrom(const Jid &j) +{ + d->e.setAttribute("from", j.full()); +} + +void Stanza::setId(const QString &id) +{ + d->e.setAttribute("id", id); +} + +void Stanza::setType(const QString &type) +{ + d->e.setAttribute("type", type); +} + +void Stanza::setLang(const QString &lang) +{ + d->e.setAttribute("xml:lang", lang); +} + +Stanza::Error Stanza::error() const +{ + Error err; + QDomElement e = d->e.elementsByTagNameNS(d->s->baseNS(), "error").item(0).toElement(); + if(e.isNull()) + return err; + + // type + int x = Private::stringToErrorType(e.attribute("type")); + if(x != -1) + err.type = x; + + // condition: find first element + QDomNodeList nl = e.childNodes(); + QDomElement t; + uint n; + for(n = 0; n < nl.count(); ++n) { + QDomNode i = nl.item(n); + if(i.isElement()) { + t = i.toElement(); + break; + } + } + if(!t.isNull() && t.namespaceURI() == NS_STANZAS) { + x = Private::stringToErrorCond(t.tagName()); + if(x != -1) + err.condition = x; + } + + // text + t = e.elementsByTagNameNS(NS_STANZAS, "text").item(0).toElement(); + if(!t.isNull()) + err.text = t.text(); + else + err.text = e.text(); + + // appspec: find first non-standard namespaced element + nl = e.childNodes(); + for(n = 0; n < nl.count(); ++n) { + QDomNode i = nl.item(n); + if(i.isElement() && i.namespaceURI() != NS_STANZAS) { + err.appSpec = i.toElement(); + break; + } + } + return err; +} + +void Stanza::setError(const Error &err) +{ + // create the element if necessary + QDomElement errElem = d->e.elementsByTagNameNS(d->s->baseNS(), "error").item(0).toElement(); + if(errElem.isNull()) { + errElem = d->e.ownerDocument().createElementNS(d->s->baseNS(), "error"); + d->e.appendChild(errElem); + } + + // error type/condition + if(d->s->old()) { + errElem.setAttribute("code", QString::number(err.condition)); + } + else { + QString stype = Private::errorTypeToString(err.type); + if(stype.isEmpty()) + return; + QString scond = Private::errorCondToString(err.condition); + if(scond.isEmpty()) + return; + + errElem.setAttribute("type", stype); + errElem.appendChild(d->e.ownerDocument().createElementNS(d->s->baseNS(), scond)); + } + + // text + if(d->s->old()) { + errElem.appendChild(d->e.ownerDocument().createTextNode(err.text)); + } + else { + QDomElement te = d->e.ownerDocument().createElementNS(d->s->baseNS(), "text"); + te.appendChild(d->e.ownerDocument().createTextNode(err.text)); + errElem.appendChild(te); + } + + // application specific + errElem.appendChild(err.appSpec); +} + +void Stanza::clearError() +{ + QDomElement errElem = d->e.elementsByTagNameNS(d->s->baseNS(), "error").item(0).toElement(); + if(!errElem.isNull()) + d->e.removeChild(errElem); +} + +//---------------------------------------------------------------------------- +// Stream +//---------------------------------------------------------------------------- +static XmlProtocol *foo = 0; +Stream::Stream(QObject *parent) +:QObject(parent) +{ +} + +Stream::~Stream() +{ +} + +Stanza Stream::createStanza(Stanza::Kind k, const Jid &to, const QString &type, const QString &id) +{ + return Stanza(this, k, to, type, id); +} + +Stanza Stream::createStanza(const QDomElement &e) +{ + return Stanza(this, e); +} + +QString Stream::xmlToString(const QDomElement &e, bool clip) +{ + if(!foo) + foo = new CoreProtocol; + return foo->elementToString(e, clip); +} + +//---------------------------------------------------------------------------- +// ClientStream +//---------------------------------------------------------------------------- +enum { + Idle, + Connecting, + WaitVersion, + WaitTLS, + NeedParams, + Active, + Closing +}; + +enum { + Client, + Server +}; + +class ClientStream::Private +{ +public: + Private() + { + conn = 0; + bs = 0; + ss = 0; + tlsHandler = 0; + tls = 0; + sasl = 0; + in.setAutoDelete(true); + + oldOnly = false; + allowPlain = false; + mutualAuth = false; + haveLocalAddr = false; + minimumSSF = 0; + maximumSSF = 0; + doBinding = true; + + in_rrsig = false; + + reset(); + } + + void reset() + { + state = Idle; + notify = 0; + newStanzas = false; + sasl_ssf = 0; + tls_warned = false; + using_tls = false; + } + + Jid jid; + QString server; + bool oldOnly; + bool allowPlain, mutualAuth; + bool haveLocalAddr; + QHostAddress localAddr; + Q_UINT16 localPort; + int minimumSSF, maximumSSF; + QString sasl_mech; + bool doBinding; + + bool in_rrsig; + + Connector *conn; + ByteStream *bs; + TLSHandler *tlsHandler; + QCA::TLS *tls; + QCA::SASL *sasl; + SecureStream *ss; + CoreProtocol client; + CoreProtocol srv; + + QString defRealm; + + int mode; + int state; + int notify; + bool newStanzas; + int sasl_ssf; + bool tls_warned, using_tls; + bool doAuth; + + QStringList sasl_mechlist; + + int errCond; + QString errText; + QDomElement errAppSpec; + + QPtrList<Stanza> in; + + QTimer noopTimer; + int noop_time; +}; + +ClientStream::ClientStream(Connector *conn, TLSHandler *tlsHandler, QObject *parent) +:Stream(parent) +{ + d = new Private; + d->mode = Client; + d->conn = conn; + connect(d->conn, SIGNAL(connected()), SLOT(cr_connected())); + connect(d->conn, SIGNAL(error()), SLOT(cr_error())); + + d->noop_time = 0; + connect(&d->noopTimer, SIGNAL(timeout()), SLOT(doNoop())); + + d->tlsHandler = tlsHandler; +} + +ClientStream::ClientStream(const QString &host, const QString &defRealm, ByteStream *bs, QCA::TLS *tls, QObject *parent) +:Stream(parent) +{ + d = new Private; + d->mode = Server; + d->bs = bs; + connect(d->bs, SIGNAL(connectionClosed()), SLOT(bs_connectionClosed())); + connect(d->bs, SIGNAL(delayedCloseFinished()), SLOT(bs_delayedCloseFinished())); + connect(d->bs, SIGNAL(error(int)), SLOT(bs_error(int))); + + QByteArray spare = d->bs->read(); + + d->ss = new SecureStream(d->bs); + connect(d->ss, SIGNAL(readyRead()), SLOT(ss_readyRead())); + connect(d->ss, SIGNAL(bytesWritten(int)), SLOT(ss_bytesWritten(int))); + connect(d->ss, SIGNAL(tlsHandshaken()), SLOT(ss_tlsHandshaken())); + connect(d->ss, SIGNAL(tlsClosed()), SLOT(ss_tlsClosed())); + connect(d->ss, SIGNAL(error(int)), SLOT(ss_error(int))); + + d->server = host; + d->defRealm = defRealm; + + d->tls = tls; + + d->srv.startClientIn(genId()); + //d->srv.startServerIn(genId()); + //d->state = Connecting; + //d->jid = Jid(); + //d->server = QString(); +} + +ClientStream::~ClientStream() +{ + reset(); + delete d; +} + +void ClientStream::reset(bool all) +{ + d->reset(); + d->noopTimer.stop(); + + // delete securestream + delete d->ss; + d->ss = 0; + + // reset sasl + delete d->sasl; + d->sasl = 0; + + // client + if(d->mode == Client) { + // reset tls + if(d->tlsHandler) + d->tlsHandler->reset(); + + // reset connector + if(d->bs) { + d->bs->close(); + d->bs = 0; + } + d->conn->done(); + + // reset state machine + d->client.reset(); + } + // server + else { + if(d->tls) + d->tls->reset(); + + if(d->bs) { + d->bs->close(); + d->bs = 0; + } + + d->srv.reset(); + } + + if(all) + d->in.clear(); +} + +Jid ClientStream::jid() const +{ + return d->jid; +} + +void ClientStream::connectToServer(const Jid &jid, bool auth) +{ + reset(true); + d->state = Connecting; + d->jid = jid; + d->doAuth = auth; + d->server = d->jid.domain(); + + d->conn->connectToServer(d->server); +} + +void ClientStream::continueAfterWarning() +{ + if(d->state == WaitVersion) { + // if we don't have TLS yet, then we're never going to get it + if(!d->tls_warned && !d->using_tls) { + d->tls_warned = true; + d->state = WaitTLS; + warning(WarnNoTLS); + return; + } + d->state = Connecting; + processNext(); + } + else if(d->state == WaitTLS) { + d->state = Connecting; + processNext(); + } +} + +void ClientStream::accept() +{ + d->srv.host = d->server; + processNext(); +} + +bool ClientStream::isActive() const +{ + return (d->state != Idle) ? true: false; +} + +bool ClientStream::isAuthenticated() const +{ + return (d->state == Active) ? true: false; +} + +void ClientStream::setUsername(const QString &s) +{ + if(d->sasl) + d->sasl->setUsername(s); +} + +void ClientStream::setPassword(const QString &s) +{ + if(d->client.old) { + d->client.setPassword(s); + } + else { + if(d->sasl) + d->sasl->setPassword(s); + } +} + +void ClientStream::setRealm(const QString &s) +{ + if(d->sasl) + d->sasl->setRealm(s); +} + +void ClientStream::continueAfterParams() +{ + if(d->state == NeedParams) { + d->state = Connecting; + if(d->client.old) { + processNext(); + } + else { + if(d->sasl) + d->sasl->continueAfterParams(); + } + } +} + +void ClientStream::setResourceBinding(bool b) +{ + d->doBinding = b; +} + +void ClientStream::setNoopTime(int mills) +{ + d->noop_time = mills; + + if(d->state != Active) + return; + + if(d->noop_time == 0) { + d->noopTimer.stop(); + return; + } + d->noopTimer.start(d->noop_time); +} + +QString ClientStream::saslMechanism() const +{ + return d->client.saslMech(); +} + +int ClientStream::saslSSF() const +{ + return d->sasl_ssf; +} + +void ClientStream::setSASLMechanism(const QString &s) +{ + d->sasl_mech = s; +} + +void ClientStream::setLocalAddr(const QHostAddress &addr, Q_UINT16 port) +{ + d->haveLocalAddr = true; + d->localAddr = addr; + d->localPort = port; +} + +int ClientStream::errorCondition() const +{ + return d->errCond; +} + +QString ClientStream::errorText() const +{ + return d->errText; +} + +QDomElement ClientStream::errorAppSpec() const +{ + return d->errAppSpec; +} + +bool ClientStream::old() const +{ + return d->client.old; +} + +void ClientStream::close() +{ + if(d->state == Active) { + d->state = Closing; + d->client.shutdown(); + processNext(); + } + else if(d->state != Idle && d->state != Closing) { + reset(); + } +} + +QDomDocument & ClientStream::doc() const +{ + return d->client.doc; +} + +QString ClientStream::baseNS() const +{ + return NS_CLIENT; +} + +QString ClientStream::xhtmlImNS() const +{ + return NS_XHTML_IM; +} + +QString ClientStream::xhtmlNS() const +{ + return NS_XHTML; +} + +void ClientStream::setAllowPlain(bool b) +{ + d->allowPlain = b; +} + +void ClientStream::setRequireMutualAuth(bool b) +{ + d->mutualAuth = b; +} + +void ClientStream::setSSFRange(int low, int high) +{ + d->minimumSSF = low; + d->maximumSSF = high; +} + +void ClientStream::setOldOnly(bool b) +{ + d->oldOnly = b; +} + +bool ClientStream::stanzaAvailable() const +{ + return (!d->in.isEmpty()); +} + +Stanza ClientStream::read() +{ + if(d->in.isEmpty()) + return Stanza(); + else { + Stanza *sp = d->in.getFirst(); + Stanza s = *sp; + d->in.removeRef(sp); + return s; + } +} + +void ClientStream::write(const Stanza &s) +{ + if(d->state == Active) { + d->client.sendStanza(s.element()); + processNext(); + } +} + +void ClientStream::cr_connected() +{ + d->bs = d->conn->stream(); + connect(d->bs, SIGNAL(connectionClosed()), SLOT(bs_connectionClosed())); + connect(d->bs, SIGNAL(delayedCloseFinished()), SLOT(bs_delayedCloseFinished())); + + QByteArray spare = d->bs->read(); + + d->ss = new SecureStream(d->bs); + connect(d->ss, SIGNAL(readyRead()), SLOT(ss_readyRead())); + connect(d->ss, SIGNAL(bytesWritten(int)), SLOT(ss_bytesWritten(int))); + connect(d->ss, SIGNAL(tlsHandshaken()), SLOT(ss_tlsHandshaken())); + connect(d->ss, SIGNAL(tlsClosed()), SLOT(ss_tlsClosed())); + connect(d->ss, SIGNAL(error(int)), SLOT(ss_error(int))); + + //d->client.startDialbackOut("andbit.net", "im.pyxa.org"); + //d->client.startServerOut(d->server); + + d->client.startClientOut(d->jid, d->oldOnly, d->conn->useSSL(), d->doAuth); + d->client.setAllowTLS(d->tlsHandler ? true: false); + d->client.setAllowBind(d->doBinding); + d->client.setAllowPlain(d->allowPlain); + + /*d->client.jid = d->jid; + d->client.server = d->server; + d->client.allowPlain = d->allowPlain; + d->client.oldOnly = d->oldOnly; + d->client.sasl_mech = d->sasl_mech; + d->client.doTLS = d->tlsHandler ? true: false; + d->client.doBinding = d->doBinding;*/ + + QGuardedPtr<QObject> self = this; + connected(); + if(!self) + return; + + // immediate SSL? + if(d->conn->useSSL()) { + d->using_tls = true; + d->ss->startTLSClient(d->tlsHandler, d->server, spare); + } + else { + d->client.addIncomingData(spare); + processNext(); + } +} + +void ClientStream::cr_error() +{ + reset(); + error(ErrConnection); +} + +void ClientStream::bs_connectionClosed() +{ + reset(); + connectionClosed(); +} + +void ClientStream::bs_delayedCloseFinished() +{ + // we don't care about this (we track all important data ourself) +} + +void ClientStream::bs_error(int) +{ + // TODO +} + +void ClientStream::ss_readyRead() +{ + QByteArray a = d->ss->read(); + +#ifdef XMPP_DEBUG + QCString cs(a.data(), a.size()+1); + fprintf(stderr, "ClientStream: recv: %d [%s]\n", a.size(), cs.data()); +#endif + + if(d->mode == Client) + d->client.addIncomingData(a); + else + d->srv.addIncomingData(a); + if(d->notify & CoreProtocol::NRecv) { +#ifdef XMPP_DEBUG + printf("We needed data, so let's process it\n"); +#endif + processNext(); + } +} + +void ClientStream::ss_bytesWritten(int bytes) +{ + if(d->mode == Client) + d->client.outgoingDataWritten(bytes); + else + d->srv.outgoingDataWritten(bytes); + + if(d->notify & CoreProtocol::NSend) { +#ifdef XMPP_DEBUG + printf("We were waiting for data to be written, so let's process\n"); +#endif + processNext(); + } +} + +void ClientStream::ss_tlsHandshaken() +{ + QGuardedPtr<QObject> self = this; + securityLayerActivated(LayerTLS); + if(!self) + return; + processNext(); +} + +void ClientStream::ss_tlsClosed() +{ + reset(); + connectionClosed(); +} + +void ClientStream::ss_error(int x) +{ + if(x == SecureStream::ErrTLS) { + reset(); + d->errCond = TLSFail; + error(ErrTLS); + } + else { + reset(); + error(ErrSecurityLayer); + } +} + +void ClientStream::sasl_clientFirstStep(const QString &mech, const QByteArray *stepData) +{ + d->client.setSASLFirst(mech, stepData ? *stepData : QByteArray()); + //d->client.sasl_mech = mech; + //d->client.sasl_firstStep = stepData ? true : false; + //d->client.sasl_step = stepData ? *stepData : QByteArray(); + + processNext(); +} + +void ClientStream::sasl_nextStep(const QByteArray &stepData) +{ + if(d->mode == Client) + d->client.setSASLNext(stepData); + //d->client.sasl_step = stepData; + else + d->srv.setSASLNext(stepData); + //d->srv.sasl_step = stepData; + + processNext(); +} + +void ClientStream::sasl_needParams(bool user, bool authzid, bool pass, bool realm) +{ +#ifdef XMPP_DEBUG + printf("need params: %d,%d,%d,%d\n", user, authzid, pass, realm); +#endif + if(authzid && !user) { + d->sasl->setAuthzid(d->jid.bare()); + //d->sasl->setAuthzid("infiniti.homelesshackers.org"); + } + if(user || pass || realm) { + d->state = NeedParams; + needAuthParams(user, pass, realm); + } + else + d->sasl->continueAfterParams(); +} + +void ClientStream::sasl_authCheck(const QString &user, const QString &) +{ +//#ifdef XMPP_DEBUG +// printf("authcheck: [%s], [%s]\n", user.latin1(), authzid.latin1()); +//#endif + QString u = user; + int n = u.find('@'); + if(n != -1) + u.truncate(n); + d->srv.user = u; + d->sasl->continueAfterAuthCheck(); +} + +void ClientStream::sasl_authenticated() +{ +#ifdef XMPP_DEBUG + printf("sasl authed!!\n"); +#endif + d->sasl_ssf = d->sasl->ssf(); + + if(d->mode == Server) { + d->srv.setSASLAuthed(); + processNext(); + } +} + +void ClientStream::sasl_error(int) +{ +//#ifdef XMPP_DEBUG +// printf("sasl error: %d\n", c); +//#endif + // has to be auth error + int x = convertedSASLCond(); + reset(); + d->errCond = x; + error(ErrAuth); +} + +void ClientStream::srvProcessNext() +{ + while(1) { + printf("Processing step...\n"); + if(!d->srv.processStep()) { + int need = d->srv.need; + if(need == CoreProtocol::NNotify) { + d->notify = d->srv.notify; + if(d->notify & CoreProtocol::NSend) + printf("More data needs to be written to process next step\n"); + if(d->notify & CoreProtocol::NRecv) + printf("More data is needed to process next step\n"); + } + else if(need == CoreProtocol::NSASLMechs) { + if(!d->sasl) { + d->sasl = new QCA::SASL; + connect(d->sasl, SIGNAL(authCheck(const QString &, const QString &)), SLOT(sasl_authCheck(const QString &, const QString &))); + connect(d->sasl, SIGNAL(nextStep(const QByteArray &)), SLOT(sasl_nextStep(const QByteArray &))); + connect(d->sasl, SIGNAL(authenticated()), SLOT(sasl_authenticated())); + connect(d->sasl, SIGNAL(error(int)), SLOT(sasl_error(int))); + + //d->sasl->setAllowAnonymous(false); + //d->sasl->setRequirePassCredentials(true); + //d->sasl->setExternalAuthID("localhost"); + + d->sasl->setMinimumSSF(0); + d->sasl->setMaximumSSF(256); + + QStringList list; + // TODO: d->server is probably wrong here + if(!d->sasl->startServer("xmpp", d->server, d->defRealm, &list)) { + printf("Error initializing SASL\n"); + return; + } + d->sasl_mechlist = list; + } + d->srv.setSASLMechList(d->sasl_mechlist); + continue; + } + else if(need == CoreProtocol::NStartTLS) { + printf("Need StartTLS\n"); + if(!d->tls->startServer()) { + printf("unable to start server!\n"); + // TODO + return; + } + QByteArray a = d->srv.spare; + d->ss->startTLSServer(d->tls, a); + } + else if(need == CoreProtocol::NSASLFirst) { + printf("Need SASL First Step\n"); + QByteArray a = d->srv.saslStep(); + d->sasl->putServerFirstStep(d->srv.saslMech(), a); + } + else if(need == CoreProtocol::NSASLNext) { + printf("Need SASL Next Step\n"); + QByteArray a = d->srv.saslStep(); + QCString cs(a.data(), a.size()+1); + printf("[%s]\n", cs.data()); + d->sasl->putStep(a); + } + else if(need == CoreProtocol::NSASLLayer) { + } + + // now we can announce stanzas + //if(!d->in.isEmpty()) + // readyRead(); + return; + } + + d->notify = 0; + + int event = d->srv.event; + printf("event: %d\n", event); + switch(event) { + case CoreProtocol::EError: { + printf("Error! Code=%d\n", d->srv.errorCode); + reset(); + error(ErrProtocol); + //handleError(); + return; + } + case CoreProtocol::ESend: { + QByteArray a = d->srv.takeOutgoingData(); + QCString cs(a.size()+1); + memcpy(cs.data(), a.data(), a.size()); + printf("Need Send: {%s}\n", cs.data()); + d->ss->write(a); + break; + } + case CoreProtocol::ERecvOpen: { + printf("Break (RecvOpen)\n"); + + // calculate key + QCString str = QCA::SHA1::hashToString("secret").utf8(); + str = QCA::SHA1::hashToString(str + "im.pyxa.org").utf8(); + str = QCA::SHA1::hashToString(str + d->srv.id.utf8()).utf8(); + d->srv.setDialbackKey(str); + + //d->srv.setDialbackKey("3c5d721ea2fcc45b163a11420e4e358f87e3142a"); + + if(d->srv.to != d->server) { + // host-gone, host-unknown, see-other-host + d->srv.shutdownWithError(CoreProtocol::HostUnknown); + } + else + d->srv.setFrom(d->server); + break; + } + case CoreProtocol::ESASLSuccess: { + printf("Break SASL Success\n"); + disconnect(d->sasl, SIGNAL(error(int)), this, SLOT(sasl_error(int))); + QByteArray a = d->srv.spare; + d->ss->setLayerSASL(d->sasl, a); + break; + } + case CoreProtocol::EPeerClosed: { + // TODO: this isn' an error + printf("peer closed\n"); + reset(); + error(ErrProtocol); + return; + } + } + } +} + +void ClientStream::doReadyRead() +{ + //QGuardedPtr<QObject> self = this; + readyRead(); + //if(!self) + // return; + //d->in_rrsig = false; +} + +void ClientStream::processNext() +{ + if(d->mode == Server) { + srvProcessNext(); + return; + } + + QGuardedPtr<QObject> self = this; + + while(1) { +#ifdef XMPP_DEBUG + printf("Processing step...\n"); +#endif + bool ok = d->client.processStep(); + // deal with send/received items + for(QValueList<XmlProtocol::TransferItem>::ConstIterator it = d->client.transferItemList.begin(); it != d->client.transferItemList.end(); ++it) { + const XmlProtocol::TransferItem &i = *it; + if(i.isExternal) + continue; + QString str; + if(i.isString) { + // skip whitespace pings + if(i.str.stripWhiteSpace().isEmpty()) + continue; + str = i.str; + } + else + str = d->client.elementToString(i.elem); + if(i.isSent) + outgoingXml(str); + else + incomingXml(str); + } + + if(!ok) { + bool cont = handleNeed(); + + // now we can announce stanzas + //if(!d->in_rrsig && !d->in.isEmpty()) { + if(!d->in.isEmpty()) { + //d->in_rrsig = true; + QTimer::singleShot(0, this, SLOT(doReadyRead())); + } + + if(cont) + continue; + return; + } + + int event = d->client.event; + d->notify = 0; + switch(event) { + case CoreProtocol::EError: { +#ifdef XMPP_DEBUG + printf("Error! Code=%d\n", d->client.errorCode); +#endif + handleError(); + return; + } + case CoreProtocol::ESend: { + QByteArray a = d->client.takeOutgoingData(); +#ifdef XMPP_DEBUG + QCString cs(a.size()+1); + memcpy(cs.data(), a.data(), a.size()); + printf("Need Send: {%s}\n", cs.data()); +#endif + d->ss->write(a); + break; + } + case CoreProtocol::ERecvOpen: { +#ifdef XMPP_DEBUG + printf("Break (RecvOpen)\n"); +#endif + +#ifdef XMPP_TEST + QString s = QString("handshake success (lang=[%1]").arg(d->client.lang); + if(!d->client.from.isEmpty()) + s += QString(", from=[%1]").arg(d->client.from); + s += ')'; + TD::msg(s); +#endif + + if(d->client.old) { + d->state = WaitVersion; + warning(WarnOldVersion); + return; + } + break; + } + case CoreProtocol::EFeatures: { +#ifdef XMPP_DEBUG + printf("Break (Features)\n"); +#endif + if(!d->tls_warned && !d->using_tls && !d->client.features.tls_supported) { + d->tls_warned = true; + d->state = WaitTLS; + warning(WarnNoTLS); + return; + } + break; + } + case CoreProtocol::ESASLSuccess: { +#ifdef XMPP_DEBUG + printf("Break SASL Success\n"); +#endif + break; + } + case CoreProtocol::EReady: { +#ifdef XMPP_DEBUG + printf("Done!\n"); +#endif + // grab the JID, in case it changed + // TODO: d->jid = d->client.jid; + d->state = Active; + setNoopTime(d->noop_time); + authenticated(); + if(!self) + return; + break; + } + case CoreProtocol::EPeerClosed: { +#ifdef XMPP_DEBUG + printf("DocumentClosed\n"); +#endif + reset(); + connectionClosed(); + return; + } + case CoreProtocol::EStanzaReady: { +#ifdef XMPP_DEBUG + printf("StanzaReady\n"); +#endif + // store the stanza for now, announce after processing all events + Stanza s = createStanza(d->client.recvStanza()); + if(s.isNull()) + break; + d->in.append(new Stanza(s)); + break; + } + case CoreProtocol::EStanzaSent: { +#ifdef XMPP_DEBUG + printf("StanzasSent\n"); +#endif + stanzaWritten(); + if(!self) + return; + break; + } + case CoreProtocol::EClosed: { +#ifdef XMPP_DEBUG + printf("Closed\n"); +#endif + reset(); + delayedCloseFinished(); + return; + } + } + } +} + +bool ClientStream::handleNeed() +{ + int need = d->client.need; + if(need == CoreProtocol::NNotify) { + d->notify = d->client.notify; +#ifdef XMPP_DEBUG + if(d->notify & CoreProtocol::NSend) + printf("More data needs to be written to process next step\n"); + if(d->notify & CoreProtocol::NRecv) + printf("More data is needed to process next step\n"); +#endif + return false; + } + + d->notify = 0; + switch(need) { + case CoreProtocol::NStartTLS: { +#ifdef XMPP_DEBUG + printf("Need StartTLS\n"); +#endif + d->using_tls = true; + d->ss->startTLSClient(d->tlsHandler, d->server, d->client.spare); + return false; + } + case CoreProtocol::NSASLFirst: { +#ifdef XMPP_DEBUG + printf("Need SASL First Step\n"); +#endif + // no SASL plugin? fall back to Simple SASL + if(!QCA::isSupported(QCA::CAP_SASL)) { + // Simple SASL needs MD5. do we have that either? + if(!QCA::isSupported(QCA::CAP_MD5)) + QCA::insertProvider(createProviderHash()); + QCA::insertProvider(createProviderSimpleSASL()); + } + + d->sasl = new QCA::SASL; + connect(d->sasl, SIGNAL(clientFirstStep(const QString &, const QByteArray *)), SLOT(sasl_clientFirstStep(const QString &, const QByteArray *))); + connect(d->sasl, SIGNAL(nextStep(const QByteArray &)), SLOT(sasl_nextStep(const QByteArray &))); + connect(d->sasl, SIGNAL(needParams(bool, bool, bool, bool)), SLOT(sasl_needParams(bool, bool, bool, bool))); + connect(d->sasl, SIGNAL(authenticated()), SLOT(sasl_authenticated())); + connect(d->sasl, SIGNAL(error(int)), SLOT(sasl_error(int))); + + if(d->haveLocalAddr) + d->sasl->setLocalAddr(d->localAddr, d->localPort); + if(d->conn->havePeerAddress()) + d->sasl->setRemoteAddr(d->conn->peerAddress(), d->conn->peerPort()); + + d->sasl->setAllowAnonymous(false); + + //d->sasl_mech = "ANONYMOUS"; + //d->sasl->setRequirePassCredentials(true); + + //d->sasl->setExternalAuthID("localhost"); + //d->sasl->setExternalSSF(64); + //d->sasl_mech = "EXTERNAL"; + + d->sasl->setAllowPlain(d->allowPlain); + d->sasl->setRequireMutualAuth(d->mutualAuth); + + d->sasl->setMinimumSSF(d->minimumSSF); + d->sasl->setMaximumSSF(d->maximumSSF); + + QStringList ml; + if(!d->sasl_mech.isEmpty()) + ml += d->sasl_mech; + else + ml = d->client.features.sasl_mechs; + + if(!d->sasl->startClient("xmpp", d->server, ml, true)) { + int x = convertedSASLCond(); + reset(); + d->errCond = x; + error(ErrAuth); + return false; + } + return false; + } + case CoreProtocol::NSASLNext: { +#ifdef XMPP_DEBUG + printf("Need SASL Next Step\n"); +#endif + QByteArray a = d->client.saslStep(); + d->sasl->putStep(a); + return false; + } + case CoreProtocol::NSASLLayer: { + // SecureStream will handle the errors from this point + disconnect(d->sasl, SIGNAL(error(int)), this, SLOT(sasl_error(int))); + d->ss->setLayerSASL(d->sasl, d->client.spare); + if(d->sasl_ssf > 0) { + QGuardedPtr<QObject> self = this; + securityLayerActivated(LayerSASL); + if(!self) + return false; + } + break; + } + case CoreProtocol::NPassword: { +#ifdef XMPP_DEBUG + printf("Need Password\n"); +#endif + d->state = NeedParams; + needAuthParams(false, true, false); + return false; + } + } + + return true; +} + +int ClientStream::convertedSASLCond() const +{ + int x = d->sasl->errorCondition(); + if(x == QCA::SASL::NoMech) + return NoMech; + else if(x == QCA::SASL::BadProto) + return BadProto; + else if(x == QCA::SASL::BadServ) + return BadServ; + else if(x == QCA::SASL::TooWeak) + return MechTooWeak; + else + return GenericAuthError; +} + +void ClientStream::doNoop() +{ + if(d->state == Active) { +#ifdef XMPP_DEBUG + printf("doPing\n"); +#endif + d->client.sendWhitespace(); + processNext(); + } +} + +void ClientStream::writeDirect(const QString &s) +{ + if(d->state == Active) { +#ifdef XMPP_DEBUG + printf("writeDirect\n"); +#endif + d->client.sendDirect(s); + processNext(); + } +} + +void ClientStream::handleError() +{ + int c = d->client.errorCode; + if(c == CoreProtocol::ErrParse) { + reset(); + error(ErrParse); + } + else if(c == CoreProtocol::ErrProtocol) { + reset(); + error(ErrProtocol); + } + else if(c == CoreProtocol::ErrStream) { + int x = d->client.errCond; + QString text = d->client.errText; + QDomElement appSpec = d->client.errAppSpec; + + int connErr = -1; + int strErr = -1; + + switch(x) { + case CoreProtocol::BadFormat: { break; } // should NOT happen (we send the right format) + case CoreProtocol::BadNamespacePrefix: { break; } // should NOT happen (we send prefixes) + case CoreProtocol::Conflict: { strErr = Conflict; break; } + case CoreProtocol::ConnectionTimeout: { strErr = ConnectionTimeout; break; } + case CoreProtocol::HostGone: { connErr = HostGone; break; } + case CoreProtocol::HostUnknown: { connErr = HostUnknown; break; } + case CoreProtocol::ImproperAddressing: { break; } // should NOT happen (we aren't a server) + case CoreProtocol::InternalServerError: { strErr = InternalServerError; break; } + case CoreProtocol::InvalidFrom: { strErr = InvalidFrom; break; } + case CoreProtocol::InvalidId: { break; } // should NOT happen (clients don't specify id) + case CoreProtocol::InvalidNamespace: { break; } // should NOT happen (we set the right ns) + case CoreProtocol::InvalidXml: { strErr = InvalidXml; break; } // shouldn't happen either, but just in case ... + case CoreProtocol::StreamNotAuthorized: { break; } // should NOT happen (we're not stupid) + case CoreProtocol::PolicyViolation: { strErr = PolicyViolation; break; } + case CoreProtocol::RemoteConnectionFailed: { connErr = RemoteConnectionFailed; break; } + case CoreProtocol::ResourceConstraint: { strErr = ResourceConstraint; break; } + case CoreProtocol::RestrictedXml: { strErr = InvalidXml; break; } // group with this one + case CoreProtocol::SeeOtherHost: { connErr = SeeOtherHost; break; } + case CoreProtocol::SystemShutdown: { strErr = SystemShutdown; break; } + case CoreProtocol::UndefinedCondition: { break; } // leave as null error + case CoreProtocol::UnsupportedEncoding: { break; } // should NOT happen (we send good encoding) + case CoreProtocol::UnsupportedStanzaType: { break; } // should NOT happen (we're not stupid) + case CoreProtocol::UnsupportedVersion: { connErr = UnsupportedVersion; break; } + case CoreProtocol::XmlNotWellFormed: { strErr = InvalidXml; break; } // group with this one + default: { break; } + } + + reset(); + + d->errText = text; + d->errAppSpec = appSpec; + if(connErr != -1) { + d->errCond = connErr; + error(ErrNeg); + } + else { + if(strErr != -1) + d->errCond = strErr; + else + d->errCond = GenericStreamError; + error(ErrStream); + } + } + else if(c == CoreProtocol::ErrStartTLS) { + reset(); + d->errCond = TLSStart; + error(ErrTLS); + } + else if(c == CoreProtocol::ErrAuth) { + int x = d->client.errCond; + int r = GenericAuthError; + if(d->client.old) { + if(x == 401) // not authorized + r = NotAuthorized; + else if(x == 409) // conflict + r = GenericAuthError; + else if(x == 406) // not acceptable (this should NOT happen) + r = GenericAuthError; + } + else { + switch(x) { + case CoreProtocol::Aborted: { r = GenericAuthError; break; } // should NOT happen (we never send <abort/>) + case CoreProtocol::IncorrectEncoding: { r = GenericAuthError; break; } // should NOT happen + case CoreProtocol::InvalidAuthzid: { r = InvalidAuthzid; break; } + case CoreProtocol::InvalidMech: { r = InvalidMech; break; } + case CoreProtocol::MechTooWeak: { r = MechTooWeak; break; } + case CoreProtocol::NotAuthorized: { r = NotAuthorized; break; } + case CoreProtocol::TemporaryAuthFailure: { r = TemporaryAuthFailure; break; } + } + } + reset(); + d->errCond = r; + error(ErrAuth); + } + else if(c == CoreProtocol::ErrPlain) { + reset(); + d->errCond = NoMech; + error(ErrAuth); + } + else if(c == CoreProtocol::ErrBind) { + int r = -1; + if(d->client.errCond == CoreProtocol::BindBadRequest) { + // should NOT happen + } + else if(d->client.errCond == CoreProtocol::BindNotAllowed) { + r = BindNotAllowed; + } + else if(d->client.errCond == CoreProtocol::BindConflict) { + r = BindConflict; + } + + if(r != -1) { + reset(); + d->errCond = r; + error(ErrBind); + } + else { + reset(); + error(ErrProtocol); + } + } +} + +//---------------------------------------------------------------------------- +// Debug +//---------------------------------------------------------------------------- +Debug::~Debug() +{ +} + +#ifdef XMPP_TEST +TD::TD() +{ +} + +TD::~TD() +{ +} + +void TD::msg(const QString &s) +{ + if(debug_ptr) + debug_ptr->msg(s); +} + +void TD::outgoingTag(const QString &s) +{ + if(debug_ptr) + debug_ptr->outgoingTag(s); +} + +void TD::incomingTag(const QString &s) +{ + if(debug_ptr) + debug_ptr->incomingTag(s); +} + +void TD::outgoingXml(const QDomElement &e) +{ + if(debug_ptr) + debug_ptr->outgoingXml(e); +} + +void TD::incomingXml(const QDomElement &e) +{ + if(debug_ptr) + debug_ptr->incomingXml(e); +} +#endif diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/td.h b/kopete/protocols/jabber/libiris/iris/xmpp-core/td.h new file mode 100644 index 00000000..b636e190 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/td.h @@ -0,0 +1,20 @@ +#ifndef TESTDEBUG_H +#define TESTDEBUG_H + +#include<qdom.h> + +class TD +{ +public: + TD(); + ~TD(); + + static void msg(const QString &); + static void outgoingTag(const QString &); + static void incomingTag(const QString &); + static void outgoingXml(const QDomElement &); + static void incomingXml(const QDomElement &); +}; + +#endif + diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/tlshandler.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/tlshandler.cpp new file mode 100644 index 00000000..f3ac0067 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/tlshandler.cpp @@ -0,0 +1,138 @@ +/* + * tlshandler.cpp - abstract wrapper for TLS + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"xmpp.h" + +#include<qtimer.h> +#include"qca.h" + +using namespace XMPP; + +//---------------------------------------------------------------------------- +// TLSHandler +//---------------------------------------------------------------------------- +TLSHandler::TLSHandler(QObject *parent) +:QObject(parent) +{ +} + +TLSHandler::~TLSHandler() +{ +} + + +//---------------------------------------------------------------------------- +// QCATLSHandler +//---------------------------------------------------------------------------- +class QCATLSHandler::Private +{ +public: + QCA::TLS *tls; + int state, err; +}; + +QCATLSHandler::QCATLSHandler(QCA::TLS *parent) +:TLSHandler(parent) +{ + d = new Private; + d->tls = parent; + connect(d->tls, SIGNAL(handshaken()), SLOT(tls_handshaken())); + connect(d->tls, SIGNAL(readyRead()), SLOT(tls_readyRead())); + connect(d->tls, SIGNAL(readyReadOutgoing(int)), SLOT(tls_readyReadOutgoing(int))); + connect(d->tls, SIGNAL(closed()), SLOT(tls_closed())); + connect(d->tls, SIGNAL(error(int)), SLOT(tls_error(int))); + d->state = 0; + d->err = -1; +} + +QCATLSHandler::~QCATLSHandler() +{ + delete d; +} + +QCA::TLS *QCATLSHandler::tls() const +{ + return d->tls; +} + +int QCATLSHandler::tlsError() const +{ + return d->err; +} + +void QCATLSHandler::reset() +{ + d->tls->reset(); + d->state = 0; +} + +void QCATLSHandler::startClient(const QString &host) +{ + d->state = 0; + d->err = -1; + if(!d->tls->startClient(host)) + QTimer::singleShot(0, this, SIGNAL(fail())); +} + +void QCATLSHandler::write(const QByteArray &a) +{ + d->tls->write(a); +} + +void QCATLSHandler::writeIncoming(const QByteArray &a) +{ + d->tls->writeIncoming(a); +} + +void QCATLSHandler::continueAfterHandshake() +{ + if(d->state == 2) { + success(); + d->state = 3; + } +} + +void QCATLSHandler::tls_handshaken() +{ + d->state = 2; + tlsHandshaken(); +} + +void QCATLSHandler::tls_readyRead() +{ + readyRead(d->tls->read()); +} + +void QCATLSHandler::tls_readyReadOutgoing(int plainBytes) +{ + readyReadOutgoing(d->tls->readOutgoing(), plainBytes); +} + +void QCATLSHandler::tls_closed() +{ + closed(); +} + +void QCATLSHandler::tls_error(int x) +{ + d->err = x; + d->state = 0; + fail(); +} diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/xmlprotocol.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/xmlprotocol.cpp new file mode 100644 index 00000000..c70a04a9 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/xmlprotocol.cpp @@ -0,0 +1,543 @@ +/* + * xmlprotocol.cpp - state machine for 'jabber-like' protocols + * 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 + * + */ + +#include"xmlprotocol.h" + +#include"bytestream.h" + +using namespace XMPP; + +// stripExtraNS +// +// This function removes namespace information from various nodes for +// display purposes only (the element is pretty much useless for processing +// after this). We do this because QXml is a bit overzealous about outputting +// redundant namespaces. +static QDomElement stripExtraNS(const QDomElement &e) +{ + // find closest parent with a namespace + QDomNode par = e.parentNode(); + while(!par.isNull() && par.namespaceURI().isNull()) + par = par.parentNode(); + bool noShowNS = false; + if(!par.isNull() && par.namespaceURI() == e.namespaceURI()) + noShowNS = true; + + // build qName (prefix:localName) + QString qName; + if(!e.prefix().isEmpty()) + qName = e.prefix() + ':' + e.localName(); + else + qName = e.tagName(); + + QDomElement i; + uint x; + if(noShowNS) + i = e.ownerDocument().createElement(qName); + else + i = e.ownerDocument().createElementNS(e.namespaceURI(), qName); + + // copy attributes + QDomNamedNodeMap al = e.attributes(); + for(x = 0; x < al.count(); ++x) { + QDomAttr a = al.item(x).cloneNode().toAttr(); + + // don't show xml namespace + if(a.namespaceURI() == NS_XML) + i.setAttribute(QString("xml:") + a.name(), a.value()); + else + i.setAttributeNodeNS(a); + } + + // copy children + QDomNodeList nl = e.childNodes(); + for(x = 0; x < nl.count(); ++x) { + QDomNode n = nl.item(x); + if(n.isElement()) + i.appendChild(stripExtraNS(n.toElement())); + else + i.appendChild(n.cloneNode()); + } + return i; +} + +// xmlToString +// +// This function converts a QDomElement into a QString, using stripExtraNS +// to make it pretty. +static QString xmlToString(const QDomElement &e, const QString &fakeNS, const QString &fakeQName, bool clip) +{ + QDomElement i = e.cloneNode().toElement(); + + // It seems QDom can only have one namespace attribute at a time (see docElement 'HACK'). + // Fortunately we only need one kind depending on the input, so it is specified here. + QDomElement fake = e.ownerDocument().createElementNS(fakeNS, fakeQName); + fake.appendChild(i); + fake = stripExtraNS(fake); + QString out; + { + QTextStream ts(&out, IO_WriteOnly); + fake.firstChild().save(ts, 0); + } + // 'clip' means to remove any unwanted (and unneeded) characters, such as a trailing newline + if(clip) { + int n = out.findRev('>'); + out.truncate(n+1); + } + return out; +} + +// createRootXmlTags +// +// This function creates three QStrings, one being an <?xml .. ?> processing +// instruction, and the others being the opening and closing tags of an +// element, <foo> and </foo>. This basically allows us to get the raw XML +// text needed to open/close an XML stream, without resorting to generating +// the XML ourselves. This function uses QDom to do the generation, which +// ensures proper encoding and entity output. +static void createRootXmlTags(const QDomElement &root, QString *xmlHeader, QString *tagOpen, QString *tagClose) +{ + QDomElement e = root.cloneNode(false).toElement(); + + // insert a dummy element to ensure open and closing tags are generated + QDomElement dummy = e.ownerDocument().createElement("dummy"); + e.appendChild(dummy); + + // convert to xml->text + QString str; + { + QTextStream ts(&str, IO_WriteOnly); + e.save(ts, 0); + } + + // parse the tags out + int n = str.find('<'); + int n2 = str.find('>', n); + ++n2; + *tagOpen = str.mid(n, n2-n); + n2 = str.findRev('>'); + n = str.findRev('<'); + ++n2; + *tagClose = str.mid(n, n2-n); + + // generate a nice xml processing header + *xmlHeader = "<?xml version=\"1.0\"?>"; +} + +//---------------------------------------------------------------------------- +// Protocol +//---------------------------------------------------------------------------- +XmlProtocol::TransferItem::TransferItem() +{ +} + +XmlProtocol::TransferItem::TransferItem(const QString &_str, bool sent, bool external) +{ + isString = true; + isSent = sent; + isExternal = external; + str = _str; +} + +XmlProtocol::TransferItem::TransferItem(const QDomElement &_elem, bool sent, bool external) +{ + isString = false; + isSent = sent; + isExternal = external; + elem = _elem; +} + +XmlProtocol::XmlProtocol() +{ + init(); +} + +XmlProtocol::~XmlProtocol() +{ +} + +void XmlProtocol::init() +{ + incoming = false; + peerClosed = false; + closeWritten = false; +} + +void XmlProtocol::reset() +{ + init(); + + elem = QDomElement(); + tagOpen = QString(); + tagClose = QString(); + xml.reset(); + outData.resize(0); + trackQueue.clear(); + transferItemList.clear(); +} + +void XmlProtocol::addIncomingData(const QByteArray &a) +{ + xml.appendData(a); +} + +QByteArray XmlProtocol::takeOutgoingData() +{ + QByteArray a = outData.copy(); + outData.resize(0); + return a; +} + +void XmlProtocol::outgoingDataWritten(int bytes) +{ + for(QValueList<TrackItem>::Iterator it = trackQueue.begin(); it != trackQueue.end();) { + TrackItem &i = *it; + + // enough bytes? + if(bytes < i.size) { + i.size -= bytes; + break; + } + int type = i.type; + int id = i.id; + int size = i.size; + bytes -= i.size; + it = trackQueue.remove(it); + + if(type == TrackItem::Raw) { + // do nothing + } + else if(type == TrackItem::Close) { + closeWritten = true; + } + else if(type == TrackItem::Custom) { + itemWritten(id, size); + } + } +} + +bool XmlProtocol::processStep() +{ + Parser::Event pe; + notify = 0; + transferItemList.clear(); + + if(state != Closing && (state == RecvOpen || stepAdvancesParser())) { + // if we get here, then it's because we're in some step that advances the parser + pe = xml.readNext(); + if(!pe.isNull()) { + // note: error/close events should be handled for ALL steps, so do them here + switch(pe.type()) { + case Parser::Event::DocumentOpen: { + transferItemList += TransferItem(pe.actualString(), false); + + //stringRecv(pe.actualString()); + break; + } + case Parser::Event::DocumentClose: { + transferItemList += TransferItem(pe.actualString(), false); + + //stringRecv(pe.actualString()); + if(incoming) { + sendTagClose(); + event = ESend; + peerClosed = true; + state = Closing; + } + else { + event = EPeerClosed; + } + return true; + } + case Parser::Event::Element: { + transferItemList += TransferItem(pe.element(), false); + + //elementRecv(pe.element()); + break; + } + case Parser::Event::Error: { + if(incoming) { + // If we get a parse error during the initial element exchange, + // flip immediately into 'open' mode so that we can report an error. + if(state == RecvOpen) { + sendTagOpen(); + state = Open; + } + return handleError(); + } + else { + event = EError; + errorCode = ErrParse; + return true; + } + } + } + } + else { + if(state == RecvOpen || stepRequiresElement()) { + need = NNotify; + notify |= NRecv; + return false; + } + } + } + + return baseStep(pe); +} + +QString XmlProtocol::xmlEncoding() const +{ + return xml.encoding(); +} + +QString XmlProtocol::elementToString(const QDomElement &e, bool clip) +{ + if(elem.isNull()) + elem = elemDoc.importNode(docElement(), true).toElement(); + + // Determine the appropriate 'fakeNS' to use + QString ns; + + // first, check root namespace + QString pre = e.prefix(); + if(pre.isNull()) + pre = ""; + if(pre == elem.prefix()) { + ns = elem.namespaceURI(); + } + else { + // scan the root attributes for 'xmlns' (oh joyous hacks) + QDomNamedNodeMap al = elem.attributes(); + uint n; + for(n = 0; n < al.count(); ++n) { + QDomAttr a = al.item(n).toAttr(); + QString s = a.name(); + int x = s.find(':'); + if(x != -1) + s = s.mid(x+1); + else + s = ""; + if(pre == s) { + ns = a.value(); + break; + } + } + if(n >= al.count()) { + // if we get here, then no appropriate ns was found. use root then.. + ns = elem.namespaceURI(); + } + } + + // build qName + QString qn; + if(!elem.prefix().isEmpty()) + qn = elem.prefix() + ':'; + qn += elem.localName(); + + // make the string + return xmlToString(e, ns, qn, clip); +} + +bool XmlProtocol::stepRequiresElement() const +{ + // default returns false + return false; +} + +void XmlProtocol::itemWritten(int, int) +{ + // default does nothing +} + +void XmlProtocol::stringSend(const QString &) +{ + // default does nothing +} + +void XmlProtocol::stringRecv(const QString &) +{ + // default does nothing +} + +void XmlProtocol::elementSend(const QDomElement &) +{ + // default does nothing +} + +void XmlProtocol::elementRecv(const QDomElement &) +{ + // default does nothing +} + +void XmlProtocol::startConnect() +{ + incoming = false; + state = SendOpen; +} + +void XmlProtocol::startAccept() +{ + incoming = true; + state = RecvOpen; +} + +bool XmlProtocol::close() +{ + sendTagClose(); + event = ESend; + state = Closing; + return true; +} + +int XmlProtocol::writeString(const QString &s, int id, bool external) +{ + transferItemList += TransferItem(s, true, external); + return internalWriteString(s, TrackItem::Custom, id); +} + +int XmlProtocol::writeElement(const QDomElement &e, int id, bool external, bool clip) +{ + if(e.isNull()) + return 0; + transferItemList += TransferItem(e, true, external); + + //elementSend(e); + QString out = elementToString(e, clip); + return internalWriteString(out, TrackItem::Custom, id); +} + +QByteArray XmlProtocol::resetStream() +{ + // reset the state + if(incoming) + state = RecvOpen; + else + state = SendOpen; + + // grab unprocessed data before resetting + QByteArray spare = xml.unprocessed(); + xml.reset(); + return spare; +} + +int XmlProtocol::internalWriteData(const QByteArray &a, TrackItem::Type t, int id) +{ + TrackItem i; + i.type = t; + i.id = id; + i.size = a.size(); + trackQueue += i; + + ByteStream::appendArray(&outData, a); + return a.size(); +} + +int XmlProtocol::internalWriteString(const QString &s, TrackItem::Type t, int id) +{ + QCString cs = s.utf8(); + QByteArray a(cs.length()); + memcpy(a.data(), cs.data(), a.size()); + return internalWriteData(a, t, id); +} + +void XmlProtocol::sendTagOpen() +{ + if(elem.isNull()) + elem = elemDoc.importNode(docElement(), true).toElement(); + + QString xmlHeader; + createRootXmlTags(elem, &xmlHeader, &tagOpen, &tagClose); + + QString s; + s += xmlHeader + '\n'; + s += tagOpen + '\n'; + + transferItemList += TransferItem(xmlHeader, true); + transferItemList += TransferItem(tagOpen, true); + + //stringSend(xmlHeader); + //stringSend(tagOpen); + internalWriteString(s, TrackItem::Raw); +} + +void XmlProtocol::sendTagClose() +{ + transferItemList += TransferItem(tagClose, true); + + //stringSend(tagClose); + internalWriteString(tagClose, TrackItem::Close); +} + +bool XmlProtocol::baseStep(const Parser::Event &pe) +{ + // Basic + if(state == SendOpen) { + sendTagOpen(); + event = ESend; + if(incoming) + state = Open; + else + state = RecvOpen; + return true; + } + else if(state == RecvOpen) { + if(incoming) + state = SendOpen; + else + state = Open; + + // note: event will always be DocumentOpen here + handleDocOpen(pe); + event = ERecvOpen; + return true; + } + else if(state == Open) { + QDomElement e; + if(pe.type() == Parser::Event::Element) + e = pe.element(); + return doStep(e); + } + // Closing + else { + if(closeWritten) { + if(peerClosed) { + event = EPeerClosed; + return true; + } + else + return handleCloseFinished(); + } + + need = NNotify; + notify = NSend; + return false; + } +} + +void XmlProtocol::setIncomingAsExternal() +{ + for(QValueList<TransferItem>::Iterator it = transferItemList.begin(); it != transferItemList.end(); ++it) { + TransferItem &i = *it; + // look for elements received + if(!i.isString && !i.isSent) + i.isExternal = true; + } +} + diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/xmlprotocol.h b/kopete/protocols/jabber/libiris/iris/xmpp-core/xmlprotocol.h new file mode 100644 index 00000000..5bf2cbda --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/xmlprotocol.h @@ -0,0 +1,145 @@ +/* + * xmlprotocol.h - state machine for 'jabber-like' protocols + * 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 + * + */ + +#ifndef XMLPROTOCOL_H +#define XMLPROTOCOL_H + +#include<qdom.h> +#include<qvaluelist.h> +#include"parser.h" + +#define NS_XML "http://www.w3.org/XML/1998/namespace" + +namespace XMPP +{ + class XmlProtocol + { + public: + enum Need { + NNotify, // need a data send and/or recv update + NCustom = 10 + }; + enum Event { + EError, // unrecoverable error, see errorCode for details + ESend, // data needs to be sent, use takeOutgoingData() + ERecvOpen, // breakpoint after root element open tag is received + EPeerClosed, // root element close tag received + EClosed, // finished closing + ECustom = 10 + }; + enum Error { + ErrParse, // there was an error parsing the xml + ErrCustom = 10 + }; + enum Notify { + NSend = 0x01, // need to know if data has been written + NRecv = 0x02 // need incoming data + }; + + XmlProtocol(); + virtual ~XmlProtocol(); + + virtual void reset(); + + // byte I/O for the stream + void addIncomingData(const QByteArray &); + QByteArray takeOutgoingData(); + void outgoingDataWritten(int); + + // advance the state machine + bool processStep(); + + // set these before returning from a step + int need, event, errorCode, notify; + + inline bool isIncoming() const { return incoming; } + QString xmlEncoding() const; + QString elementToString(const QDomElement &e, bool clip=false); + + class TransferItem + { + public: + TransferItem(); + TransferItem(const QString &str, bool sent, bool external=false); + TransferItem(const QDomElement &elem, bool sent, bool external=false); + + bool isSent; // else, received + bool isString; // else, is element + bool isExternal; // not owned by protocol + QString str; + QDomElement elem; + }; + QValueList<TransferItem> transferItemList; + void setIncomingAsExternal(); + + protected: + virtual QDomElement docElement()=0; + virtual void handleDocOpen(const Parser::Event &pe)=0; + virtual bool handleError()=0; + virtual bool handleCloseFinished()=0; + virtual bool stepAdvancesParser() const=0; + virtual bool stepRequiresElement() const; + virtual bool doStep(const QDomElement &e)=0; + virtual void itemWritten(int id, int size); + + // 'debug' + virtual void stringSend(const QString &s); + virtual void stringRecv(const QString &s); + virtual void elementSend(const QDomElement &e); + virtual void elementRecv(const QDomElement &e); + + void startConnect(); + void startAccept(); + bool close(); + int writeString(const QString &s, int id, bool external); + int writeElement(const QDomElement &e, int id, bool external, bool clip=false); + QByteArray resetStream(); + + private: + enum { SendOpen, RecvOpen, Open, Closing }; + class TrackItem + { + public: + enum Type { Raw, Close, Custom }; + int type, id, size; + }; + + bool incoming; + QDomDocument elemDoc; + QDomElement elem; + QString tagOpen, tagClose; + int state; + bool peerClosed; + bool closeWritten; + + Parser xml; + QByteArray outData; + QValueList<TrackItem> trackQueue; + + void init(); + int internalWriteData(const QByteArray &a, TrackItem::Type t, int id=-1); + int internalWriteString(const QString &s, TrackItem::Type t, int id=-1); + void sendTagOpen(); + void sendTagClose(); + bool baseStep(const Parser::Event &pe); + }; +} + +#endif |