diff --git a/protocols/jabber/libiris/CMakeLists.txt b/protocols/jabber/libiris/CMakeLists.txt index e4ed3acd0..1c9c08dd0 100644 --- a/protocols/jabber/libiris/CMakeLists.txt +++ b/protocols/jabber/libiris/CMakeLists.txt @@ -1,166 +1,154 @@ if(NOT WIN32) add_definitions(-fPIC) else(NOT WIN32) if(MINGW) add_definitions(-DWIN32) endif(MINGW) endif(NOT WIN32) include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/src ${CMAKE_CURRENT_SOURCE_DIR}/src/xmpp/base ${CMAKE_CURRENT_SOURCE_DIR}/src/xmpp/base ${CMAKE_CURRENT_SOURCE_DIR}/src/xmpp/jid ${CMAKE_CURRENT_SOURCE_DIR}/src/irisnet/noncore/cutestuff ${CMAKE_CURRENT_SOURCE_DIR}/src/xmpp/xmpp-im ${CMAKE_CURRENT_SOURCE_DIR}/src/xmpp/sasl ${CMAKE_CURRENT_SOURCE_DIR}/src/xmpp/zlib ${CMAKE_CURRENT_SOURCE_DIR}/src/xmpp/base64 ${CMAKE_CURRENT_SOURCE_DIR}/src/xmpp/xmpp-core ${CMAKE_CURRENT_SOURCE_DIR}/src/xmpp/jingle ${CMAKE_CURRENT_SOURCE_DIR}/src/jdns/include/jdns ${CMAKE_CURRENT_SOURCE_DIR}/src/irisnet/noncore ${CMAKE_CURRENT_SOURCE_DIR}/src/irisnet/noncore/legacy ${CMAKE_CURRENT_SOURCE_DIR}/src/irisnet/corelib ${CMAKE_CURRENT_SOURCE_DIR}/src/irisnet/appledns ${IDN_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR} ${DNSSD_INCLUDE_DIR} ) -qt_wrap_cpp(iris_kopete iris_MOC_SRCS - src/xmpp/xmpp-im/xmpp_client.h - src/xmpp/xmpp-core/xmpp.h - src/xmpp/xmpp-core/xmpp_clientstream.h - src/xmpp/xmpp-core/xmpp_stream.h - src/xmpp/xmpp-core/td.h - src/jdns/src/qjdns/qjdnsshared_p.h - src/jdns/include/jdns/qjdns.h - src/jdns/include/jdns/qjdnsshared.h - ) - set(iris_SRCS src/xmpp/base/randomnumbergenerator.cpp src/xmpp/base/timezone.cpp src/xmpp/jid/jid.cpp src/irisnet/noncore/cutestuff/httppoll.cpp src/irisnet/noncore/cutestuff/socks.cpp src/irisnet/noncore/cutestuff/bytestream.cpp src/irisnet/noncore/cutestuff/bsocket.cpp src/irisnet/noncore/cutestuff/httpconnect.cpp src/xmpp/xmpp-im/xmpp_discoitem.cpp src/xmpp/xmpp-im/client.cpp src/xmpp/xmpp-im/types.cpp src/xmpp/xmpp-im/xmpp_vcard.cpp src/xmpp/xmpp-im/xmpp_xmlcommon.cpp src/xmpp/xmpp-im/xmpp_ibb.cpp src/xmpp/xmpp-im/xmpp_xdata.cpp src/xmpp/xmpp-im/xmpp_task.cpp src/xmpp/xmpp-im/xmpp_features.cpp src/xmpp/xmpp-im/xmpp_discoinfotask.cpp src/xmpp/xmpp-im/xmpp_bitsofbinary.cpp src/xmpp/xmpp-im/xmpp_bytestream.cpp src/xmpp/xmpp-im/xmpp_caps.cpp src/xmpp/xmpp-im/s5b.cpp src/xmpp/xmpp-im/xmpp_tasks.cpp src/xmpp/xmpp-im/filetransfer.cpp src/xmpp/sasl/digestmd5proplist.cpp src/xmpp/sasl/digestmd5response.cpp src/xmpp/sasl/plainmessage.cpp src/xmpp/sasl/scramsha1message.cpp src/xmpp/sasl/scramsha1signature.cpp src/xmpp/sasl/scramsha1response.cpp src/xmpp/zlib/zlibcompressor.cpp src/xmpp/zlib/zlibdecompressor.cpp src/xmpp/xmpp-core/tlshandler.cpp src/xmpp/xmpp-core/xmpp_stanza.cpp src/xmpp/xmpp-core/stream.cpp src/xmpp/xmpp-core/securestream.cpp src/xmpp/xmpp-core/simplesasl.cpp src/xmpp/xmpp-core/xmlprotocol.cpp src/xmpp/xmpp-core/protocol.cpp src/xmpp/xmpp-core/sm.cpp src/xmpp/xmpp-core/compressionhandler.cpp src/xmpp/xmpp-core/parser.cpp src/xmpp/xmpp-core/connector.cpp src/irisnet/noncore/ice176.cpp src/irisnet/noncore/icecomponent.cpp src/irisnet/noncore/icetransport.cpp src/irisnet/noncore/iceturntransport.cpp src/irisnet/noncore/stunallocate.cpp src/irisnet/noncore/legacy/ndns.cpp src/irisnet/noncore/legacy/srvresolver.cpp src/irisnet/noncore/legacy/safedelete.cpp src/irisnet/noncore/legacy/servsock.cpp src/irisnet/noncore/icelocaltransport.cpp src/irisnet/noncore/stunmessage.cpp src/irisnet/noncore/stunbinding.cpp src/irisnet/noncore/stuntransaction.cpp src/irisnet/noncore/stuntypes.cpp src/irisnet/noncore/stunutil.cpp src/irisnet/noncore/processquit.cpp src/irisnet/noncore/turnclient.cpp src/irisnet/noncore/udpportreserver.cpp src/irisnet/corelib/netavailability.cpp src/irisnet/corelib/netnames_jdns.cpp src/irisnet/corelib/netnames.cpp src/irisnet/corelib/irisnetplugin.cpp src/irisnet/corelib/netinterface.cpp src/irisnet/corelib/objectsession.cpp src/irisnet/corelib/irisnetglobal.cpp src/jdns/src/jdns/jdns_util.c src/jdns/src/jdns/jdns_packet.c src/jdns/src/jdns/jdns_mdnsd.c src/jdns/src/jdns/jdns_sys.c src/jdns/src/jdns/jdns.c src/jdns/src/qjdns/qjdns_sock.cpp src/jdns/src/qjdns/qjdns.cpp src/jdns/src/qjdns/qjdnsshared.cpp ) if(WIN32) LIST(APPEND iris_SRCS src/irisnet/corelib/netinterface_win.cpp) else(WIN32) LIST(APPEND iris_SRCS src/irisnet/corelib/netinterface_unix.cpp) endif(WIN32) set(libiris_SRCS ${cutestuff_SRCS} ${iris_SRCS} - ${iris_MOC_SRCS} ) add_definitions(-DIRISNET_STATIC) add_definitions(-DJDNS_STATIC) if(CMAKE_COMPILER_IS_GNUCXX) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu90") endif(CMAKE_COMPILER_IS_GNUCXX) add_library(iris_kopete STATIC ${libiris_SRCS}) set(iris_kopete_libs Qt5::Core Qt5::Network Qt5::Xml Qt5::Gui Qt5::Widgets ${IDN_LIBRARIES} qca-qt5 ${ZLIB_LIBRARIES} ) if(WIN32) set(iris_kopete_libs ${iris_kopete_libs} ws2_32) endif(WIN32) # On Solaris, some of the name resolution functions are in libnsl; # this needs to be linked in if found. Copied from kdelibs. include(CheckLibraryExists) check_library_exists(nsl gethostbyname "" HAVE_NSL_LIBRARY) if(HAVE_NSL_LIBRARY) # This is probably Solaris, and libiris needs to link # to libnsl for gethostbyname set(iris_kopete_libs ${iris_kopete_libs} nsl) endif(HAVE_NSL_LIBRARY) target_link_libraries(iris_kopete ${iris_kopete_libs}) ########### install files ############### diff --git a/protocols/jabber/libiris/src/jdns/src/qjdns/qjdns.cpp b/protocols/jabber/libiris/src/jdns/src/qjdns/qjdns.cpp index 044f77a9c..c26301131 100644 --- a/protocols/jabber/libiris/src/jdns/src/qjdns/qjdns.cpp +++ b/protocols/jabber/libiris/src/jdns/src/qjdns/qjdns.cpp @@ -1,1016 +1,1018 @@ /* * Copyright (C) 2005-2008 Justin Karneges * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "qjdns_p.h" #include #include "qjdns_sock.h" // for fprintf #include // safeobj stuff, from qca static void releaseAndDeleteLater(QObject *owner, QObject *obj) { obj->disconnect(owner); obj->setParent(0); obj->deleteLater(); } SafeTimer::SafeTimer(QObject *parent) : QObject(parent) { t = new QTimer(this); connect(t, SIGNAL(timeout()), SIGNAL(timeout())); } SafeTimer::~SafeTimer() { releaseAndDeleteLater(this, t); } int SafeTimer::interval() const { return t->interval(); } bool SafeTimer::isActive() const { return t->isActive(); } bool SafeTimer::isSingleShot() const { return t->isSingleShot(); } void SafeTimer::setInterval(int msec) { t->setInterval(msec); } void SafeTimer::setSingleShot(bool singleShot) { t->setSingleShot(singleShot); } int SafeTimer::timerId() const { return t->timerId(); } void SafeTimer::start(int msec) { t->start(msec); } void SafeTimer::start() { t->start(); } void SafeTimer::stop() { t->stop(); } static jdns_string_t *qt2str(const QByteArray &in) { jdns_string_t *out = jdns_string_new(); jdns_string_set(out, (const unsigned char *)in.data(), in.size()); return out; } static QByteArray str2qt(const jdns_string_t *in) { return QByteArray((const char *)in->data, in->size); } static void qt2addr_set(jdns_address_t *addr, const QHostAddress &host) { if(host.protocol() == QAbstractSocket::IPv6Protocol) jdns_address_set_ipv6(addr, host.toIPv6Address().c); else jdns_address_set_ipv4(addr, host.toIPv4Address()); } static jdns_address_t *qt2addr(const QHostAddress &host) { jdns_address_t *addr = jdns_address_new(); qt2addr_set(addr, host); return addr; } static QHostAddress addr2qt(const jdns_address_t *addr) { if(addr->isIpv6) return QHostAddress(addr->addr.v6); else return QHostAddress(addr->addr.v4); } static QJDns::Record import_record(const jdns_rr_t *in) { QJDns::Record out; out.owner = QByteArray((const char *)in->owner); out.ttl = in->ttl; out.type = in->type; out.rdata = QByteArray((const char *)in->rdata, in->rdlength); // known if(in->haveKnown) { int type = in->type; if(type == QJDns::A || type == QJDns::Aaaa) { out.haveKnown = true; out.address = addr2qt(in->data.address); } else if(type == QJDns::Mx) { out.haveKnown = true; out.name = QByteArray((const char *)in->data.server->name); out.priority = in->data.server->priority; } else if(type == QJDns::Srv) { out.haveKnown = true; out.name = QByteArray((const char *)in->data.server->name); out.priority = in->data.server->priority; out.weight = in->data.server->weight; out.port = in->data.server->port; } else if(type == QJDns::Cname || type == QJDns::Ptr || type == QJDns::Ns) { out.haveKnown = true; out.name = QByteArray((const char *)in->data.name); } else if(type == QJDns::Txt) { out.haveKnown = true; out.texts.clear(); for(int n = 0; n < in->data.texts->count; ++n) out.texts += str2qt(in->data.texts->item[n]); } else if(type == QJDns::Hinfo) { out.haveKnown = true; out.cpu = str2qt(in->data.hinfo.cpu); out.os = str2qt(in->data.hinfo.os); } } return out; } static jdns_rr_t *export_record(const QJDns::Record &in) { jdns_rr_t *out = jdns_rr_new(); jdns_rr_set_owner(out, (const unsigned char *)in.owner.data()); out->ttl = in.ttl; // if we have known, use that if(in.haveKnown) { int type = in.type; if(type == QJDns::A) { jdns_address_t *addr = qt2addr(in.address); jdns_rr_set_A(out, addr); jdns_address_delete(addr); } else if(type == QJDns::Aaaa) { jdns_address_t *addr = qt2addr(in.address); jdns_rr_set_AAAA(out, addr); jdns_address_delete(addr); } else if(type == QJDns::Mx) { jdns_rr_set_MX(out, (const unsigned char *)in.name.data(), in.priority); } else if(type == QJDns::Srv) { jdns_rr_set_SRV(out, (const unsigned char *)in.name.data(), in.port, in.priority, in.weight); } else if(type == QJDns::Cname) { jdns_rr_set_CNAME(out, (const unsigned char *)in.name.data()); } else if(type == QJDns::Ptr) { jdns_rr_set_PTR(out, (const unsigned char *)in.name.data()); } else if(type == QJDns::Txt) { jdns_stringlist_t *list = jdns_stringlist_new(); for(int n = 0; n < in.texts.count(); ++n) { jdns_string_t *str = qt2str(in.texts[n]); jdns_stringlist_append(list, str); jdns_string_delete(str); } jdns_rr_set_TXT(out, list); jdns_stringlist_delete(list); } else if(type == QJDns::Hinfo) { jdns_string_t *cpu = qt2str(in.cpu); jdns_string_t *os = qt2str(in.os); jdns_rr_set_HINFO(out, cpu, os); jdns_string_delete(cpu); jdns_string_delete(os); } else if(type == QJDns::Ns) { jdns_rr_set_NS(out, (const unsigned char *)in.name.data()); } } else jdns_rr_set_record(out, in.type, (const unsigned char *)in.rdata.data(), in.rdata.size()); return out; } //---------------------------------------------------------------------------- // QJDns::NameServer //---------------------------------------------------------------------------- QJDns::NameServer::NameServer() { port = JDNS_UNICAST_PORT; } //---------------------------------------------------------------------------- // QJDns::Record //---------------------------------------------------------------------------- QJDns::Record::Record() { ttl = 0; type = -1; haveKnown = false; } bool QJDns::Record::verify() const { jdns_rr_t *rr = export_record(*this); int ok = jdns_rr_verify(rr); jdns_rr_delete(rr); return (ok ? true : false); } //---------------------------------------------------------------------------- // QJDns //---------------------------------------------------------------------------- static int my_srand_done = 0; static void my_srand() { if(my_srand_done) return; // lame attempt at randomizing without srand int count = ::time(NULL) % 128; for(int n = 0; n < count; ++n) rand(); my_srand_done = 1; } QJDns::Private::Private(QJDns *_q) : QObject(_q) , q(_q) , stepTrigger(this) , debugTrigger(this) , stepTimeout(this) , pErrors(0) , pPublished(0) , pResponses(0) { sess = 0; shutting_down = false; new_debug_strings = false; pending = 0; connect(&stepTrigger, SIGNAL(timeout()), SLOT(doNextStepSlot())); stepTrigger.setSingleShot(true); connect(&debugTrigger, SIGNAL(timeout()), SLOT(doDebug())); debugTrigger.setSingleShot(true); connect(&stepTimeout, SIGNAL(timeout()), SLOT(st_timeout())); stepTimeout.setSingleShot(true); my_srand(); clock.start(); } QJDns::Private::~Private() { cleanup(); } void QJDns::Private::cleanup() { if(sess) { jdns_session_delete(sess); sess = 0; } shutting_down = false; pending = 0; // it is safe to delete the QUdpSocket objects here without // deleteLater, since this code path never occurs when // a signal from those objects is on the stack qDeleteAll(socketForHandle); socketForHandle.clear(); handleForSocket.clear(); stepTrigger.stop(); stepTimeout.stop(); need_handle = 0; } bool QJDns::Private::init(QJDns::Mode _mode, const QHostAddress &address) { mode = _mode; jdns_callbacks_t callbacks; callbacks.app = this; callbacks.time_now = cb_time_now; callbacks.rand_int = cb_rand_int; callbacks.debug_line = cb_debug_line; callbacks.udp_bind = cb_udp_bind; callbacks.udp_unbind = cb_udp_unbind; callbacks.udp_read = cb_udp_read; callbacks.udp_write = cb_udp_write; sess = jdns_session_new(&callbacks); jdns_set_hold_ids_enabled(sess, 1); next_handle = 1; need_handle = false; int ret; jdns_address_t *baddr = qt2addr(address); if(mode == Unicast) { ret = jdns_init_unicast(sess, baddr, 0); } else { jdns_address_t *maddr; if(address.protocol() == QAbstractSocket::IPv6Protocol) maddr = jdns_address_multicast6_new(); else maddr = jdns_address_multicast4_new(); ret = jdns_init_multicast(sess, baddr, JDNS_MULTICAST_PORT, maddr); jdns_address_delete(maddr); } jdns_address_delete(baddr); if(!ret) { jdns_session_delete(sess); sess = 0; return false; } return true; } void QJDns::Private::setNameServers(const QList &nslist) { jdns_nameserverlist_t *addrs = jdns_nameserverlist_new(); for(int n = 0; n < nslist.count(); ++n) { jdns_address_t *addr = qt2addr(nslist[n].address); jdns_nameserverlist_append(addrs, addr, nslist[n].port); jdns_address_delete(addr); } jdns_set_nameservers(sess, addrs); jdns_nameserverlist_delete(addrs); } void QJDns::Private::process() { if(!stepTrigger.isActive()) { stepTimeout.stop(); stepTrigger.start(); } } void QJDns::Private::processDebug() { new_debug_strings = true; if(!debugTrigger.isActive()) debugTrigger.start(); } void QJDns::Private::doNextStep() { if(shutting_down && complete_shutdown) { cleanup(); emit q->shutdownFinished(); return; } QPointer self = this; int ret = jdns_step(sess); QList errors; QList published; QList responses; bool finish_shutdown = false; pErrors = &errors; pPublished = &published; pResponses = &responses; while(1) { jdns_event_t *e = jdns_next_event(sess); if(!e) break; if(e->type == JDNS_EVENT_SHUTDOWN) { finish_shutdown = true; } else if(e->type == JDNS_EVENT_PUBLISH) { if(e->status != JDNS_STATUS_SUCCESS) { QJDns::Error error; if(e->status == JDNS_STATUS_CONFLICT) error = QJDns::ErrorConflict; else error = QJDns::ErrorGeneric; LateError le; le.source_type = 1; le.id = e->id; le.error = error; errors += le; } else { published += e->id; } } else if(e->type == JDNS_EVENT_RESPONSE) { if(e->status != JDNS_STATUS_SUCCESS) { QJDns::Error error; if(e->status == JDNS_STATUS_NXDOMAIN) error = QJDns::ErrorNXDomain; else if(e->status == JDNS_STATUS_TIMEOUT) error = QJDns::ErrorTimeout; else error = QJDns::ErrorGeneric; LateError le; le.source_type = 0; le.id = e->id; le.error = error; errors += le; } else { QJDns::Response out_response; for(int n = 0; n < e->response->answerCount; ++n) out_response.answerRecords += import_record(e->response->answerRecords[n]); LateResponse lr; lr.id = e->id; lr.response = out_response; if(mode == Unicast) lr.do_cancel = true; else lr.do_cancel = false; responses += lr; } } jdns_event_delete(e); } if(ret & JDNS_STEP_TIMER) stepTimeout.start(jdns_next_timer(sess)); else stepTimeout.stop(); need_handle = (ret & JDNS_STEP_HANDLE); // read the lists safely enough so that items can be deleted // behind our back while(!errors.isEmpty()) { LateError i = errors.takeFirst(); if(i.source_type == 0) jdns_cancel_query(sess, i.id); else jdns_cancel_publish(sess, i.id); emit q->error(i.id, i.error); if(!self) return; } while(!published.isEmpty()) { int i = published.takeFirst(); emit q->published(i); if(!self) return; } while(!responses.isEmpty()) { LateResponse i = responses.takeFirst(); if(i.do_cancel) jdns_cancel_query(sess, i.id); emit q->resultsReady(i.id, i.response); if(!self) return; } if(finish_shutdown) { // if we have pending udp packets to write, stick around if(pending > 0) { pending_wait = true; } else { complete_shutdown = true; process(); } } pErrors = 0; pPublished = 0; pResponses = 0; } void QJDns::Private::removeCancelled(int id) { if(pErrors) { for(int n = 0; n < pErrors->count(); ++n) { if(pErrors->at(n).id == id) { pErrors->removeAt(n); --n; // adjust position } } } if(pPublished) { for(int n = 0; n < pPublished->count(); ++n) { if(pPublished->at(n) == id) { pPublished->removeAt(n); --n; // adjust position } } } if(pResponses) { for(int n = 0; n < pResponses->count(); ++n) { if(pResponses->at(n).id == id) { pResponses->removeAt(n); --n; // adjust position } } } } void QJDns::Private::udp_readyRead() { QUdpSocket *sock = (QUdpSocket *)sender(); int handle = handleForSocket.value(sock); if(need_handle) { jdns_set_handle_readable(sess, handle); process(); } else { // eat packet QByteArray buf(4096, 0); QHostAddress from_addr; quint16 from_port; sock->readDatagram(buf.data(), buf.size(), &from_addr, &from_port); } } void QJDns::Private::udp_bytesWritten(qint64) { if(pending > 0) { --pending; if(shutting_down && pending_wait && pending == 0) { pending_wait = false; complete_shutdown = true; process(); } } } void QJDns::Private::st_timeout() { doNextStep(); } void QJDns::Private::doNextStepSlot() { doNextStep(); } void QJDns::Private::doDebug() { if(new_debug_strings) { new_debug_strings = false; if(!debug_strings.isEmpty()) emit q->debugLinesReady(); } } // jdns callbacks int QJDns::Private::cb_time_now(jdns_session_t *, void *app) { QJDns::Private *self = (QJDns::Private *)app; return self->clock.elapsed(); } int QJDns::Private::cb_rand_int(jdns_session_t *, void *) { return rand() % 65536; } void QJDns::Private::cb_debug_line(jdns_session_t *, void *app, const char *str) { QJDns::Private *self = (QJDns::Private *)app; self->debug_strings += QString::fromLatin1(str); self->processDebug(); } int QJDns::Private::cb_udp_bind(jdns_session_t *, void *app, const jdns_address_t *addr, int port, const jdns_address_t *maddr) { QJDns::Private *self = (QJDns::Private *)app; // we always pass non-null to jdns_init, so this should be a valid address QHostAddress host = addr2qt(addr); QUdpSocket *sock = new QUdpSocket(self); self->connect(sock, SIGNAL(readyRead()), SLOT(udp_readyRead())); // use queued for bytesWritten, since qt is evil and emits before writeDatagram returns qRegisterMetaType("qint64"); self->connect(sock, SIGNAL(bytesWritten(qint64)), SLOT(udp_bytesWritten(qint64)), Qt::QueuedConnection); QUdpSocket::BindMode mode; mode |= QUdpSocket::ShareAddress; mode |= QUdpSocket::ReuseAddressHint; if(!sock->bind(host, port, mode)) { delete sock; return 0; } if(maddr) { int sd = sock->socketDescriptor(); bool ok; int errorCode; if(maddr->isIpv6) ok = qjdns_sock_setMulticast6(sd, maddr->addr.v6, &errorCode); else ok = qjdns_sock_setMulticast4(sd, maddr->addr.v4, &errorCode); if(!ok) { delete sock; self->debug_strings += QString("failed to setup multicast on the socket (errorCode=%1)").arg(errorCode); self->processDebug(); return 0; } if(maddr->isIpv6) { qjdns_sock_setTTL6(sd, 255); qjdns_sock_setIPv6Only(sd); } else qjdns_sock_setTTL4(sd, 255); } int handle = self->next_handle++; self->socketForHandle.insert(handle, sock); self->handleForSocket.insert(sock, handle); return handle; } void QJDns::Private::cb_udp_unbind(jdns_session_t *, void *app, int handle) { QJDns::Private *self = (QJDns::Private *)app; QUdpSocket *sock = self->socketForHandle.value(handle); if(!sock) return; self->socketForHandle.remove(handle); self->handleForSocket.remove(sock); delete sock; } int QJDns::Private::cb_udp_read(jdns_session_t *, void *app, int handle, jdns_address_t *addr, int *port, unsigned char *buf, int *bufsize) { QJDns::Private *self = (QJDns::Private *)app; QUdpSocket *sock = self->socketForHandle.value(handle); if(!sock) return 0; // nothing to read? if(!sock->hasPendingDatagrams()) return 0; QHostAddress from_addr; quint16 from_port; int ret = sock->readDatagram((char *)buf, *bufsize, &from_addr, &from_port); if(ret == -1) return 0; qt2addr_set(addr, from_addr); *port = (int)from_port; *bufsize = ret; return 1; } int QJDns::Private::cb_udp_write(jdns_session_t *, void *app, int handle, const jdns_address_t *addr, int port, unsigned char *buf, int bufsize) { QJDns::Private *self = (QJDns::Private *)app; QUdpSocket *sock = self->socketForHandle.value(handle); if(!sock) return 0; QHostAddress host = addr2qt(addr); int ret = sock->writeDatagram((const char *)buf, bufsize, host, port); if(ret == -1) { // this can happen if the datagram to send is too big. i'm not sure what else // may cause this. if we return 0, then jdns may try to resend the packet, // which might not work if it is too large (causing the same error over and // over). we'll return success to jdns, so the result is as if the packet // was dropped. return 1; } ++self->pending; return 1; } QJDns::QJDns(QObject *parent) :QObject(parent) { d = new Private(this); } QJDns::~QJDns() { delete d; } bool QJDns::init(Mode mode, const QHostAddress &address) { return d->init(mode, address); } void QJDns::shutdown() { d->shutting_down = true; d->pending_wait = false; d->complete_shutdown = false; jdns_shutdown(d->sess); d->process(); } QStringList QJDns::debugLines() { QStringList tmp = d->debug_strings; d->debug_strings.clear(); return tmp; } QJDns::SystemInfo QJDns::systemInfo() { SystemInfo out; jdns_dnsparams_t *params = jdns_system_dnsparams(); for(int n = 0; n < params->nameservers->count; ++n) { NameServer ns; ns.address = addr2qt(params->nameservers->item[n]->address); out.nameServers += ns; } for(int n = 0; n < params->domains->count; ++n) out.domains += str2qt(params->domains->item[n]); for(int n = 0; n < params->hosts->count; ++n) { DnsHost h; h.name = str2qt(params->hosts->item[n]->name); h.address = addr2qt(params->hosts->item[n]->address); out.hosts += h; } jdns_dnsparams_delete(params); return out; } #define PROBE_BASE 20000 #define PROBE_RANGE 100 QHostAddress QJDns::detectPrimaryMulticast(const QHostAddress &address) { my_srand(); QUdpSocket *sock = new QUdpSocket; QUdpSocket::BindMode mode; mode |= QUdpSocket::ShareAddress; mode |= QUdpSocket::ReuseAddressHint; int port = -1; for(int n = 0; n < PROBE_RANGE; ++n) { if(sock->bind(address, PROBE_BASE + n, mode)) { port = PROBE_BASE + n; break; } } if(port == -1) { delete sock; return QHostAddress(); } jdns_address_t *a; if(address.protocol() == QAbstractSocket::IPv6Protocol) a = jdns_address_multicast6_new(); else a = jdns_address_multicast4_new(); QHostAddress maddr = addr2qt(a); jdns_address_delete(a); if(address.protocol() == QAbstractSocket::IPv6Protocol) { int x; if(!qjdns_sock_setMulticast6(sock->socketDescriptor(), maddr.toIPv6Address().c, &x)) { delete sock; return QHostAddress(); } qjdns_sock_setTTL6(sock->socketDescriptor(), 0); } else { int x; if(!qjdns_sock_setMulticast4(sock->socketDescriptor(), maddr.toIPv4Address(), &x)) { delete sock; return QHostAddress(); } qjdns_sock_setTTL4(sock->socketDescriptor(), 0); } QHostAddress result; QByteArray out(128, 0); for(int n = 0; n < out.size(); ++n) out[n] = rand(); if(sock->writeDatagram(out.data(), out.size(), maddr, port) == -1) { delete sock; return QHostAddress(); } while(1) { if(!sock->waitForReadyRead(1000)) { fprintf(stderr, "QJDns::detectPrimaryMulticast: timeout while checking %s\n", qPrintable(address.toString())); delete sock; return QHostAddress(); } QByteArray in(128, 0); QHostAddress from_addr; quint16 from_port; int ret = sock->readDatagram(in.data(), in.size(), &from_addr, &from_port); if(ret == -1) { delete sock; return QHostAddress(); } if(from_port != port) continue; in.resize(ret); if(in != out) continue; result = from_addr; break; } delete sock; return result; } void QJDns::setNameServers(const QList &list) { d->setNameServers(list); } int QJDns::queryStart(const QByteArray &name, int type) { int id = jdns_query(d->sess, (const unsigned char *)name.data(), type); d->process(); return id; } void QJDns::queryCancel(int id) { jdns_cancel_query(d->sess, id); d->removeCancelled(id); d->process(); } int QJDns::publishStart(PublishMode m, const Record &record) { jdns_rr_t *rr = export_record(record); int pubmode; if(m == QJDns::Unique) pubmode = JDNS_PUBLISH_UNIQUE; else pubmode = JDNS_PUBLISH_SHARED; int id = jdns_publish(d->sess, pubmode, rr); jdns_rr_delete(rr); d->process(); return id; } void QJDns::publishUpdate(int id, const Record &record) { jdns_rr_t *rr = export_record(record); jdns_update_publish(d->sess, id, rr); jdns_rr_delete(rr); d->process(); } void QJDns::publishCancel(int id) { jdns_cancel_publish(d->sess, id); d->removeCancelled(id); d->process(); } + +#include "moc_qjdns.cpp" diff --git a/protocols/jabber/libiris/src/jdns/src/qjdns/qjdnsshared.cpp b/protocols/jabber/libiris/src/jdns/src/qjdns/qjdnsshared.cpp index c1b5c1965..e16729c10 100644 --- a/protocols/jabber/libiris/src/jdns/src/qjdns/qjdnsshared.cpp +++ b/protocols/jabber/libiris/src/jdns/src/qjdns/qjdnsshared.cpp @@ -1,1208 +1,1211 @@ /* * Copyright (C) 2006-2008 Justin Karneges * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ // Note: QJDnsShared supports multiple interfaces for multicast, but only one // for IPv4 and one for IPv6. Sharing multiple interfaces of the same IP // version for multicast is unfortunately not possible without reworking // the jdns subsystem. // // The reason for this limitation is that in order to do multi-interface // multicast, you have to do a single bind to Any, and then use special // functions to determine which interface a packet came from and to // specify which interface a packet should go out on. Again this is just // not possible with the current system and the assumptions made by jdns. // Note: When quering against multiple interfaces with multicast, it is // possible that different answers for a unique record may be reported // on each interface. We don't do anything about this. #include "qjdnsshared_p.h" // for caching system info class SystemInfoCache { public: QJDns::SystemInfo info; QTime time; }; Q_GLOBAL_STATIC(QMutex, jdnsshared_mutex) Q_GLOBAL_STATIC(SystemInfoCache, jdnsshared_infocache) static QJDns::SystemInfo get_sys_info() { QMutexLocker locker(jdnsshared_mutex()); SystemInfoCache *c = jdnsshared_infocache(); // cache info for 1/2 second, enough to prevent re-reading of sys // info 20 times because of all the different resolves if(c->time.isNull() || c->time.elapsed() >= 500) { c->info = QJDns::systemInfo(); c->time.start(); } return c->info; } static bool domainCompare(const QByteArray &a, const QByteArray &b) { return (qstricmp(a.data(), b.data()) == 0) ? true: false; } // adapted from jdns_mdnsd.c, _a_match() static bool matchRecordExceptTtl(const QJDns::Record &a, const QJDns::Record &b) { if(a.type != b.type || !domainCompare(a.owner, b.owner)) return false; if(a.type == QJDns::Srv) { if(domainCompare(a.name, b.name) && a.port == b.port && a.priority == b.priority && a.weight == b.weight) { return true; } } else if(a.type == QJDns::Ptr || a.type == QJDns::Ns || a.type == QJDns::Cname) { if(domainCompare(a.name, b.name)) return true; } else if(a.rdata == b.rdata) return true; return false; } static void getHex(unsigned char in, char *hi, char *lo) { QString str; str.sprintf("%02x", in); *hi = str[0].toLatin1(); *lo = str[1].toLatin1(); } static QByteArray getDec(int in) { return QString::number(in).toLatin1(); } static QByteArray makeReverseName(const QHostAddress &addr) { QByteArray out; if(addr.protocol() == QAbstractSocket::IPv6Protocol) { Q_IPV6ADDR raw = addr.toIPv6Address(); for(int n = 0; n < 32; ++n) { char hi, lo; getHex(raw.c[31 - n], &hi, &lo); out += lo; out += '.'; out += hi; out += '.'; } out += "ip6.arpa."; } else { quint32 rawi = addr.toIPv4Address(); int raw[4]; raw[0] = (rawi >> 24) & 0xff; raw[1] = (rawi >> 16) & 0xff; raw[2] = (rawi >> 8) & 0xff; raw[3] = rawi & 0xff; for(int n = 0; n < 4; ++n) { out += getDec(raw[3 - n]); out += '.'; } out += "in-addr.arpa."; } return out; } // adapted from qHash static inline uint qHash(const Handle &key) { uint h1 = ::qHash(key.jdns); uint h2 = ::qHash(key.id); return ((h1 << 16) | (h1 >> 16)) ^ h2; } //---------------------------------------------------------------------------- // JDnsShutdown //---------------------------------------------------------------------------- void JDnsShutdownAgent::start() { QMetaObject::invokeMethod(this, "started", Qt::QueuedConnection); } JDnsShutdownWorker::JDnsShutdownWorker(const QList &_list) : QObject(0), list(_list) { foreach(QJDnsShared *i, list) { connect(i, SIGNAL(shutdownFinished()), SLOT(jdns_shutdownFinished())); i->shutdown(); // MUST support DOR-DS, and it does } } void JDnsShutdownWorker::jdns_shutdownFinished() { QJDnsShared *i = (QJDnsShared *)sender(); list.removeAll(i); delete i; if(list.isEmpty()) emit finished(); } void JDnsShutdown::waitForShutdown(const QList &_list) { list = _list; phase = 0; m.lock(); start(); w.wait(&m); foreach(QJDnsShared *i, list) { i->setParent(0); i->moveToThread(this); } phase = 1; agent->start(); wait(); } void JDnsShutdown::run() { m.lock(); agent = new JDnsShutdownAgent; connect(agent, SIGNAL(started()), SLOT(agent_started()), Qt::DirectConnection); agent->start(); exec(); delete agent; } void JDnsShutdown::agent_started() { if(phase == 0) { w.wakeOne(); m.unlock(); } else { worker = new JDnsShutdownWorker(list); connect(worker, SIGNAL(finished()), SLOT(worker_finished()), Qt::DirectConnection); } } void JDnsShutdown::worker_finished() { delete worker; worker = 0; quit(); } //---------------------------------------------------------------------------- // QJDnsSharedDebug //---------------------------------------------------------------------------- QJDnsSharedDebugPrivate::QJDnsSharedDebugPrivate(QJDnsSharedDebug *_q) : QObject(_q) , q(_q) { dirty = false; } void QJDnsSharedDebugPrivate::addDebug(const QString &name, const QStringList &_lines) { if(!_lines.isEmpty()) { QMutexLocker locker(&m); for(int n = 0; n < _lines.count(); ++n) lines += name + ": " + _lines[n]; if(!dirty) { dirty = true; QMetaObject::invokeMethod(this, "doUpdate", Qt::QueuedConnection); } } } void QJDnsSharedDebugPrivate::doUpdate() { { QMutexLocker locker(&m); if(!dirty) return; } emit q->readyRead(); } QJDnsSharedDebug::QJDnsSharedDebug(QObject *parent) :QObject(parent) { d = new QJDnsSharedDebugPrivate(this); } QJDnsSharedDebug::~QJDnsSharedDebug() { delete d; } QStringList QJDnsSharedDebug::readDebugLines() { QMutexLocker locker(&d->m); QStringList tmplines = d->lines; d->lines.clear(); d->dirty = false; return tmplines; } //---------------------------------------------------------------------------- // QJDnsSharedRequest //---------------------------------------------------------------------------- QJDnsSharedPrivate::QJDnsSharedPrivate(QJDnsShared *_q) : QObject(_q) , q(_q) { } QJDnsSharedRequest *QJDnsSharedPrivate::findRequest(QJDns *jdns, int id) const { Handle h(jdns, id); return requestForHandle.value(h); } void QJDnsSharedPrivate::jdns_link(QJDns *jdns) { connect(jdns, SIGNAL(resultsReady(int,QJDns::Response)), SLOT(jdns_resultsReady(int,QJDns::Response))); connect(jdns, SIGNAL(published(int)), SLOT(jdns_published(int))); connect(jdns, SIGNAL(error(int,QJDns::Error)), SLOT(jdns_error(int,QJDns::Error))); connect(jdns, SIGNAL(shutdownFinished()), SLOT(jdns_shutdownFinished())); connect(jdns, SIGNAL(debugLinesReady()), SLOT(jdns_debugLinesReady())); } int QJDnsSharedPrivate::getNewIndex() const { // find lowest unused value for(int n = 0;; ++n) { bool found = false; foreach(Instance *i, instances) { if(i->index == n) { found = true; break; } } if(!found) return n; } } void QJDnsSharedPrivate::addDebug(int index, const QString &line) { if(db) db->d->addDebug(dbname + QString::number(index), QStringList() << line); } void QJDnsSharedPrivate::doDebug(QJDns *jdns, int index) { QStringList lines = jdns->debugLines(); if(db) db->d->addDebug(dbname + QString::number(index), lines); } QJDnsSharedPrivate::PreprocessMode QJDnsSharedPrivate::determinePpMode(const QJDns::Record &in) { // Note: since our implementation only allows 1 ipv4 and 1 ipv6 // interface to exist, it is safe to publish both kinds of // records on both interfaces, with the same values. For // example, an A record can be published on both interfaces, // with the value set to the ipv4 interface. If we supported // multiple ipv4 interfaces, then this wouldn't work, because // we wouldn't know which value to use for the A record when // publishing on the ipv6 interface. // publishing our own IP address? null address means the user // wants us to fill in the blank with our address. if((in.type == QJDns::Aaaa || in.type == QJDns::A) && in.address.isNull()) { return FillInAddress; } // publishing our own reverse lookup? partial owner means // user wants us to fill in the rest. else if(in.type == QJDns::Ptr && in.owner == ".ip6.arpa.") { return FillInPtrOwner6; } else if(in.type == QJDns::Ptr && in.owner == ".in-addr.arpa.") { return FillInPtrOwner4; } return None; } QJDns::Record QJDnsSharedPrivate::manipulateRecord(const QJDns::Record &in, PreprocessMode ppmode, bool *modified) { if(ppmode == FillInAddress) { QJDns::Record out = in; if(in.type == QJDns::Aaaa) { // are we operating on ipv6? foreach(Instance *i, instances) { if(i->addr.protocol() == QAbstractSocket::IPv6Protocol) { if(modified && !(out.address == i->addr)) *modified = true; out.address = i->addr; break; } } } else // A { // are we operating on ipv4? foreach(Instance *i, instances) { if(i->addr.protocol() == QAbstractSocket::IPv4Protocol) { if(modified && !(out.address == i->addr)) *modified = true; out.address = i->addr; break; } } } return out; } else if(ppmode == FillInPtrOwner6) { QJDns::Record out = in; // are we operating on ipv6? foreach(Instance *i, instances) { if(i->addr.protocol() == QAbstractSocket::IPv6Protocol) { QByteArray newOwner = makeReverseName(i->addr); if(modified && !(out.owner == newOwner)) *modified = true; out.owner = newOwner; break; } } return out; } else if(ppmode == FillInPtrOwner4) { QJDns::Record out = in; // are we operating on ipv4? foreach(Instance *i, instances) { if(i->addr.protocol() == QAbstractSocket::IPv4Protocol) { QByteArray newOwner = makeReverseName(i->addr); if(modified && !(out.owner == newOwner)) *modified = true; out.owner = newOwner; break; } } return out; } if(modified) *modified = false; return in; } void QJDnsSharedPrivate::late_shutdown() { shutting_down = false; emit q->shutdownFinished(); } QJDnsSharedRequestPrivate::QJDnsSharedRequestPrivate(QJDnsSharedRequest *_q) : QObject(_q), q(_q), lateTimer(this) { connect(&lateTimer, SIGNAL(timeout()), SLOT(lateTimer_timeout())); } void QJDnsSharedRequestPrivate::resetSession() { name = QByteArray(); pubrecord = QJDns::Record(); handles.clear(); published.clear(); queryCache.clear(); } void QJDnsSharedRequestPrivate::lateTimer_timeout() { emit q->resultsReady(); } QJDnsSharedRequest::QJDnsSharedRequest(QJDnsShared *jdnsShared, QObject *parent) :QObject(parent) { d = new QJDnsSharedRequestPrivate(this); d->jsp = jdnsShared->d; } QJDnsSharedRequest::~QJDnsSharedRequest() { cancel(); delete d; } QJDnsSharedRequest::Type QJDnsSharedRequest::type() { return d->type; } void QJDnsSharedRequest::query(const QByteArray &name, int type) { cancel(); d->jsp->queryStart(this, name, type); } void QJDnsSharedRequest::publish(QJDns::PublishMode m, const QJDns::Record &record) { cancel(); d->jsp->publishStart(this, m, record); } void QJDnsSharedRequest::publishUpdate(const QJDns::Record &record) { // only allowed to update if we have an active publish if(!d->handles.isEmpty() && d->type == Publish) d->jsp->publishUpdate(this, record); } void QJDnsSharedRequest::cancel() { d->lateTimer.stop(); if(!d->handles.isEmpty()) { if(d->type == Query) d->jsp->queryCancel(this); else d->jsp->publishCancel(this); } d->resetSession(); } bool QJDnsSharedRequest::success() const { return d->success; } QJDnsSharedRequest::Error QJDnsSharedRequest::error() const { return d->error; } QList QJDnsSharedRequest::results() const { return d->results; } //---------------------------------------------------------------------------- // QJDnsShared //---------------------------------------------------------------------------- QJDnsShared::QJDnsShared(Mode mode, QObject *parent) :QObject(parent) { d = new QJDnsSharedPrivate(this); d->mode = mode; d->shutting_down = false; d->db = 0; } QJDnsShared::~QJDnsShared() { foreach(QJDnsSharedPrivate::Instance *i, d->instances) { delete i->jdns; delete i; } delete d; } void QJDnsShared::setDebug(QJDnsSharedDebug *db, const QString &name) { d->db = db; d->dbname = name; } bool QJDnsShared::addInterface(const QHostAddress &addr) { return d->addInterface(addr); } void QJDnsShared::removeInterface(const QHostAddress &addr) { d->removeInterface(addr); } void QJDnsShared::shutdown() { d->shutting_down = true; if(!d->instances.isEmpty()) { foreach(QJDnsSharedPrivate::Instance *i, d->instances) i->jdns->shutdown(); } else QMetaObject::invokeMethod(d, "late_shutdown", Qt::QueuedConnection); } QList QJDnsShared::domains() { return get_sys_info().domains; } void QJDnsShared::waitForShutdown(const QList &instances) { JDnsShutdown s; s.waitForShutdown(instances); } bool QJDnsSharedPrivate::addInterface(const QHostAddress &addr) { if(shutting_down) return false; // make sure we don't have this one already foreach(Instance *i, instances) { if(i->addr == addr) return false; } int index = getNewIndex(); addDebug(index, QString("attempting to use interface %1").arg(addr.toString())); QJDns *jdns; if(mode == QJDnsShared::UnicastInternet || mode == QJDnsShared::UnicastLocal) { jdns = new QJDns(this); jdns_link(jdns); if(!jdns->init(QJDns::Unicast, addr)) { doDebug(jdns, index); delete jdns; return false; } if(mode == QJDnsShared::UnicastLocal) { QJDns::NameServer host; if(addr.protocol() == QAbstractSocket::IPv6Protocol) host.address = QHostAddress("FF02::FB"); else host.address = QHostAddress("224.0.0.251"); host.port = 5353; jdns->setNameServers(QList() << host); } } else // Multicast { // only one multicast interface allowed per IP protocol version. // this is because we bind to INADDR_ANY. bool have_v6 = false; bool have_v4 = false; foreach(Instance *i, instances) { if(i->addr.protocol() == QAbstractSocket::IPv6Protocol) have_v6 = true; else have_v4 = true; } bool is_v6 = (addr.protocol() == QAbstractSocket::IPv6Protocol) ? true : false; if(is_v6 && have_v6) { addDebug(index, "already have an ipv6 interface"); return false; } if(!is_v6 && have_v4) { addDebug(index, "already have an ipv4 interface"); return false; } QHostAddress actualBindAddress; if(is_v6) actualBindAddress = QHostAddress::AnyIPv6; else actualBindAddress = QHostAddress::Any; jdns = new QJDns(this); jdns_link(jdns); if(!jdns->init(QJDns::Multicast, actualBindAddress)) { doDebug(jdns, index); delete jdns; return false; } } Instance *i = new Instance; i->jdns = jdns; i->addr = addr; i->index = index; instances += i; instanceForQJDns.insert(i->jdns, i); addDebug(index, "interface ready"); if(mode == QJDnsShared::Multicast) { // extend active requests to this interface foreach(QJDnsSharedRequest *obj, requests) { if(obj->d->type == QJDnsSharedRequest::Query) { Handle h(i->jdns, i->jdns->queryStart(obj->d->name, obj->d->qType)); obj->d->handles += h; requestForHandle.insert(h, obj); } else // Publish { bool modified; obj->d->pubrecord = manipulateRecord(obj->d->pubrecord, obj->d->ppmode, &modified); // if the record changed, update on the other (existing) interfaces if(modified) { foreach(Handle h, obj->d->handles) h.jdns->publishUpdate(h.id, obj->d->pubrecord); } // publish the record on the new interface Handle h(i->jdns, i->jdns->publishStart(obj->d->pubmode, obj->d->pubrecord)); obj->d->handles += h; requestForHandle.insert(h, obj); } } } return true; } void QJDnsSharedPrivate::removeInterface(const QHostAddress &addr) { Instance *i = 0; for(int n = 0; n < instances.count(); ++n) { if(instances[n]->addr == addr) { i = instances[n]; break; } } if(!i) return; int index = i->index; // we don't cancel operations or shutdown jdns, we simply // delete our references. this is because if the interface // is gone, then we have nothing to send on anyway. foreach(QJDnsSharedRequest *obj, requests) { for(int n = 0; n < obj->d->handles.count(); ++n) { Handle h = obj->d->handles[n]; if(h.jdns == i->jdns) { // see above, no need to cancel the operation obj->d->handles.removeAt(n); requestForHandle.remove(h); break; } } // remove published reference if(obj->d->type == QJDnsSharedRequest::Publish) { for(int n = 0; n < obj->d->published.count(); ++n) { Handle h = obj->d->published[n]; if(h.jdns == i->jdns) { obj->d->published.removeAt(n); break; } } } } // see above, no need to shutdown jdns instanceForQJDns.remove(i->jdns); instances.removeAll(i); delete i->jdns; delete i; // if that was the last interface to be removed, then there should // be no more handles left. let's take action with these // handleless requests. foreach(QJDnsSharedRequest *obj, requests) { if(obj->d->handles.isEmpty()) { if(mode == QJDnsShared::UnicastInternet || mode == QJDnsShared::UnicastLocal) { // for unicast, we'll invalidate with ErrorNoNet obj->d->success = false; obj->d->error = QJDnsSharedRequest::ErrorNoNet; obj->d->lateTimer.start(); } else // Multicast { // for multicast, we'll keep all requests alive. // activity will resume when an interface is // added. } } } addDebug(index, QString("removing from %1").arg(addr.toString())); } void QJDnsSharedPrivate::queryStart(QJDnsSharedRequest *obj, const QByteArray &name, int qType) { obj->d->type = QJDnsSharedRequest::Query; obj->d->success = false; obj->d->results.clear(); obj->d->name = name; obj->d->qType = qType; // is the input an IP address and the qType is an address record? if(qType == QJDns::Aaaa || qType == QJDns::A) { QHostAddress addr; if(addr.setAddress(QString::fromLocal8Bit(name))) { if(qType == QJDns::Aaaa && addr.protocol() == QAbstractSocket::IPv6Protocol) { QJDns::Record rec; rec.owner = name; rec.type = QJDns::Aaaa; rec.ttl = 120; rec.haveKnown = true; rec.address = addr; obj->d->success = true; obj->d->results = QList() << rec; obj->d->lateTimer.start(); return; } else if(qType == QJDns::A && addr.protocol() == QAbstractSocket::IPv4Protocol) { QJDns::Record rec; rec.owner = name; rec.type = QJDns::A; rec.ttl = 120; rec.haveKnown = true; rec.address = addr; obj->d->success = true; obj->d->results = QList() << rec; obj->d->lateTimer.start(); return; } } } QJDns::SystemInfo sysInfo = get_sys_info(); // is the input name a known host and the qType is an address record? if(qType == QJDns::Aaaa || qType == QJDns::A) { QByteArray lname = name.toLower(); QList known = sysInfo.hosts; foreach(QJDns::DnsHost host, known) { if(((qType == QJDns::Aaaa && host.address.protocol() == QAbstractSocket::IPv6Protocol) || (qType == QJDns::A && host.address.protocol() == QAbstractSocket::IPv4Protocol)) && host.name.toLower() == lname) { QJDns::Record rec; rec.owner = name; rec.type = qType; rec.ttl = 120; rec.haveKnown = true; rec.address = host.address; obj->d->success = true; obj->d->results = QList() << rec; obj->d->lateTimer.start(); return; } } } // if we have no QJDns instances to operate on, then error if(instances.isEmpty()) { obj->d->error = QJDnsSharedRequest::ErrorNoNet; obj->d->lateTimer.start(); return; } if(mode == QJDnsShared::UnicastInternet) { // get latest nameservers, split into ipv6/v4, apply to jdns instances QList ns_v6; QList ns_v4; { QList nameServers = sysInfo.nameServers; foreach(QJDns::NameServer ns, nameServers) { if(ns.address.protocol() == QAbstractSocket::IPv6Protocol) ns_v6 += ns; else ns_v4 += ns; } } foreach(Instance *i, instances) { if(i->addr.protocol() == QAbstractSocket::IPv6Protocol) i->jdns->setNameServers(ns_v6); else i->jdns->setNameServers(ns_v4); } } // keep track of this request requests += obj; // query on all jdns instances foreach(Instance *i, instances) { Handle h(i->jdns, i->jdns->queryStart(name, qType)); obj->d->handles += h; // keep track of this handle for this request requestForHandle.insert(h, obj); } } void QJDnsSharedPrivate::queryCancel(QJDnsSharedRequest *obj) { if(!requests.contains(obj)) return; foreach(Handle h, obj->d->handles) { h.jdns->queryCancel(h.id); requestForHandle.remove(h); } obj->d->handles.clear(); requests.remove(obj); } void QJDnsSharedPrivate::publishStart(QJDnsSharedRequest *obj, QJDns::PublishMode m, const QJDns::Record &record) { obj->d->type = QJDnsSharedRequest::Publish; obj->d->success = false; obj->d->results.clear(); obj->d->pubmode = m; obj->d->ppmode = determinePpMode(record); obj->d->pubrecord = manipulateRecord(record, obj->d->ppmode); // if we have no QJDns instances to operate on, then error if(instances.isEmpty()) { obj->d->error = QJDnsSharedRequest::ErrorNoNet; obj->d->lateTimer.start(); return; } // keep track of this request requests += obj; // attempt to publish on all jdns instances foreach(QJDnsSharedPrivate::Instance *i, instances) { Handle h(i->jdns, i->jdns->publishStart(m, obj->d->pubrecord)); obj->d->handles += h; // keep track of this handle for this request requestForHandle.insert(h, obj); } } void QJDnsSharedPrivate::publishUpdate(QJDnsSharedRequest *obj, const QJDns::Record &record) { if(!requests.contains(obj)) return; obj->d->ppmode = determinePpMode(record); obj->d->pubrecord = manipulateRecord(record, obj->d->ppmode); // publish update on all handles for this request foreach(Handle h, obj->d->handles) h.jdns->publishUpdate(h.id, obj->d->pubrecord); } void QJDnsSharedPrivate::publishCancel(QJDnsSharedRequest *obj) { if(!requests.contains(obj)) return; foreach(Handle h, obj->d->handles) { h.jdns->publishCancel(h.id); requestForHandle.remove(h); } obj->d->handles.clear(); obj->d->published.clear(); requests.remove(obj); } void QJDnsSharedPrivate::jdns_resultsReady(int id, const QJDns::Response &results) { QJDns *jdns = (QJDns *)sender(); QJDnsSharedRequest *obj = findRequest(jdns, id); Q_ASSERT(obj); obj->d->success = true; obj->d->results = results.answerRecords; if(mode == QJDnsShared::UnicastInternet || mode == QJDnsShared::UnicastLocal) { // only one response, so "cancel" it for(int n = 0; n < obj->d->handles.count(); ++n) { Handle h = obj->d->handles[n]; if(h.jdns == jdns && h.id == id) { obj->d->handles.removeAt(n); requestForHandle.remove(h); break; } } // cancel related handles foreach(Handle h, obj->d->handles) { h.jdns->queryCancel(h.id); requestForHandle.remove(h); } obj->d->handles.clear(); requests.remove(obj); } else // Multicast { // check our cache to see how we should report these results for(int n = 0; n < obj->d->results.count(); ++n) { QJDns::Record &r = obj->d->results[n]; // do we have this answer already in our cache? QJDns::Record *c = 0; int c_at = -1; for(int k = 0; k < obj->d->queryCache.count(); ++k) { QJDns::Record &tmp = obj->d->queryCache[k]; if(matchRecordExceptTtl(r, tmp)) { c = &tmp; c_at = k; break; } } // don't report duplicates or unknown removals if((c && r.ttl != 0) || (!c && r.ttl == 0)) { obj->d->results.removeAt(n); --n; // adjust position continue; } // if we have it, and it is removed, remove from cache if(c && r.ttl == 0) { obj->d->queryCache.removeAt(c_at); } // otherwise, if we don't have it, add it to the cache else if(!c) { obj->d->queryCache += r; } } if(obj->d->results.isEmpty()) return; } emit obj->resultsReady(); } void QJDnsSharedPrivate::jdns_published(int id) { QJDns *jdns = (QJDns *)sender(); QJDnsSharedRequest *obj = findRequest(jdns, id); Q_ASSERT(obj); // find handle Handle handle; for(int n = 0; n < obj->d->handles.count(); ++n) { Handle h = obj->d->handles[n]; if(h.jdns == jdns && h.id == id) { handle = h; break; } } obj->d->published += handle; // if this publish has already been considered successful, then // a publish has succeeded on a new interface and there's no // need to report success for this request again if(obj->d->success) return; // all handles published? if(obj->d->published.count() == obj->d->handles.count()) { obj->d->success = true; emit obj->resultsReady(); } } void QJDnsSharedPrivate::jdns_error(int id, QJDns::Error e) { QJDns *jdns = (QJDns *)sender(); QJDnsSharedRequest *obj = findRequest(jdns, id); Q_ASSERT(obj); // "cancel" it for(int n = 0; n < obj->d->handles.count(); ++n) { Handle h = obj->d->handles[n]; if(h.jdns == jdns && h.id == id) { obj->d->handles.removeAt(n); requestForHandle.remove(h); break; } } if(obj->d->type == QJDnsSharedRequest::Query) { // ignore the error if it is not the last error if(!obj->d->handles.isEmpty()) return; requests.remove(obj); obj->d->success = false; QJDnsSharedRequest::Error x = QJDnsSharedRequest::ErrorGeneric; if(e == QJDns::ErrorNXDomain) x = QJDnsSharedRequest::ErrorNXDomain; else if(e == QJDns::ErrorTimeout) x = QJDnsSharedRequest::ErrorTimeout; else // ErrorGeneric x = QJDnsSharedRequest::ErrorGeneric; obj->d->error = x; emit obj->resultsReady(); } else // Publish { // cancel related handles foreach(Handle h, obj->d->handles) { h.jdns->publishCancel(h.id); requestForHandle.remove(h); } obj->d->handles.clear(); obj->d->published.clear(); requests.remove(obj); obj->d->success = false; QJDnsSharedRequest::Error x = QJDnsSharedRequest::ErrorGeneric; if(e == QJDns::ErrorConflict) x = QJDnsSharedRequest::ErrorConflict; else // ErrorGeneric x = QJDnsSharedRequest::ErrorGeneric; obj->d->error = x; emit obj->resultsReady(); } } void QJDnsSharedPrivate::jdns_shutdownFinished() { QJDns *jdns = (QJDns *)sender(); addDebug(instanceForQJDns.value(jdns)->index, "jdns_shutdownFinished, removing interface"); Instance *instance = instanceForQJDns.value(jdns); delete instance->jdns; delete instance; instanceForQJDns.remove(jdns); instances.removeAll(instance); if(instances.isEmpty()) late_shutdown(); } void QJDnsSharedPrivate::jdns_debugLinesReady() { QJDns *jdns = (QJDns *)sender(); doDebug(jdns, instanceForQJDns.value(jdns)->index); } + +#include "moc_qjdnsshared.cpp" +#include "moc_qjdnsshared_p.cpp" diff --git a/protocols/jabber/libiris/src/xmpp/xmpp-core/connector.cpp b/protocols/jabber/libiris/src/xmpp/xmpp-core/connector.cpp index 3dabede88..4887be178 100644 --- a/protocols/jabber/libiris/src/xmpp/xmpp-core/connector.cpp +++ b/protocols/jabber/libiris/src/xmpp/xmpp-core/connector.cpp @@ -1,576 +1,578 @@ /* * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 #include #include #include #include #include "bsocket.h" #include "httpconnect.h" #include "httppoll.h" #include "socks.h" //#define XMPP_DEBUG #ifdef XMPP_DEBUG # define XDEBUG (qDebug() << this << "#" << __FUNCTION__ << ":") #endif using namespace XMPP; static const int XMPP_DEFAULT_PORT = 5222; static const int XMPP_LEGACY_PORT = 5223; static const char* XMPP_CLIENT_SRV = "xmpp-client"; static const char* XMPP_CLIENT_TRANSPORT = "tcp"; //---------------------------------------------------------------------------- // 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; } quint16 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, quint16 _port) { haveaddr = true; addr = _addr; port = _port; } QString Connector::host() const { return QString(); } //---------------------------------------------------------------------------- // 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; } quint16 AdvancedConnector::Proxy::port() const { return v_port; } QUrl 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, quint16 port) { t = HttpConnect; v_host = host; v_port = port; } void AdvancedConnector::Proxy::setHttpPoll(const QString &host, quint16 port, const QUrl &url) { t = HttpPoll; v_host = host; v_port = port; v_url = url; } void AdvancedConnector::Proxy::setSocks(const QString &host, quint16 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 //---------------------------------------------------------------------------- typedef enum { Idle, Connecting, Connected } Mode; typedef enum { Force, Probe, Never } LegacySSL; class AdvancedConnector::Private { public: ByteStream *bs; //!< Socket to use /* configuration values / "options" */ QString opt_host; //!< explicit host from config quint16 opt_port; //!< explicit port from config LegacySSL opt_ssl; //!< Whether to use legacy SSL support Proxy proxy; //!< Proxy configuration /* State tracking values */ Mode mode; //!< Idle, Connecting, Connected QString host; //!< Host we currently try to connect to, set from connectToServer() int port; //!< Port we currently try to connect to, set from connectToServer() and bs_error() int errorCode; //!< Current error, if any }; AdvancedConnector::AdvancedConnector(QObject *parent) :Connector(parent) { d = new Private; d->bs = 0; d->opt_ssl = Never; cleanup(); d->errorCode = 0; } AdvancedConnector::~AdvancedConnector() { cleanup(); delete d; } void AdvancedConnector::cleanup() { d->mode = Idle; // destroy the bytestream, if there is one delete d->bs; d->bs = 0; setUseSSL(false); setPeerAddressNone(); } void AdvancedConnector::setProxy(const Proxy &proxy) { if(d->mode != Idle) return; d->proxy = proxy; } void AdvancedConnector::setOptHostPort(const QString &_host, quint16 _port) { #ifdef XMPP_DEBUG XDEBUG << "h:" << _host << "p:" << _port; #endif if(d->mode != Idle) return; // empty host means disable explicit host support if(_host.isEmpty()) { d->opt_host.clear(); return; } d->opt_host = _host; d->opt_port = _port; } void AdvancedConnector::setOptProbe(bool b) { #ifdef XMPP_DEBUG XDEBUG << "b:" << b; #endif if(d->mode != Idle) return; d->opt_ssl = (b ? Probe : Never); } void AdvancedConnector::setOptSSL(bool b) { #ifdef XMPP_DEBUG XDEBUG << "b:" << b; #endif if(d->mode != Idle) return; d->opt_ssl = (b ? Force : Never); } void AdvancedConnector::connectToServer(const QString &server) { #ifdef XMPP_DEBUG XDEBUG << "s:" << server; #endif if(d->mode != Idle) return; if(server.isEmpty()) return; d->errorCode = 0; d->mode = Connecting; // Encode the servername d->host = QUrl::toAce(server); if (d->host == QByteArray()) { /* server contains invalid characters for DNS name, but maybe valid characters for connecting, like "::1" */ d->host = server; } d->port = XMPP_DEFAULT_PORT; if (d->opt_ssl == Probe && (d->proxy.type() != Proxy::None || !d->opt_host.isEmpty())) { #ifdef XMPP_DEBUG XDEBUG << "Don't probe ssl port because of incompatible params"; #endif d->opt_ssl = Never; // probe is possible only with direct connect } if(d->proxy.type() == Proxy::HttpPoll) { 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->proxy.type() == Proxy::HttpConnect) { 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->opt_host.isEmpty()) { d->host = d->opt_host; d->port = d->opt_port; } 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 (d->proxy.type() == Proxy::Socks) { 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->opt_host.isEmpty()) { d->host = d->opt_host; d->port = d->opt_port; } 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 { BSocket *s = new BSocket; d->bs = s; #ifdef XMPP_DEBUG XDEBUG << "Adding socket:" << s; #endif connect(s, SIGNAL(connected()), SLOT(bs_connected())); connect(s, SIGNAL(error(int)), SLOT(bs_error(int))); if(!d->opt_host.isEmpty()) { /* if custom host:port */ d->host = d->opt_host; d->port = d->opt_port; s->connectToHost(d->host, d->port); return; } else if (d->opt_ssl != Never) { /* if ssl forced or should be probed */ d->port = XMPP_LEGACY_PORT; } s->connectToHost(XMPP_CLIENT_SRV, XMPP_CLIENT_TRANSPORT, d->host, d->port); } } void AdvancedConnector::changePollInterval(int secs) { if(d->bs && (d->bs->inherits("XMPP::HttpPoll") || d->bs->inherits("HttpPoll"))) { HttpPoll *s = static_cast(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::bs_connected() { #ifdef XMPP_DEBUG XDEBUG; #endif if(d->proxy.type() == Proxy::None) { QHostAddress h = (static_cast(d->bs))->peerAddress(); int p = (static_cast(d->bs))->peerPort(); setPeerAddress(h, p); } // We won't use ssl with HttpPoll since it has ow tls handler enabled for https. // The only variant for ssl is legacy port in probing or forced mde. if(d->proxy.type() != Proxy::HttpPoll && (d->opt_ssl == Force || ( d->opt_ssl == Probe && peerPort() == XMPP_LEGACY_PORT))) { // in case of Probe it's ok to check actual peer "port" since we are sure Proxy=None setUseSSL(true); } d->mode = Connected; emit connected(); } void AdvancedConnector::bs_error(int x) { #ifdef XMPP_DEBUG XDEBUG << "e:" << x; #endif if(d->mode == Connected) { d->errorCode = ErrStream; emit error(); return; } bool proxyError = false; int err = ErrConnectionRefused; int t = d->proxy.type(); #ifdef XMPP_DEBUG qDebug("bse1"); #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(proxyError) { cleanup(); d->errorCode = err; emit error(); return; } /* if we shall probe the ssl legacy port, and we just did that (port=legacy), then try to connect to the normal port instead */ if(d->opt_ssl == Probe && d->port == XMPP_LEGACY_PORT) { #ifdef XMPP_DEBUG qDebug("bse1.2"); #endif BSocket *s = static_cast(d->bs); d->port = XMPP_DEFAULT_PORT; // at this moment we already tried everything from srv. so just try the host itself s->connectToHost(d->host, d->port); } /* otherwise we have no fallbacks and must have failed to connect */ else { #ifdef XMPP_DEBUG qDebug("bse1.3"); #endif cleanup(); d->errorCode = ErrConnectionRefused; emit error(); } } void AdvancedConnector::http_syncStarted() { httpSyncStarted(); } void AdvancedConnector::http_syncFinished() { httpSyncFinished(); } void AdvancedConnector::t_timeout() { //bs_error(-1); } QString AdvancedConnector::host() const { return d->host; } + +#include "moc_xmpp.cpp" diff --git a/protocols/jabber/libiris/src/xmpp/xmpp-core/stream.cpp b/protocols/jabber/libiris/src/xmpp/xmpp-core/stream.cpp index eb46f1e04..1a2e6e8ad 100644 --- a/protocols/jabber/libiris/src/xmpp/xmpp-core/stream.cpp +++ b/protocols/jabber/libiris/src/xmpp/xmpp-core/stream.cpp @@ -1,1548 +1,1551 @@ /* * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 even if we close prematurely? - ensure ClientStream and child classes are fully deletable after signals - xml:lang in root () element - sasl external - sasl anonymous */ #include "xmpp.h" #include #include #include #include #include #include #include //#include #include #include "bytestream.h" #include "simplesasl.h" #include "securestream.h" #include "protocol.h" #ifndef NO_IRISNET #include "irisnetglobal_p.h" #endif #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; a.resize(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::Hash("sha1").hashToString(randomArray(128)); } //---------------------------------------------------------------------------- // 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; #ifndef NO_IRISNET irisNetAddPostRoutine(cleanup); #endif } return foo->elementToString(e, clip); } void Stream::cleanup() { delete foo; foo = 0; } //---------------------------------------------------------------------------- // 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; oldOnly = false; allowPlain = NoAllowPlain; mutualAuth = false; haveLocalAddr = false; minimumSSF = 0; maximumSSF = 0; doBinding = true; doCompress = false; lang = ""; in_rrsig = false; quiet_reconnection = 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 mutualAuth; AllowPlainType allowPlain; bool haveLocalAddr; QHostAddress localAddr; quint16 localPort; QString connectHost; int minimumSSF, maximumSSF; QString sasl_mech; QMap mechProviders; // mech to provider map bool doBinding; bool in_rrsig; Connector *conn; ByteStream *bs; TLSHandler *tlsHandler; QCA::TLS *tls; QCA::SASL *sasl; SecureStream *ss; CoreProtocol client; CoreProtocol srv; QString lang; QString defRealm; int mode; int state; int notify; bool newStanzas; int sasl_ssf; bool tls_warned, using_tls; bool doAuth; bool doCompress; QStringList sasl_mechlist; int errCond; QString errText; QDomElement errAppSpec; QList in; QTimer timeout_timer; QTimer noopTimer; int noop_time; bool quiet_reconnection; }; 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->readAll(); d->ss = new SecureStream(d->bs); connect(d->ss, SIGNAL(readyRead()), SLOT(ss_readyRead())); connect(d->ss, SIGNAL(bytesWritten(qint64)), SLOT(ss_bytesWritten(qint64))); 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(); connect(&(d->timeout_timer), SIGNAL(timeout()), SLOT(sm_timeout())); } ClientStream::~ClientStream() { //fprintf(stderr, "\tClientStream::~ClientStream\n"); //fflush(stderr); reset(); delete d; } void ClientStream::reset(bool all) { //fprintf(stderr, "\tClientStream::reset\n"); //fflush(stderr); d->reset(); d->noopTimer.stop(); // delete securestream delete d->ss; d->ss = 0; // reset sasl delete d->sasl; d->sasl = 0; if(all) { while (!d->in.isEmpty()) { delete d->in.takeFirst(); } } else { QSharedPointer sd; foreach (Stanza *s, d->in) { sd = s->unboundDocument(sd); } } // client if(d->mode == Client) { // reset tls // FIXME: Temporarily disabled //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(); } } 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(QCA::SecureArray(s.toUtf8())); } } void ClientStream::setRealm(const QString &s) { if(d->sasl) d->sasl->setRealm(s); } void ClientStream::setAuthzid(const QString &s) { if(d->sasl) d->sasl->setAuthzid(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::setSaslMechanismProvider(const QString &m, const QString &p) { d->mechProviders.insert(m, p); } QString ClientStream::saslMechanismProvider(const QString &m) const { return d->mechProviders.value(m); } QCA::Provider::Context *ClientStream::currentSASLContext() const { if (d->sasl) { return d->sasl->context(); } return 0; } void ClientStream::setSCRAMStoredSaltedHash(const QString &s) { QCA::SASLContext *context = (QCA::SASLContext *)(d->sasl->context()); if (context) { context->setProperty("scram-salted-password-base64", s); } } const QString ClientStream::getSCRAMStoredSaltedHash() { QCA::SASLContext *context = (QCA::SASLContext *)(d->sasl->context()); if (context) { return context->property("scram-salted-password-base64").toString(); } return QString(); } void ClientStream::setResourceBinding(bool b) { d->doBinding = b; } void ClientStream::setLang(const QString& lang) { d->lang = lang; } 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, quint16 port) { d->haveLocalAddr = true; d->localAddr = addr; d->localPort = port; } void ClientStream::setCompress(bool compress) { d->doCompress = compress; } 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; } void ClientStream::setAllowPlain(AllowPlainType a) { d->allowPlain = a; } 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.takeFirst(); Stanza s = *sp; delete sp; return s; } } void ClientStream::write(const Stanza &s) { if(d->state == Active) { d->client.sendStanza(s.element()); processNext(); } } void ClientStream::cr_connected() { d->connectHost = d->conn->host(); 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->readAll(); d->ss = new SecureStream(d->bs); connect(d->ss, SIGNAL(readyRead()), SLOT(ss_readyRead())); connect(d->ss, SIGNAL(bytesWritten(qint64)), SLOT(ss_bytesWritten(qint64))); 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->doCompress); d->client.setAllowTLS(d->tlsHandler ? true: false); d->client.setAllowBind(d->doBinding); d->client.setAllowPlain(d->allowPlain == AllowPlain || (d->allowPlain == AllowPlainOverTLS && d->conn->useSSL())); d->client.setLang(d->lang); /*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;*/ QPointer self = this; if (!d->quiet_reconnection) emit 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->readAll(); #ifdef XMPP_DEBUG qDebug("ClientStream: recv: %d [%s]\n", a.size(), a.data()); #endif if(d->mode == Client) d->client.addIncomingData(a); else d->srv.addIncomingData(a); if(d->notify & CoreProtocol::NRecv) { #ifdef XMPP_DEBUG qDebug("We needed data, so let's process it\n"); #endif processNext(); } } void ClientStream::ss_bytesWritten(qint64 bytes) { if(d->mode == Client) d->client.outgoingDataWritten(bytes); else d->srv.outgoingDataWritten(bytes); if(d->notify & CoreProtocol::NSend) { #ifdef XMPP_DEBUG qDebug("We were waiting for data to be written, so let's process\n"); #endif processNext(); } } void ClientStream::ss_tlsHandshaken() { QPointer self = this; if (!d->quiet_reconnection) securityLayerActivated(LayerTLS); if(!self) return; d->client.setAllowPlain(d->allowPlain == AllowPlain || d->allowPlain == AllowPlainOverTLS); 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(bool, const QByteArray& ba) { d->client.setSASLFirst(d->sasl->mechanism(), ba); //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(const QCA::SASL::Params& p) { #ifdef XMPP_DEBUG qDebug("need params: needUsername: %d, canSendAuthzid: %d, needPassword: %d, canSendRealm: %d\n", p.needUsername()?1:0, p.canSendAuthzid()? 1:0, p.needPassword()? 1:0, p.canSendRealm()? 1:0); #endif /*if(p.authzid && !p.user) { d->sasl->setAuthzid(d->jid.bare()); //d->sasl->setAuthzid("infiniti.homelesshackers.org"); }*/ if(p.needUsername() || p.needPassword() || p.canSendRealm()) { d->state = NeedParams; needAuthParams(p.needUsername(), p.needPassword(), p.canSendRealm()); } else d->sasl->continueAfterParams(); } void ClientStream::sasl_authCheck(const QString &user, const QString &) { //#ifdef XMPP_DEBUG // qDebug("authcheck: [%s], [%s]\n", user.latin1(), authzid.latin1()); //#endif QString u = user; int n = u.indexOf('@'); if(n != -1) u.truncate(n); d->srv.user = u; d->sasl->continueAfterAuthCheck(); } void ClientStream::sasl_authenticated() { #ifdef XMPP_DEBUG qDebug("sasl authed!!\n"); #endif d->sasl_ssf = d->sasl->ssf(); if(d->mode == Server) { d->srv.setSASLAuthed(); processNext(); } } void ClientStream::sasl_error() { #ifdef XMPP_DEBUG qDebug("sasl error: %d\n", d->sasl->authCondition()); #endif // has to be auth error int x = convertedSASLCond(); d->errText = tr("Offered mechanisms: ") + d->client.features.sasl_mechs.join(", "); reset(); d->errCond = x; error(ErrAuth); } void ClientStream::srvProcessNext() { while(1) { qDebug("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) qDebug("More data needs to be written to process next step\n"); if(d->notify & CoreProtocol::NRecv) qDebug("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(QString,QString)), SLOT(sasl_authCheck(QString,QString))); connect(d->sasl, SIGNAL(nextStep(QByteArray)), SLOT(sasl_nextStep(QByteArray))); connect(d->sasl, SIGNAL(authenticated()), SLOT(sasl_authenticated())); connect(d->sasl, SIGNAL(error()), SLOT(sasl_error())); //d->sasl->setAllowAnonymous(false); //d->sasl->setRequirePassCredentials(true); //d->sasl->setExternalAuthID("localhost"); QCA::SASL::AuthFlags auth_flags = (QCA::SASL::AuthFlags) 0; d->sasl->setConstraints(auth_flags,0,256); QStringList list; // TODO: d->server is probably wrong here d->sasl->startServer("xmpp", d->server, d->defRealm, QCA::SASL::AllowServerSendLast); d->sasl_mechlist = list; } d->srv.setSASLMechList(d->sasl_mechlist); continue; } else if(need == CoreProtocol::NStartTLS) { qDebug("Need StartTLS\n"); //if(!d->tls->startServer()) { d->tls->startServer(); QByteArray a = d->srv.spare; d->ss->startTLSServer(d->tls, a); } else if(need == CoreProtocol::NSASLFirst) { qDebug("Need SASL First Step\n"); QByteArray a = d->srv.saslStep(); d->sasl->putServerFirstStep(d->srv.saslMech(), a); } else if(need == CoreProtocol::NSASLNext) { qDebug("Need SASL Next Step\n"); QByteArray a = d->srv.saslStep(); qDebug("[%s]\n", a.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; qDebug("event: %d\n", event); switch(event) { case CoreProtocol::EError: { qDebug("Error! Code=%d\n", d->srv.errorCode); reset(); error(ErrProtocol); //handleError(); return; } case CoreProtocol::ESend: { QByteArray a = d->srv.takeOutgoingData(); qDebug("Need Send: {%s}\n", a.data()); d->ss->write(a); break; } case CoreProtocol::ERecvOpen: { qDebug("Break (RecvOpen)\n"); // calculate key QByteArray str = QCA::Hash("sha1").hashToString("secret").toUtf8(); str = QCA::Hash("sha1").hashToString(QByteArray(str + "im.pyxa.org")).toUtf8(); str = QCA::Hash("sha1").hashToString(QByteArray(str + d->srv.id.toUtf8())).toUtf8(); 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: { qDebug("Break SASL Success\n"); disconnect(d->sasl, SIGNAL(error()), this, SLOT(sasl_error())); QByteArray a = d->srv.spare; d->ss->setLayerSASL(d->sasl, a); break; } case CoreProtocol::EPeerClosed: { // TODO: this isn' an error qDebug("peer closed\n"); reset(); error(ErrProtocol); return; } } } } void ClientStream::doReadyRead() { //QGuardedPtr self = this; if (isActive()) emit readyRead(); //if(!self) // return; //d->in_rrsig = false; } void ClientStream::processNext() { if(d->mode == Server) { srvProcessNext(); return; } QPointer self = this; while(1) { #ifdef XMPP_DEBUG qDebug("Processing step...\n"); #endif bool ok = d->client.processStep(); // deal with send/received items foreach (const XmlProtocol::TransferItem &i, d->client.transferItemList) { if(i.isExternal) continue; QString str; if(i.isString) { // skip whitespace pings if(i.str.trimmed().isEmpty()) continue; str = i.str; } else str = d->client.elementToString(i.elem); if(i.isSent) emit outgoingXml(str); else emit incomingXml(str); } #ifdef XMPP_DEBUG qDebug("\tNOTIFY: %d\n", d->client.notify); #endif if (d->client.notify & CoreProtocol::NTimeout ) { #ifdef XMPP_DEBUG qDebug() << "Time = "<< d->client.timeout_sec; #endif d->timeout_timer.setSingleShot(true); d->timeout_timer.start(d->client.timeout_sec * 1000); d->client.notify &= ~ CoreProtocol::NTimeout; #ifdef XMPP_DEBUG qDebug() << "\tNTimeout received | Start timer"; #endif } 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; //fprintf(stderr, "\tClientStream::processNext() QTimer::singleShot\n"); //fflush(stderr); 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 qDebug("Error! Code=%d\n", d->client.errorCode); #endif handleError(); return; } case CoreProtocol::ESend: { QByteArray a = d->client.takeOutgoingData(); #ifdef XMPP_DEBUG qDebug("Need Send: {%s}\n", a.data()); #endif d->ss->write(a); break; } case CoreProtocol::ERecvOpen: { #ifdef XMPP_DEBUG qDebug("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 qDebug("Break (Features)\n"); #endif if (d->client.unhandledFeatures.count()) { emit haveUnhandledFeatures(); } 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 qDebug("Break SASL Success\n"); #endif break; } case CoreProtocol::EReady: { #ifdef XMPP_DEBUG qDebug("Done!\n"); #endif // grab the JID, in case it changed d->jid = d->client.jid(); d->state = Active; setNoopTime(d->noop_time); if (!d->quiet_reconnection) authenticated(); if(!self) return; break; } case CoreProtocol::EPeerClosed: { #ifdef XMPP_DEBUG qDebug("DocumentClosed\n"); #endif reset(); connectionClosed(); return; } case CoreProtocol::EStanzaReady: { #ifdef XMPP_DEBUG qDebug("StanzaReady\n"); #endif // store the stanza for now, announce after processing all events // TODO: add a method to the stanza to mark them handled. Stanza s = createStanza(d->client.recvStanza()); if(s.isNull()) break; if (d->client.sm.isActive()) d->client.sm.markStanzaHandled(); d->in.append(new Stanza(s)); break; } case CoreProtocol::EStanzaSent: { #ifdef XMPP_DEBUG qDebug("StanzasSent\n"); #endif stanzaWritten(); if(!self) return; break; } case CoreProtocol::EClosed: { #ifdef XMPP_DEBUG qDebug("Closed\n"); #endif reset(); delayedCloseFinished(); return; } case CoreProtocol::EAck: { int ack_cnt = d->client.sm.takeAckedCount(); #ifdef XMPP_DEBUG qDebug() << "Stream Management: [INF] Received ack amount: " << ack_cnt; #endif emit stanzasAcked(ack_cnt); break; } case CoreProtocol::ESMConnTimeout: { #ifdef XMPP_DEBUG qDebug() << "Stream Management: [INF] Connection timeout"; #endif reset(); if (d->client.sm.state().isResumption()) { d->state = Connecting; emit warning(WarnSMReconnection); d->quiet_reconnection = true; if (d->client.sm.state().isLocationValid()) d->conn->setOptHostPort(d->client.sm.state().resumption_location.host , d->client.sm.state().resumption_location.port); d->conn->connectToServer(d->server); } else { d->quiet_reconnection = false; emit connectionClosed(); } 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) qDebug("More data needs to be written to process next step\n"); if(d->notify & CoreProtocol::NRecv) qDebug("More data is needed to process next step\n"); #endif return false; } d->notify = 0; switch(need) { case CoreProtocol::NStartTLS: { #ifdef XMPP_DEBUG qDebug("Need StartTLS\n"); #endif d->using_tls = true; d->ss->startTLSClient(d->tlsHandler, d->server, d->client.spare); return false; } case CoreProtocol::NCompress: { #ifdef XMPP_DEBUG qDebug("Need compress\n"); #endif d->ss->setLayerCompress(d->client.spare); return true; } case CoreProtocol::NSASLFirst: { #ifdef XMPP_DEBUG qDebug("Need SASL First Step\n"); #endif // ensure simplesasl provider is installed bool found = false; foreach(QCA::Provider *p, QCA::providers()) { if(p->name() == "simplesasl") { found = true; break; } } if(!found) { // install with low-priority QCA::insertProvider(createProviderSimpleSASL()); QCA::setProviderPriority("simplesasl", 10); } QStringList ml; if(!d->sasl_mech.isEmpty()) ml += d->sasl_mech; else ml = d->client.features.sasl_mechs; QString saslProvider; foreach (const QString &mech, d->mechProviders.keys()) { if (ml.contains(mech)) { saslProvider = d->mechProviders[mech]; break; } } d->sasl = new QCA::SASL(0, saslProvider); connect(d->sasl, SIGNAL(clientStarted(bool,QByteArray)), SLOT(sasl_clientFirstStep(bool,QByteArray))); connect(d->sasl, SIGNAL(nextStep(QByteArray)), SLOT(sasl_nextStep(QByteArray))); connect(d->sasl, SIGNAL(needParams(QCA::SASL::Params)), SLOT(sasl_needParams(QCA::SASL::Params))); connect(d->sasl, SIGNAL(authenticated()), SLOT(sasl_authenticated())); connect(d->sasl, SIGNAL(error()), SLOT(sasl_error())); if(d->haveLocalAddr) d->sasl->setLocalAddress(d->localAddr.toString(), d->localPort); if(d->conn->havePeerAddress()) d->sasl->setRemoteAddress(d->conn->peerAddress().toString(), d->conn->peerPort()); //d->sasl_mech = "ANONYMOUS"; //d->sasl->setRequirePassCredentials(true); //d->sasl->setExternalAuthID("localhost"); //d->sasl->setExternalSSF(64); //d->sasl_mech = "EXTERNAL"; QCA::SASL::AuthFlags auth_flags = (QCA::SASL::AuthFlags) 0; if (d->allowPlain == AllowPlain || (d->allowPlain == AllowPlainOverTLS && d->using_tls)) auth_flags = (QCA::SASL::AuthFlags) (auth_flags | QCA::SASL::AllowPlain); if (d->mutualAuth) auth_flags = (QCA::SASL::AuthFlags) (auth_flags | QCA::SASL::RequireMutualAuth); d->sasl->setConstraints(auth_flags,d->minimumSSF,d->maximumSSF); #ifdef IRIS_SASLCONNECTHOST d->sasl->startClient("xmpp", QUrl::toAce(d->connectHost), ml, QCA::SASL::AllowClientSendFirst); #else d->sasl->startClient("xmpp", QUrl::toAce(d->server), ml, QCA::SASL::AllowClientSendFirst); #endif return false; } case CoreProtocol::NSASLNext: { #ifdef XMPP_DEBUG qDebug("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()), this, SLOT(sasl_error())); d->ss->setLayerSASL(d->sasl, d->client.spare); if(d->sasl_ssf > 0) { QPointer self = this; if (!d->quiet_reconnection) securityLayerActivated(LayerSASL); if(!self) return false; } break; } case CoreProtocol::NPassword: { #ifdef XMPP_DEBUG qDebug("Need Password\n"); #endif d->state = NeedParams; needAuthParams(false, true, false); return false; } } return true; } int ClientStream::convertedSASLCond() const { int x = d->sasl->authCondition(); if(x == QCA::SASL::NoMechanism) return NoMech; else if(x == QCA::SASL::BadProtocol) return BadProto; else if(x == QCA::SASL::BadServer) return BadServ; else if(x == QCA::SASL::TooWeak) return MechTooWeak; else return GenericAuthError; return 0; } void ClientStream::sm_timeout() { #ifdef XMPP_DEBUG printf("ClientStream::sm_timeout()\n"); #endif d->client.timeout_sec = 0; processNext(); } void ClientStream::doNoop() { if(d->state == Active) { #ifdef XMPP_DEBUG qDebug("doPing\n"); #endif d->client.sendWhitespace(); processNext(); } } void ClientStream::writeDirect(const QString &s) { if(d->state == Active) { #ifdef XMPP_DEBUG qDebug("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::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::NotWellFormed: { 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 ) 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); } } } bool ClientStream::isResumed() const { return d->client.sm.isResumed(); } void ClientStream::setSMEnabled(bool e) { d->client.sm.state().setEnabled(e); } QStringList ClientStream::hosts() const { return d->client.hosts; } const StreamFeatures &ClientStream::streamFeatures() const { return d->client.features; } QList ClientStream::unhandledFeatures() const { return d->client.unhandledFeatures; } //---------------------------------------------------------------------------- // 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 + +#include "moc_xmpp_clientstream.cpp" +#include "moc_xmpp_stream.cpp" diff --git a/protocols/jabber/libiris/src/xmpp/xmpp-im/client.cpp b/protocols/jabber/libiris/src/xmpp/xmpp-im/client.cpp index 73db7439a..fb62b8830 100644 --- a/protocols/jabber/libiris/src/xmpp/xmpp-im/client.cpp +++ b/protocols/jabber/libiris/src/xmpp/xmpp-im/client.cpp @@ -1,1362 +1,1364 @@ /* * client.cpp - IM Client * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ //! \class XMPP::Client client.h //! \brief Communicates with the XMPP network. Start here. //! //! Client controls an active XMPP connection. It allows you to connect, //! authenticate, manipulate the roster, and send / receive messages and //! presence. It is the centerpiece of this library, and all Tasks must pass //! through it. //! //! For convenience, many Tasks are handled internally to Client (such as //! JT_Auth). However, for accessing features beyond the basics provided by //! Client, you will need to manually invoke Tasks. Fortunately, the //! process is very simple. //! //! The entire Task system is heavily founded on Qt. All Tasks have a parent, //! except for the root Task, and are considered QObjects. By using Qt's RTTI //! facilities (QObject::sender(), QObject::isA(), etc), you can use a //! "fire and forget" approach with Tasks. //! //! \code //! #include "client.h" //! using namespace XMPP; //! //! ... //! //! Client *client; //! //! Session::Session() //! { //! client = new Client; //! connect(client, SIGNAL(handshaken()), SLOT(clientHandshaken())); //! connect(client, SIGNAL(authFinished(bool,int,QString)), SLOT(authFinished(bool,int,QString))); //! client->connectToHost("jabber.org"); //! } //! //! void Session::clientHandshaken() //! { //! client->authDigest("jabtest", "12345", "Psi"); //! } //! //! void Session::authFinished(bool success, int, const QString &err) //! { //! if(success) //! printf("Login success!"); //! else //! printf("Login failed. Here's why: %s\n", err.toLatin1()); //! } //! \endcode #include #include #include #include #include #include "im.h" #include "safedelete.h" #include "xmpp_tasks.h" #include "xmpp_xmlcommon.h" #include "s5b.h" #include "xmpp_ibb.h" #include "xmpp_bitsofbinary.h" #include "filetransfer.h" #include "xmpp_caps.h" #include "protocol.h" #ifdef Q_OS_WIN #define vsnprintf _vsnprintf #endif namespace XMPP { //---------------------------------------------------------------------------- // Client //---------------------------------------------------------------------------- class Client::GroupChat { public: enum { Connecting, Connected, Closing }; GroupChat() {} Jid j; int status; QString password; }; class Client::ClientPrivate { public: ClientPrivate() {} QPointer stream; QDomDocument doc; int id_seed; Task *root; QString host, user, pass, resource; QString osName, osVersion, tzname, clientName, clientVersion; CapsSpec caps, serverCaps; DiscoItem::Identity identity; Features features; QMap extension_features; int tzoffset; bool useTzoffset; // manual tzoffset is old way of doing utc<->local translations bool active; LiveRoster roster; ResourceList resourceList; CapsManager *capsman; S5BManager *s5bman; IBBManager *ibbman; BoBManager *bobman; FileTransferManager *ftman; bool ftEnabled; QList groupChatList; }; Client::Client(QObject *par) :QObject(par) { d = new ClientPrivate; d->tzoffset = 0; d->useTzoffset = false; d->active = false; d->osName = "N/A"; d->clientName = "N/A"; d->clientVersion = "0.0"; d->id_seed = 0xaaaa; d->root = new Task(this, true); d->s5bman = new S5BManager(this); connect(d->s5bman, SIGNAL(incomingReady()), SLOT(s5b_incomingReady())); d->ibbman = new IBBManager(this); connect(d->ibbman, SIGNAL(incomingReady()), SLOT(ibb_incomingReady())); d->bobman = new BoBManager(this); d->ftman = 0; d->capsman = new CapsManager(this); } Client::~Client() { //fprintf(stderr, "\tClient::~Client\n"); //fflush(stderr); close(true); delete d->ftman; delete d->ibbman; delete d->s5bman; delete d->root; delete d; //fprintf(stderr, "\tClient::~Client\n"); } void Client::connectToServer(ClientStream *s, const Jid &j, bool auth) { d->stream = s; //connect(d->stream, SIGNAL(connected()), SLOT(streamConnected())); //connect(d->stream, SIGNAL(handshaken()), SLOT(streamHandshaken())); connect(d->stream, SIGNAL(error(int)), SLOT(streamError(int))); //connect(d->stream, SIGNAL(sslCertificateReady(QSSLCert)), SLOT(streamSSLCertificateReady(QSSLCert))); connect(d->stream, SIGNAL(readyRead()), SLOT(streamReadyRead())); //connect(d->stream, SIGNAL(closeFinished()), SLOT(streamCloseFinished())); connect(d->stream, SIGNAL(incomingXml(QString)), SLOT(streamIncomingXml(QString))); connect(d->stream, SIGNAL(outgoingXml(QString)), SLOT(streamOutgoingXml(QString))); connect(d->stream, SIGNAL(haveUnhandledFeatures()), SLOT(parseUnhandledStreamFeatures())); d->stream->connectToServer(j, auth); } void Client::start(const QString &host, const QString &user, const QString &pass, const QString &_resource) { // TODO d->host = host; d->user = user; d->pass = pass; d->resource = _resource; Status stat; stat.setIsAvailable(false); d->resourceList += Resource(resource(), stat); JT_PushPresence *pp = new JT_PushPresence(rootTask()); connect(pp, SIGNAL(subscription(Jid,QString,QString)), SLOT(ppSubscription(Jid,QString,QString))); connect(pp, SIGNAL(presence(Jid,Status)), SLOT(ppPresence(Jid,Status))); JT_PushMessage *pm = new JT_PushMessage(rootTask()); connect(pm, SIGNAL(message(Message)), SLOT(pmMessage(Message))); JT_PushRoster *pr = new JT_PushRoster(rootTask()); connect(pr, SIGNAL(roster(Roster)), SLOT(prRoster(Roster))); new JT_ServInfo(rootTask()); new JT_PongServer(rootTask()); d->active = true; } void Client::setFileTransferEnabled(bool b) { if(b) { if(!d->ftman) d->ftman = new FileTransferManager(this); } else { if(d->ftman) { delete d->ftman; d->ftman = 0; } } } FileTransferManager *Client::fileTransferManager() const { return d->ftman; } S5BManager *Client::s5bManager() const { return d->s5bman; } IBBManager *Client::ibbManager() const { return d->ibbman; } BoBManager *Client::bobManager() const { return d->bobman; } CapsManager *Client::capsManager() const { return d->capsman; } bool Client::isActive() const { return d->active; } QString Client::groupChatPassword(const QString& host, const QString& room) const { Jid jid(room + "@" + host); foreach(GroupChat i, d->groupChatList) { if(i.j.compare(jid, false)) { return i.password; } } return QString(); } void Client::groupChatChangeNick(const QString &host, const QString &room, const QString &nick, const Status &_s) { Jid jid(room + "@" + host + "/" + nick); for(QList::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end(); it++) { GroupChat &i = *it; if(i.j.compare(jid, false)) { i.j = jid; Status s = _s; s.setIsAvailable(true); JT_Presence *j = new JT_Presence(rootTask()); j->pres(jid, s); j->go(true); break; } } } bool Client::groupChatJoin(const QString &host, const QString &room, const QString &nick, const QString& password, int maxchars, int maxstanzas, int seconds, const QDateTime &since, const Status& _s) { Jid jid(room + "@" + host + "/" + nick); for(QList::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end();) { GroupChat &i = *it; if(i.j.compare(jid, false)) { // if this room is shutting down, then free it up if(i.status == GroupChat::Closing) it = d->groupChatList.erase(it); else return false; } else ++it; } debug(QString("Client: Joined: [%1]\n").arg(jid.full())); GroupChat i; i.j = jid; i.status = GroupChat::Connecting; i.password = password; d->groupChatList += i; JT_Presence *j = new JT_Presence(rootTask()); Status s = _s; s.setMUC(); s.setMUCHistory(maxchars, maxstanzas, seconds, since); if (!password.isEmpty()) { s.setMUCPassword(password); } j->pres(jid,s); j->go(true); return true; } void Client::groupChatSetStatus(const QString &host, const QString &room, const Status &_s) { Jid jid(room + "@" + host); bool found = false; foreach (const GroupChat &i, d->groupChatList) { if(i.j.compare(jid, false)) { found = true; jid = i.j; break; } } if(!found) return; Status s = _s; s.setIsAvailable(true); JT_Presence *j = new JT_Presence(rootTask()); j->pres(jid, s); j->go(true); } void Client::groupChatLeave(const QString &host, const QString &room, const QString &statusStr) { Jid jid(room + "@" + host); for(QList::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end(); it++) { GroupChat &i = *it; if(!i.j.compare(jid, false)) continue; i.status = GroupChat::Closing; debug(QString("Client: Leaving: [%1]\n").arg(i.j.full())); JT_Presence *j = new JT_Presence(rootTask()); Status s; s.setIsAvailable(false); s.setStatus(statusStr); j->pres(i.j, s); j->go(true); } } void Client::groupChatLeaveAll(const QString &statusStr) { if (d->stream && d->active) { for(QList::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end(); it++) { GroupChat &i = *it; i.status = GroupChat::Closing; JT_Presence *j = new JT_Presence(rootTask()); Status s; s.setIsAvailable(false); s.setStatus(statusStr); j->pres(i.j, s); j->go(true); } } } QString Client::groupChatNick(const QString &host, const QString &room) const { Jid jid(room + "@" + host); foreach (const GroupChat &gc, d->groupChatList) { if (gc.j.compare(jid, false)) { return gc.j.resource(); } } return QString(); } /*void Client::start() { if(d->stream->old()) { // old has no activation step d->active = true; activated(); } else { // TODO: IM session } }*/ // TODO: fast close void Client::close(bool) { //fprintf(stderr, "\tClient::close\n"); //fflush(stderr); if(d->stream) { d->stream->disconnect(this); d->stream->close(); d->stream = 0; } disconnected(); cleanup(); } void Client::cleanup() { d->active = false; //d->authed = false; d->groupChatList.clear(); } /*void Client::continueAfterCert() { d->stream->continueAfterCert(); } void Client::streamConnected() { connected(); } void Client::streamHandshaken() { handshaken(); }*/ void Client::streamError(int) { //StreamError e = err; //error(e); //if(!e.isWarning()) { disconnected(); cleanup(); //} } /*void Client::streamSSLCertificateReady(const QSSLCert &cert) { sslCertReady(cert); } void Client::streamCloseFinished() { closeFinished(); }*/ static QDomElement oldStyleNS(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; QDomElement i; int x; //if(noShowNS) i = e.ownerDocument().createElement(e.tagName()); //else // i = e.ownerDocument().createElementNS(e.namespaceURI(), e.tagName()); // copy attributes QDomNamedNodeMap al = e.attributes(); for(x = 0; x < al.count(); ++x) i.setAttributeNode(al.item(x).cloneNode().toAttr()); if(!noShowNS) i.setAttribute("xmlns", e.namespaceURI()); // copy children QDomNodeList nl = e.childNodes(); for(x = 0; x < nl.count(); ++x) { QDomNode n = nl.item(x); if(n.isElement()) i.appendChild(oldStyleNS(n.toElement())); else i.appendChild(n.cloneNode()); } return i; } void Client::streamReadyRead() { //fprintf(stderr, "\tClientStream::streamReadyRead\n"); //fflush(stderr); while(d->stream && d->stream->stanzaAvailable()) { Stanza s = d->stream->read(); QString out = s.toString(); debug(QString("Client: incoming: [\n%1]\n").arg(out)); emit xmlIncoming(out); QDomElement x = oldStyleNS(s.element()); distribute(x); } } void Client::streamIncomingXml(const QString &s) { QString str = s; if(str.at(str.length()-1) != '\n') str += '\n'; emit xmlIncoming(str); } void Client::streamOutgoingXml(const QString &s) { QString str = s; if(str.at(str.length()-1) != '\n') str += '\n'; emit xmlOutgoing(str); } void Client::parseUnhandledStreamFeatures() { QList nl = d->stream->unhandledFeatures(); foreach (const QDomElement &e, nl) { if (e.localName() == "c" && e.namespaceURI() == NS_CAPS) { d->serverCaps = CapsSpec::fromXml(e); if (d->capsman->isEnabled()) { d->capsman->updateCaps(Jid(d->stream->jid().domain()), d->serverCaps); } } } } void Client::debug(const QString &str) { emit debugText(str); } QString Client::genUniqueId() { QString s; s.sprintf("a%x", d->id_seed); d->id_seed += 0x10; return s; } Task *Client::rootTask() { return d->root; } QDomDocument *Client::doc() const { return &d->doc; } void Client::distribute(const QDomElement &x) { if(x.hasAttribute("from")) { Jid j(x.attribute("from")); if(!j.isValid()) { debug("Client: bad 'from' JID\n"); return; } } if(!rootTask()->take(x) && (x.attribute("type") == "get" || x.attribute("type") == "set") ) { debug("Client: Unrecognized IQ.\n"); // Create reply element QDomElement reply = createIQ(doc(), "error", x.attribute("from"), x.attribute("id")); // Copy children for (QDomNode n = x.firstChild(); !n.isNull(); n = n.nextSibling()) { reply.appendChild(n.cloneNode()); } // Add error QDomElement error = doc()->createElement("error"); error.setAttribute("type","cancel"); reply.appendChild(error); QDomElement error_type = doc()->createElement("feature-not-implemented"); error_type.setAttribute("xmlns","urn:ietf:params:xml:ns:xmpp-stanzas"); error.appendChild(error_type); send(reply); } } void Client::send(const QDomElement &x) { if(!d->stream) return; //QString out; //QTextStream ts(&out, IO_WriteOnly); //x.save(ts, 0); //QString out = Stream::xmlToString(x); //debug(QString("Client: outgoing: [\n%1]\n").arg(out)); //xmlOutgoing(out); QDomElement e = addCorrectNS(x); Stanza s = d->stream->createStanza(e); if(s.isNull()) { //printf("bad stanza??\n"); return; } emit stanzaElementOutgoing(e); QString out = s.toString(); //qWarning() << "Out: " << out; debug(QString("Client: outgoing: [\n%1]\n").arg(out)); emit xmlOutgoing(out); //printf("x[%s] x2[%s] s[%s]\n", Stream::xmlToString(x).toLatin1(), Stream::xmlToString(e).toLatin1(), s.toString().toLatin1()); d->stream->write(s); } void Client::send(const QString &str) { if(!d->stream) return; debug(QString("Client: outgoing: [\n%1]\n").arg(str)); emit xmlOutgoing(str); static_cast(d->stream)->writeDirect(str); } Stream & Client::stream() { return *(d->stream.data()); } QString Client::streamBaseNS() const { return d->stream->baseNS(); } const LiveRoster & Client::roster() const { return d->roster; } const ResourceList & Client::resourceList() const { return d->resourceList; } QString Client::host() const { return d->host; } QString Client::user() const { return d->user; } QString Client::pass() const { return d->pass; } QString Client::resource() const { return d->resource; } Jid Client::jid() const { QString s; if(!d->user.isEmpty()) s += d->user + '@'; s += d->host; if(!d->resource.isEmpty()) { s += '/'; s += d->resource; } return Jid(s); } void Client::ppSubscription(const Jid &j, const QString &s, const QString& n) { emit subscription(j, s, n); } void Client::ppPresence(const Jid &j, const Status &s) { if(s.isAvailable()) debug(QString("Client: %1 is available.\n").arg(j.full())); else debug(QString("Client: %1 is unavailable.\n").arg(j.full())); for(QList::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end(); it++) { GroupChat &i = *it; if(i.j.compare(j, false)) { bool us = (i.j.resource() == j.resource() || j.resource().isEmpty()) ? true: false; debug(QString("for groupchat i=[%1] pres=[%2], [us=%3].\n").arg(i.j.full()).arg(j.full()).arg(us)); switch(i.status) { case GroupChat::Connecting: if(us && s.hasError()) { Jid j = i.j; d->groupChatList.erase(it); emit groupChatError(j, s.errorCode(), s.errorString()); } else { // don't signal success unless it is a non-error presence if(!s.hasError()) { i.status = GroupChat::Connected; emit groupChatJoined(i.j); } emit groupChatPresence(j, s); } break; case GroupChat::Connected: emit groupChatPresence(j, s); break; case GroupChat::Closing: if(us && !s.isAvailable()) { Jid j = i.j; d->groupChatList.erase(it); emit groupChatLeft(j); } break; default: break; } return; } } if(s.hasError()) { emit presenceError(j, s.errorCode(), s.errorString()); return; } // is it me? if(j.compare(jid(), false)) { updateSelfPresence(j, s); } else { // update all relavent roster entries for(LiveRoster::Iterator it = d->roster.begin(); it != d->roster.end(); ++it) { LiveRosterItem &i = *it; if(!i.jid().compare(j, false)) continue; // roster item has its own resource? if(!i.jid().resource().isEmpty()) { if(i.jid().resource() != j.resource()) continue; } updatePresence(&i, j, s); } } } void Client::updateSelfPresence(const Jid &j, const Status &s) { ResourceList::Iterator rit = d->resourceList.find(j.resource()); bool found = (rit == d->resourceList.end()) ? false: true; // unavailable? remove the resource if(!s.isAvailable()) { if(found) { debug(QString("Client: Removing self resource: name=[%1]\n").arg(j.resource())); (*rit).setStatus(s); emit resourceUnavailable(j, *rit); d->resourceList.erase(rit); } } // available? add/update the resource else { Resource r; if(!found) { r = Resource(j.resource(), s); d->resourceList += r; debug(QString("Client: Adding self resource: name=[%1]\n").arg(j.resource())); } else { (*rit).setStatus(s); r = *rit; debug(QString("Client: Updating self resource: name=[%1]\n").arg(j.resource())); } emit resourceAvailable(j, r); } } void Client::updatePresence(LiveRosterItem *i, const Jid &j, const Status &s) { ResourceList::Iterator rit = i->resourceList().find(j.resource()); bool found = (rit == i->resourceList().end()) ? false: true; // unavailable? remove the resource if(!s.isAvailable()) { if(found) { (*rit).setStatus(s); debug(QString("Client: Removing resource from [%1]: name=[%2]\n").arg(i->jid().full()).arg(j.resource())); emit resourceUnavailable(j, *rit); i->resourceList().erase(rit); i->setLastUnavailableStatus(s); } else { // create the resource just for the purpose of emit Resource r = Resource(j.resource(), s); i->resourceList() += r; rit = i->resourceList().find(j.resource()); emit resourceUnavailable(j, *rit); i->resourceList().erase(rit); i->setLastUnavailableStatus(s); } } // available? add/update the resource else { Resource r; if(!found) { r = Resource(j.resource(), s); i->resourceList() += r; debug(QString("Client: Adding resource to [%1]: name=[%2]\n").arg(i->jid().full()).arg(j.resource())); } else { (*rit).setStatus(s); r = *rit; debug(QString("Client: Updating resource to [%1]: name=[%2]\n").arg(i->jid().full()).arg(j.resource())); } emit resourceAvailable(j, r); } } void Client::pmMessage(const Message &m) { debug(QString("Client: Message from %1\n").arg(m.from().full())); // bits of binary. we can't do this in Message, since it knows nothing about Client foreach (const BoBData &b, m.bobDataList()) { d->bobman->append(b); } if (!m.ibbData().data.isEmpty()) { d->ibbman->takeIncomingData(m.from(), m.id(), m.ibbData(), Stanza::Message); } if(m.type() == "groupchat") { for(QList::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end(); it++) { const GroupChat &i = *it; if(!i.j.compare(m.from(), false)) continue; if(i.status == GroupChat::Connected) messageReceived(m); } } else messageReceived(m); } void Client::prRoster(const Roster &r) { importRoster(r); } void Client::rosterRequest() { if(!d->active) return; JT_Roster *r = new JT_Roster(rootTask()); connect(r, SIGNAL(finished()), SLOT(slotRosterRequestFinished())); r->get(); d->roster.flagAllForDelete(); // mod_groups patch r->go(true); } void Client::slotRosterRequestFinished() { JT_Roster *r = (JT_Roster *)sender(); // on success, let's take it if(r->success()) { //d->roster.flagAllForDelete(); // mod_groups patch importRoster(r->roster()); for(LiveRoster::Iterator it = d->roster.begin(); it != d->roster.end();) { LiveRosterItem &i = *it; if(i.flagForDelete()) { emit rosterItemRemoved(i); it = d->roster.erase(it); } else ++it; } } else { // don't report a disconnect. Client::error() will do that. if(r->statusCode() == Task::ErrDisc) return; } // report success / fail emit rosterRequestFinished(r->success(), r->statusCode(), r->statusString()); } void Client::importRoster(const Roster &r) { emit beginImportRoster(); for(Roster::ConstIterator it = r.begin(); it != r.end(); ++it) { importRosterItem(*it); } emit endImportRoster(); } void Client::importRosterItem(const RosterItem &item) { QString substr; switch(item.subscription().type()) { case Subscription::Both: substr = "<-->"; break; case Subscription::From: substr = " ->"; break; case Subscription::To: substr = "<- "; break; case Subscription::Remove: substr = "xxxx"; break; case Subscription::None: default: substr = "----"; break; } QString dstr, str; str.sprintf(" %s %-32s", qPrintable(substr), qPrintable(item.jid().full())); if(!item.name().isEmpty()) str += QString(" [") + item.name() + "]"; str += '\n'; // Remove if(item.subscription().type() == Subscription::Remove) { LiveRoster::Iterator it = d->roster.find(item.jid()); if(it != d->roster.end()) { emit rosterItemRemoved(*it); d->roster.erase(it); } dstr = "Client: (Removed) "; } // Add/Update else { LiveRoster::Iterator it = d->roster.find(item.jid()); if(it != d->roster.end()) { LiveRosterItem &i = *it; i.setFlagForDelete(false); i.setRosterItem(item); emit rosterItemUpdated(i); dstr = "Client: (Updated) "; } else { LiveRosterItem i(item); d->roster += i; // signal it emit rosterItemAdded(i); dstr = "Client: (Added) "; } } debug(dstr + str); } void Client::sendMessage(const Message &m) { JT_Message *j = new JT_Message(rootTask(), m); j->go(true); } void Client::sendSubscription(const Jid &jid, const QString &type, const QString& nick) { JT_Presence *j = new JT_Presence(rootTask()); j->sub(jid, type, nick); j->go(true); } void Client::setPresence(const Status &s) { if (d->capsman->isEnabled()) { if (d->caps.version().isEmpty() && !d->caps.node().isEmpty()) { d->caps = CapsSpec(makeDiscoResult(d->caps.node())); /* recompute caps hash */ } } JT_Presence *j = new JT_Presence(rootTask()); j->pres(s); j->go(true); // update our resourceList ppPresence(jid(), s); //ResourceList::Iterator rit = d->resourceList.find(resource()); //Resource &r = *rit; //r.setStatus(s); } QString Client::OSName() const { return d->osName; } QString Client::OSVersion() const { return d->osVersion; } QString Client::timeZone() const { return d->tzname; } int Client::timeZoneOffset() const { return d->tzoffset; } /** \brief Returns true if Client is using old, manual time zone conversions. By default, conversions between UTC and local time are done automatically by Qt. In this mode, manualTimeZoneOffset() returns true, and timeZoneOffset() always retuns 0 (so you shouldn't use that function). However, if you call setTimeZone(), Client instance switches to old mode and uses given time zone offset for all calculations. */ bool Client::manualTimeZoneOffset() const { return d->useTzoffset; } QString Client::clientName() const { return d->clientName; } QString Client::clientVersion() const { return d->clientVersion; } CapsSpec Client::caps() const { return d->caps; } CapsSpec Client::serverCaps() const { return d->serverCaps; } void Client::setOSName(const QString &name) { d->osName = name; } void Client::setOSVersion(const QString &version) { d->osVersion = version; } void Client::setTimeZone(const QString &name, int offset) { d->tzname = name; d->tzoffset = offset; d->useTzoffset = true; } void Client::setClientName(const QString &s) { d->clientName = s; } void Client::setClientVersion(const QString &s) { d->clientVersion = s; } void Client::setCaps(const CapsSpec &s) { d->caps = s; } DiscoItem::Identity Client::identity() const { return d->identity; } void Client::setIdentity(const DiscoItem::Identity &identity) { if (!(d->identity == identity)) { d->caps.resetVersion(); } d->identity = identity; } void Client::setFeatures(const Features& f) { if (!(d->features == f)) { d->caps.resetVersion(); } d->features = f; } const Features& Client::features() const { return d->features; } DiscoItem Client::makeDiscoResult(const QString &node) const { DiscoItem item; item.setNode(node); DiscoItem::Identity id = identity(); if (id.category.isEmpty() || id.type.isEmpty()) { id.category = "client"; id.type = "pc"; } item.setIdentities(id); Features features; if (d->ftman) { features.addFeature("http://jabber.org/protocol/bytestreams"); features.addFeature("http://jabber.org/protocol/ibb"); features.addFeature("http://jabber.org/protocol/si"); features.addFeature("http://jabber.org/protocol/si/profile/file-transfer"); } features.addFeature("http://jabber.org/protocol/disco#info"); features.addFeature("jabber:x:data"); features.addFeature("urn:xmpp:bob"); features.addFeature("urn:xmpp:ping"); features.addFeature("urn:xmpp:time"); features.addFeature("urn:xmpp:message-correct:0"); // Client-specific features foreach (const QString & i, d->features.list()) { features.addFeature(i); } item.setFeatures(features); // xep-0232 Software Information XData si; XData::FieldList si_fields; XData::Field si_type_field; si_type_field.setType(XData::Field::Field_Hidden); si_type_field.setVar("FORM_TYPE"); si_type_field.setValue(QStringList(QLatin1String("urn:xmpp:dataforms:softwareinfo"))); si_fields.append(si_type_field); XData::Field software_field; software_field.setType(XData::Field::Field_TextSingle); software_field.setVar("software"); software_field.setValue(QStringList(d->clientName)); si_fields.append(software_field); XData::Field software_v_field; software_v_field.setType(XData::Field::Field_TextSingle); software_v_field.setVar("software_version"); software_v_field.setValue(QStringList(d->clientVersion)); si_fields.append(software_v_field); XData::Field os_field; os_field.setType(XData::Field::Field_TextSingle); os_field.setVar("os"); os_field.setValue(QStringList(d->osName)); si_fields.append(os_field); XData::Field os_v_field; os_v_field.setType(XData::Field::Field_TextSingle); os_v_field.setVar("os_version"); os_v_field.setValue(QStringList(d->osVersion)); si_fields.append(os_v_field); si.setType(XData::Data_Result); si.setFields(si_fields); item.setExtensions(QList() << si); return item; } void Client::s5b_incomingReady() { handleIncoming(d->s5bman->takeIncoming()); } void Client::ibb_incomingReady() { handleIncoming(d->ibbman->takeIncoming()); } void Client::handleIncoming(BSConnection *c) { if(!c) return; if(!d->ftman) { c->close(); c->deleteLater(); return; } d->ftman->stream_incomingReady(c); } void Client::handleSMAckResponse(int h) { qDebug() << "handleSMAckResponse: h = " << h; } //--------------------------------------------------------------------------- // LiveRosterItem //--------------------------------------------------------------------------- LiveRosterItem::LiveRosterItem(const Jid &jid) :RosterItem(jid) { setFlagForDelete(false); } LiveRosterItem::LiveRosterItem(const RosterItem &i) { setRosterItem(i); setFlagForDelete(false); } LiveRosterItem::~LiveRosterItem() { } void LiveRosterItem::setRosterItem(const RosterItem &i) { setJid(i.jid()); setName(i.name()); setGroups(i.groups()); setSubscription(i.subscription()); setAsk(i.ask()); setIsPush(i.isPush()); } ResourceList & LiveRosterItem::resourceList() { return v_resourceList; } ResourceList::Iterator LiveRosterItem::priority() { return v_resourceList.priority(); } const ResourceList & LiveRosterItem::resourceList() const { return v_resourceList; } ResourceList::ConstIterator LiveRosterItem::priority() const { return v_resourceList.priority(); } bool LiveRosterItem::isAvailable() const { if(v_resourceList.count() > 0) return true; return false; } const Status & LiveRosterItem::lastUnavailableStatus() const { return v_lastUnavailableStatus; } bool LiveRosterItem::flagForDelete() const { return v_flagForDelete; } void LiveRosterItem::setLastUnavailableStatus(const Status &s) { v_lastUnavailableStatus = s; } void LiveRosterItem::setFlagForDelete(bool b) { v_flagForDelete = b; } //--------------------------------------------------------------------------- // LiveRoster //--------------------------------------------------------------------------- LiveRoster::LiveRoster() :QList() { } LiveRoster::~LiveRoster() { } void LiveRoster::flagAllForDelete() { for(Iterator it = begin(); it != end(); ++it) (*it).setFlagForDelete(true); } LiveRoster::Iterator LiveRoster::find(const Jid &j, bool compareRes) { Iterator it; for(it = begin(); it != end(); ++it) { if((*it).jid().compare(j, compareRes)) break; } return it; } LiveRoster::ConstIterator LiveRoster::find(const Jid &j, bool compareRes) const { ConstIterator it; for(it = begin(); it != end(); ++it) { if((*it).jid().compare(j, compareRes)) break; } return it; } } + +#include "moc_xmpp_client.cpp"