diff options
| author | Jeremy Lainé <jeremy.laine@m4x.org> | 2012-02-08 11:53:52 +0000 |
|---|---|---|
| committer | Jeremy Lainé <jeremy.laine@m4x.org> | 2012-02-08 11:53:52 +0000 |
| commit | 36bbeb02497f5f79ddae56857893c2f71bf08ee9 (patch) | |
| tree | 34191bb93ff6cb044d5d28d2f630dc0476cc5659 /src/server | |
| parent | aa334abcca63101292c48a72c7b1851b8ecc26c7 (diff) | |
move server code to "server" directory
Diffstat (limited to 'src/server')
| -rw-r--r-- | src/server/QXmppDialback.cpp | 117 | ||||
| -rw-r--r-- | src/server/QXmppDialback.h | 69 | ||||
| -rw-r--r-- | src/server/QXmppIncomingClient.cpp | 445 | ||||
| -rw-r--r-- | src/server/QXmppIncomingClient.h | 73 | ||||
| -rw-r--r-- | src/server/QXmppIncomingServer.cpp | 207 | ||||
| -rw-r--r-- | src/server/QXmppIncomingServer.h | 69 | ||||
| -rw-r--r-- | src/server/QXmppOutgoingServer.cpp | 331 | ||||
| -rw-r--r-- | src/server/QXmppOutgoingServer.h | 82 | ||||
| -rw-r--r-- | src/server/QXmppServer.cpp | 807 | ||||
| -rw-r--r-- | src/server/QXmppServer.h | 148 | ||||
| -rw-r--r-- | src/server/QXmppServerExtension.cpp | 155 | ||||
| -rw-r--r-- | src/server/QXmppServerExtension.h | 79 | ||||
| -rw-r--r-- | src/server/QXmppServerPlugin.h | 60 |
13 files changed, 2642 insertions, 0 deletions
diff --git a/src/server/QXmppDialback.cpp b/src/server/QXmppDialback.cpp new file mode 100644 index 00000000..f9100869 --- /dev/null +++ b/src/server/QXmppDialback.cpp @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#include <QDomElement> + +#include "QXmppConstants.h" +#include "QXmppDialback.h" +#include "QXmppUtils.h" + +/// Constructs a QXmppDialback. + +QXmppDialback::QXmppDialback() + : m_command(Result) +{ +} + +/// Returns the dialback command. + +QXmppDialback::Command QXmppDialback::command() const +{ + return m_command; +} + +/// Sets the dialback command. +/// +/// \param command + +void QXmppDialback::setCommand(QXmppDialback::Command command) +{ + m_command = command; +} + +/// Returns the dialback key. + +QString QXmppDialback::key() const +{ + return m_key; +} + +/// Sets the dialback key. +/// +/// \param key + +void QXmppDialback::setKey(const QString &key) +{ + m_key = key; +} + +/// Returns the dialback type. + +QString QXmppDialback::type() const +{ + return m_type; +} + +/// Sets the dialback type. +/// +/// \param type + +void QXmppDialback::setType(const QString &type) +{ + m_type = type; +} + +bool QXmppDialback::isDialback(const QDomElement &element) +{ + return element.namespaceURI() == ns_server_dialback && + (element.tagName() == QLatin1String("result") || + element.tagName() == QLatin1String("verify")); +} + +void QXmppDialback::parse(const QDomElement &element) +{ + QXmppStanza::parse(element); + if (element.tagName() == QLatin1String("result")) + m_command = Result; + else + m_command = Verify; + m_type = element.attribute("type"); + m_key = element.text(); +} + +void QXmppDialback::toXml(QXmlStreamWriter *xmlWriter) const +{ + if (m_command == Result) + xmlWriter->writeStartElement("db:result"); + else + xmlWriter->writeStartElement("db:verify"); + helperToXmlAddAttribute(xmlWriter, "id", id()); + helperToXmlAddAttribute(xmlWriter, "to", to()); + helperToXmlAddAttribute(xmlWriter, "from", from()); + helperToXmlAddAttribute(xmlWriter, "type", m_type); + if (!m_key.isEmpty()) + xmlWriter->writeCharacters(m_key); + xmlWriter->writeEndElement(); +} + diff --git a/src/server/QXmppDialback.h b/src/server/QXmppDialback.h new file mode 100644 index 00000000..28f01f81 --- /dev/null +++ b/src/server/QXmppDialback.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#ifndef QXMPPDIALBACK_H +#define QXMPPDIALBACK_H + +#include "QXmppStanza.h" + +/// \brief The QXmppDialback class represents a stanza used for the Server +/// Dialback protocol as specified by XEP-0220: Server Dialback. +/// +/// \ingroup Stanzas + +class QXmppDialback : public QXmppStanza +{ +public: + /// This enum is used to describe a dialback command. + enum Command { + Result, ///< A dialback command between the originating server + ///< and the receiving server. + Verify, ///< A dialback command between the receiving server + ///< and the authoritative server. + }; + + QXmppDialback(); + + Command command() const; + void setCommand(Command command); + + QString key() const; + void setKey(const QString &key); + + QString type() const; + void setType(const QString &type); + + /// \cond + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + + static bool isDialback(const QDomElement &element); + /// \endcond + +private: + Command m_command; + QString m_key; + QString m_type; +}; + +#endif diff --git a/src/server/QXmppIncomingClient.cpp b/src/server/QXmppIncomingClient.cpp new file mode 100644 index 00000000..7338115a --- /dev/null +++ b/src/server/QXmppIncomingClient.cpp @@ -0,0 +1,445 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#include <QDomElement> +#include <QSslKey> +#include <QSslSocket> +#include <QTimer> + +#include "QXmppBindIq.h" +#include "QXmppConstants.h" +#include "QXmppMessage.h" +#include "QXmppPasswordChecker.h" +#include "QXmppSaslAuth.h" +#include "QXmppSessionIq.h" +#include "QXmppStreamFeatures.h" +#include "QXmppUtils.h" + +#include "QXmppIncomingClient.h" + +class QXmppIncomingClientPrivate +{ +public: + QTimer *idleTimer; + + QString domain; + QString username; + QString resource; + QString jid; + QXmppPasswordChecker *passwordChecker; + QXmppSaslDigestMd5 saslDigest; + int saslDigestStep; + QString saslDigestUsername; +}; + +/// Constructs a new incoming client stream. +/// +/// \param socket The socket for the XMPP stream. +/// \param domain The local domain. +/// \param parent The parent QObject for the stream (optional). +/// + +QXmppIncomingClient::QXmppIncomingClient(QSslSocket *socket, const QString &domain, QObject *parent) + : QXmppStream(parent), + d(new QXmppIncomingClientPrivate) +{ + d->passwordChecker = 0; + d->domain = domain; + d->saslDigestStep = 0; + + if (socket) { + info(QString("Incoming client connection from %1 %2").arg( + socket->peerAddress().toString(), + QString::number(socket->peerPort()))); + setSocket(socket); + } + + // create inactivity timer + d->idleTimer = new QTimer(this); + d->idleTimer->setSingleShot(true); + bool check = connect(d->idleTimer, SIGNAL(timeout()), + this, SLOT(onTimeout())); + Q_ASSERT(check); + Q_UNUSED(check); +} + +/// Destroys the current stream. +/// + +QXmppIncomingClient::~QXmppIncomingClient() +{ + delete d; +} + +/// Returns true if the socket is connected, the client is authenticated +/// and a resource is bound. +/// + +bool QXmppIncomingClient::isConnected() const +{ + return QXmppStream::isConnected() && + !d->username.isEmpty() && + !d->resource.isEmpty(); +} + +/// Returns the client's JID. +/// + +QString QXmppIncomingClient::jid() const +{ + return d->jid; +} + +/// Sets the number of seconds after which a client will be disconnected +/// for inactivity. + +void QXmppIncomingClient::setInactivityTimeout(int secs) +{ + d->idleTimer->stop(); + d->idleTimer->setInterval(secs * 1000); + if (d->idleTimer->interval()) + d->idleTimer->start(); +} + +/// Sets the password checker used to verify client credentials. +/// +/// \param checker +/// + +void QXmppIncomingClient::setPasswordChecker(QXmppPasswordChecker *checker) +{ + d->passwordChecker = checker; +} + +void QXmppIncomingClient::handleStream(const QDomElement &streamElement) +{ + if (d->idleTimer->interval()) + d->idleTimer->start(); + d->saslDigestStep = 0; + d->saslDigestUsername.clear(); + + // start stream + const QByteArray sessionId = generateStanzaHash().toAscii(); + QString response = QString("<?xml version='1.0'?><stream:stream" + " xmlns=\"%1\" xmlns:stream=\"%2\"" + " id=\"%3\" from=\"%4\" version=\"1.0\" xml:lang=\"en\">").arg( + ns_client, + ns_stream, + sessionId, + d->domain.toAscii()); + sendData(response.toUtf8()); + + // check requested domain + if (streamElement.attribute("to") != d->domain) + { + QString response = QString("<stream:error>" + "<host-unknown xmlns=\"urn:ietf:params:xml:ns:xmpp-streams\"/>" + "<text xmlns=\"urn:ietf:params:xml:ns:xmpp-streams\">" + "This server does not serve %1" + "</text>" + "</stream:error>").arg(streamElement.attribute("to")); + sendData(response.toUtf8()); + disconnectFromHost(); + return; + } + + // send stream features + QXmppStreamFeatures features; + if (socket() && !socket()->isEncrypted() && !socket()->localCertificate().isNull() && !socket()->privateKey().isNull()) + features.setTlsMode(QXmppStreamFeatures::Enabled); + if (!d->username.isEmpty()) + { + features.setBindMode(QXmppStreamFeatures::Required); + features.setSessionMode(QXmppStreamFeatures::Enabled); + } + else if (d->passwordChecker) + { + QList<QXmppConfiguration::SASLAuthMechanism> mechanisms; + mechanisms << QXmppConfiguration::SASLPlain; + if (d->passwordChecker->hasGetPassword()) + mechanisms << QXmppConfiguration::SASLDigestMD5; + features.setAuthMechanisms(mechanisms); + } + sendPacket(features); +} + +void QXmppIncomingClient::handleStanza(const QDomElement &nodeRecv) +{ + const QString ns = nodeRecv.namespaceURI(); + + if (d->idleTimer->interval()) + d->idleTimer->start(); + + if (ns == ns_tls && nodeRecv.tagName() == QLatin1String("starttls")) + { + sendData("<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>"); + socket()->flush(); + socket()->startServerEncryption(); + return; + } + else if (ns == ns_sasl) + { + if (nodeRecv.tagName() == QLatin1String("auth")) + { + const QString mechanism = nodeRecv.attribute("mechanism"); + if (mechanism == QLatin1String("PLAIN")) + { + QList<QByteArray> auth = QByteArray::fromBase64(nodeRecv.text().toAscii()).split('\0'); + if (auth.size() != 3) + { + sendData("<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><incorrect-encoding/></failure>"); + disconnectFromHost(); + return; + } + + QXmppPasswordRequest request; + request.setDomain(d->domain); + request.setUsername(QString::fromUtf8(auth[1])); + request.setPassword(QString::fromUtf8(auth[2])); + if (!d->passwordChecker) { + // FIXME: what type of failure? + warning(QString("Cannot authenticate '%1', no password checker").arg(request.username())); + sendData("<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>"); + disconnectFromHost(); + return; + } + + QXmppPasswordReply *reply = d->passwordChecker->checkPassword(request); + reply->setParent(this); + reply->setProperty("__sasl_username", request.username()); + connect(reply, SIGNAL(finished()), this, SLOT(onPasswordReply())); + } + else if (mechanism == QLatin1String("DIGEST-MD5")) + { + // generate nonce + d->saslDigest.setNonce(QXmppSaslDigestMd5::generateNonce()); + d->saslDigest.setQop("auth"); + d->saslDigestStep = 1; + + QMap<QByteArray, QByteArray> challenge; + challenge["nonce"] = d->saslDigest.nonce(); + challenge["realm"] = d->domain.toUtf8(); + challenge["qop"] = d->saslDigest.qop(); + challenge["charset"] = "utf-8"; + challenge["algorithm"] = "md5-sess"; + + const QByteArray data = QXmppSaslDigestMd5::serializeMessage(challenge).toBase64(); + sendData("<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" + data +"</challenge>"); + } + else + { + // unsupported method + sendData("<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'></failure>"); + disconnectFromHost(); + return; + } + } + else if (nodeRecv.tagName() == QLatin1String("response")) + { + if (d->saslDigestStep == 1) + { + const QByteArray raw = QByteArray::fromBase64(nodeRecv.text().toAscii()); + QMap<QByteArray, QByteArray> saslResponse = QXmppSaslDigestMd5::parseMessage(raw); + + // check credentials + const QString username = QString::fromUtf8(saslResponse.value("username")); + if (!d->passwordChecker) { + // FIXME: what type of failure? + warning(QString("Cannot authenticate '%1', no password checker").arg(username)); + sendData("<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>"); + disconnectFromHost(); + return; + } + + QXmppPasswordRequest request; + request.setUsername(username); + request.setDomain(d->domain); + + QXmppPasswordReply *reply = d->passwordChecker->getDigest(request); + reply->setParent(this); + reply->setProperty("__sasl_raw", raw); + connect(reply, SIGNAL(finished()), this, SLOT(onDigestReply())); + } + else if (d->saslDigestStep == 2) + { + // authentication succeeded + d->saslDigestStep = 3; + d->username = d->saslDigestUsername; + d->jid = QString("%1@%2").arg(d->username, d->domain); + info(QString("Authentication succeeded for '%1'").arg(d->username)); + sendData("<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>"); + handleStart(); + } + } + } + else if (ns == ns_client) + { + if (nodeRecv.tagName() == QLatin1String("iq")) + { + const QString type = nodeRecv.attribute("type"); + if (QXmppBindIq::isBindIq(nodeRecv) && type == QLatin1String("set")) + { + QXmppBindIq bindSet; + bindSet.parse(nodeRecv); + d->resource = bindSet.resource().trimmed(); + if (d->resource.isEmpty()) + d->resource = generateStanzaHash(); + d->jid = QString("%1@%2/%3").arg(d->username, d->domain, d->resource); + + QXmppBindIq bindResult; + bindResult.setType(QXmppIq::Result); + bindResult.setId(bindSet.id()); + bindResult.setJid(d->jid); + sendPacket(bindResult); + + // bound + emit connected(); + return; + } + else if (QXmppSessionIq::isSessionIq(nodeRecv) && type == QLatin1String("set")) + { + QXmppSessionIq sessionSet; + sessionSet.parse(nodeRecv); + + QXmppIq sessionResult; + sessionResult.setType(QXmppIq::Result); + sessionResult.setId(sessionSet.id()); + sessionResult.setTo(d->jid); + sendPacket(sessionResult); + return; + } + } + + // check the sender is legitimate + const QString from = nodeRecv.attribute("from"); + if (!from.isEmpty() && from != d->jid && from != jidToBareJid(d->jid)) + { + warning(QString("Received a stanza from unexpected JID %1").arg(from)); + return; + } + + // process unhandled stanzas + if (nodeRecv.tagName() == QLatin1String("iq") || + nodeRecv.tagName() == QLatin1String("message") || + nodeRecv.tagName() == QLatin1String("presence")) + { + QDomElement nodeFull(nodeRecv); + + // if the sender is empty, set it to the appropriate JID + if (nodeFull.attribute("from").isEmpty()) + { + if (nodeFull.tagName() == QLatin1String("presence") && + (nodeFull.attribute("type") == QLatin1String("subscribe") || + nodeFull.attribute("type") == QLatin1String("subscribed"))) + nodeFull.setAttribute("from", jidToBareJid(d->jid)); + else + nodeFull.setAttribute("from", d->jid); + } + + // if the recipient is empty, set it to the local domain + if (nodeFull.attribute("to").isEmpty()) + nodeFull.setAttribute("to", d->domain); + + // emit stanza for processing by server + emit elementReceived(nodeFull); + } + } +} + +void QXmppIncomingClient::onDigestReply() +{ + QXmppPasswordReply *reply = qobject_cast<QXmppPasswordReply*>(sender()); + if (!reply) + return; + reply->deleteLater(); + + const QMap<QByteArray, QByteArray> saslResponse = QXmppSaslDigestMd5::parseMessage(reply->property("__sasl_raw").toByteArray()); + const QString username = QString::fromUtf8(saslResponse.value("username")); + if (reply->error() == QXmppPasswordReply::TemporaryError) { + warning(QString("Temporary authentication failure for '%1'").arg(username)); + sendData("<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><temporary-auth-failure/></failure>"); + disconnectFromHost(); + return; + } + + d->saslDigest.setSecret(reply->digest()); + d->saslDigest.setDigestUri(saslResponse.value("digest-uri")); + d->saslDigest.setNc(saslResponse.value("nc")); + d->saslDigest.setCnonce(saslResponse.value("cnonce")); + if (saslResponse.value("response") != d->saslDigest.calculateDigest( + QByteArray("AUTHENTICATE:") + d->saslDigest.digestUri())) + { + sendData("<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><not-authorized/></failure>"); + disconnectFromHost(); + return; + } + + // send new challenge + d->saslDigestUsername = username; + d->saslDigestStep = 2; + QMap<QByteArray, QByteArray> challenge; + challenge["rspauth"] = d->saslDigest.calculateDigest( + QByteArray(":") + d->saslDigest.digestUri()); + const QByteArray data = QXmppSaslDigestMd5::serializeMessage(challenge).toBase64(); + sendData("<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" + data +"</challenge>"); +} + +void QXmppIncomingClient::onPasswordReply() +{ + QXmppPasswordReply *reply = qobject_cast<QXmppPasswordReply*>(sender()); + if (!reply) + return; + reply->deleteLater(); + + const QString username = reply->property("__sasl_username").toString(); + switch (reply->error()) { + case QXmppPasswordReply::NoError: + d->username = username; + d->jid = QString("%1@%2").arg(d->username, d->domain); + info(QString("Authentication succeeded for '%1'").arg(username)); + sendData("<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>"); + handleStart(); + break; + case QXmppPasswordReply::AuthorizationError: + warning(QString("Authentication failed for '%1'").arg(username)); + sendData("<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><not-authorized/></failure>"); + disconnectFromHost(); + break; + case QXmppPasswordReply::TemporaryError: + warning(QString("Temporary authentication failure for '%1'").arg(username)); + sendData("<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><temporary-auth-failure/></failure>"); + disconnectFromHost(); + break; + } +} + +void QXmppIncomingClient::onTimeout() +{ + warning(QString("Idle timeout for '%1'").arg(d->jid)); + disconnectFromHost(); + + // make sure disconnected() gets emitted no matter what + QTimer::singleShot(30, this, SIGNAL(disconnected())); +} + + diff --git a/src/server/QXmppIncomingClient.h b/src/server/QXmppIncomingClient.h new file mode 100644 index 00000000..793b8141 --- /dev/null +++ b/src/server/QXmppIncomingClient.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#ifndef QXMPPINCOMINGCLIENT_H +#define QXMPPINCOMINGCLIENT_H + +#include "QXmppStream.h" + +class QXmppIncomingClientPrivate; +class QXmppPasswordChecker; + +/// \brief Interface for password checkers. +/// + +/// \brief The QXmppIncomingClient class represents an incoming XMPP stream +/// from an XMPP client. +/// + +class QXmppIncomingClient : public QXmppStream +{ + Q_OBJECT + +public: + QXmppIncomingClient(QSslSocket *socket, const QString &domain, QObject *parent = 0); + ~QXmppIncomingClient(); + + bool isConnected() const; + QString jid() const; + + void setInactivityTimeout(int secs); + void setPasswordChecker(QXmppPasswordChecker *checker); + +signals: + /// This signal is emitted when an element is received. + void elementReceived(const QDomElement &element); + +protected: + /// \cond + void handleStream(const QDomElement &element); + void handleStanza(const QDomElement &element); + /// \endcond + +private slots: + void onDigestReply(); + void onPasswordReply(); + void onTimeout(); + +private: + Q_DISABLE_COPY(QXmppIncomingClient) + QXmppIncomingClientPrivate* const d; +}; + +#endif diff --git a/src/server/QXmppIncomingServer.cpp b/src/server/QXmppIncomingServer.cpp new file mode 100644 index 00000000..2c4cfb9f --- /dev/null +++ b/src/server/QXmppIncomingServer.cpp @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#include <QDomElement> +#include <QSslKey> +#include <QSslSocket> + +#include "QXmppConstants.h" +#include "QXmppDialback.h" +#include "QXmppIncomingServer.h" +#include "QXmppOutgoingServer.h" +#include "QXmppStreamFeatures.h" +#include "QXmppUtils.h" + +class QXmppIncomingServerPrivate +{ +public: + QSet<QString> authenticated; + QString domain; + QString localStreamId; +}; + +/// Constructs a new incoming server stream. +/// +/// \param socket The socket for the XMPP stream. +/// \param domain The local domain. +/// \param parent The parent QObject for the stream (optional). +/// + +QXmppIncomingServer::QXmppIncomingServer(QSslSocket *socket, const QString &domain, QObject *parent) + : QXmppStream(parent), + d(new QXmppIncomingServerPrivate) +{ + d->domain = domain; + + if (socket) { + info(QString("Incoming server connection from %1 %2").arg( + socket->peerAddress().toString(), + QString::number(socket->peerPort()))); + setSocket(socket); + } +} + +/// Destroys the current stream. + +QXmppIncomingServer::~QXmppIncomingServer() +{ + delete d; +} + +/// Returns the stream's identifier. +/// + +QString QXmppIncomingServer::localStreamId() const +{ + return d->localStreamId; +} + +void QXmppIncomingServer::handleStream(const QDomElement &streamElement) +{ + const QString from = streamElement.attribute("from"); + if (!from.isEmpty()) + info(QString("Incoming server stream from %1").arg(from)); + + // start stream + d->localStreamId = generateStanzaHash().toAscii(); + QString data = QString("<?xml version='1.0'?><stream:stream" + " xmlns='%1' xmlns:db='%2' xmlns:stream='%3'" + " id='%4' version=\"1.0\">").arg( + ns_server, + ns_server_dialback, + ns_stream, + d->localStreamId); + sendData(data.toUtf8()); + + // send stream features + QXmppStreamFeatures features; + if (!socket()->isEncrypted() && !socket()->localCertificate().isNull() && !socket()->privateKey().isNull()) + features.setTlsMode(QXmppStreamFeatures::Enabled); + sendPacket(features); +} + +void QXmppIncomingServer::handleStanza(const QDomElement &stanza) +{ + const QString ns = stanza.namespaceURI(); + + if (ns == ns_tls && stanza.tagName() == QLatin1String("starttls")) + { + sendData("<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>"); + socket()->flush(); + socket()->startServerEncryption(); + return; + } + else if (QXmppDialback::isDialback(stanza)) + { + QXmppDialback request; + request.parse(stanza); + // check the request is valid + if (!request.type().isEmpty() || + request.from().isEmpty() || + request.to() != d->domain || + request.key().isEmpty()) + { + warning("Invalid dialback received"); + return; + } + + const QString domain = request.from(); + if (request.command() == QXmppDialback::Result) + { + debug(QString("Received a dialback result from %1").arg(domain)); + + // establish dialback connection + QXmppOutgoingServer *stream = new QXmppOutgoingServer(d->domain, this); + bool check = connect(stream, SIGNAL(dialbackResponseReceived(QXmppDialback)), + this, SLOT(slotDialbackResponseReceived(QXmppDialback))); + Q_ASSERT(check); + Q_UNUSED(check); + stream->setVerify(d->localStreamId, request.key()); + stream->connectToHost(domain); + } + else if (request.command() == QXmppDialback::Verify) + { + debug(QString("Received a dialback verify from %1").arg(domain)); + emit dialbackRequestReceived(request); + } + + } + else if (d->authenticated.contains(jidToDomain(stanza.attribute("from")))) + { + // relay stanza if the remote party is authenticated + emit elementReceived(stanza); + } else { + warning(QString("Received an element from unverified domain %1").arg(jidToDomain(stanza.attribute("from")))); + disconnectFromHost(); + } +} + +/// Returns true if the socket is connected and the remote server is +/// authenticated. +/// + +bool QXmppIncomingServer::isConnected() const +{ + return QXmppStream::isConnected() && !d->authenticated.isEmpty(); +} + +/// Handles a dialback response received from the authority server. +/// +/// \param response +/// + +void QXmppIncomingServer::slotDialbackResponseReceived(const QXmppDialback &dialback) +{ + QXmppOutgoingServer *stream = qobject_cast<QXmppOutgoingServer*>(sender()); + if (!stream || + dialback.command() != QXmppDialback::Verify || + dialback.id() != d->localStreamId || + dialback.from() != stream->remoteDomain()) + return; + + // relay verify response + QXmppDialback response; + response.setCommand(QXmppDialback::Result); + response.setTo(dialback.from()); + response.setFrom(d->domain); + response.setType(dialback.type()); + sendPacket(response); + + // check for success + if (response.type() == QLatin1String("valid")) + { + info(QString("Verified incoming domain %1").arg(dialback.from())); + const bool wasConnected = !d->authenticated.isEmpty(); + d->authenticated.insert(dialback.from()); + if (!wasConnected) + emit connected(); + } else { + warning(QString("Failed to verify incoming domain %1").arg(dialback.from())); + disconnectFromHost(); + } + + // disconnect dialback + stream->disconnectFromHost(); + stream->deleteLater(); +} + diff --git a/src/server/QXmppIncomingServer.h b/src/server/QXmppIncomingServer.h new file mode 100644 index 00000000..a06e983c --- /dev/null +++ b/src/server/QXmppIncomingServer.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#ifndef QXMPPINCOMINGSERVER_H +#define QXMPPINCOMINGSERVER_H + +#include "QXmppStream.h" + +class QXmppDialback; +class QXmppIncomingServerPrivate; +class QXmppOutgoingServer; + +/// \brief The QXmppIncomingServer class represents an incoming XMPP stream +/// from an XMPP server. +/// + +class QXmppIncomingServer : public QXmppStream +{ + Q_OBJECT + +public: + QXmppIncomingServer(QSslSocket *socket, const QString &domain, QObject *parent); + ~QXmppIncomingServer(); + + bool isConnected() const; + QString localStreamId() const; + +signals: + /// This signal is emitted when a dialback verify request is received. + void dialbackRequestReceived(const QXmppDialback &result); + + /// This signal is emitted when an element is received. + void elementReceived(const QDomElement &element); + +protected: + /// \cond + void handleStanza(const QDomElement &stanzaElement); + void handleStream(const QDomElement &streamElement); + /// \endcond + +private slots: + void slotDialbackResponseReceived(const QXmppDialback &dialback); + +private: + Q_DISABLE_COPY(QXmppIncomingServer) + QXmppIncomingServerPrivate* const d; +}; + +#endif diff --git a/src/server/QXmppOutgoingServer.cpp b/src/server/QXmppOutgoingServer.cpp new file mode 100644 index 00000000..e980c27e --- /dev/null +++ b/src/server/QXmppOutgoingServer.cpp @@ -0,0 +1,331 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#include <QDomElement> +#include <QSslKey> +#include <QSslSocket> +#include <QTimer> +#include "qdnslookup.h" + +#include "QXmppConstants.h" +#include "QXmppDialback.h" +#include "QXmppOutgoingServer.h" +#include "QXmppStreamFeatures.h" +#include "QXmppUtils.h" + +class QXmppOutgoingServerPrivate +{ +public: + QList<QByteArray> dataQueue; + QDnsLookup dns; + QString localDomain; + QString localStreamKey; + QString remoteDomain; + QString verifyId; + QString verifyKey; + QTimer *dialbackTimer; + bool ready; +}; + +/// Constructs a new outgoing server-to-server stream. +/// +/// \param domain the local domain +/// \param parent the parent object +/// + +QXmppOutgoingServer::QXmppOutgoingServer(const QString &domain, QObject *parent) + : QXmppStream(parent), + d(new QXmppOutgoingServerPrivate) +{ + bool check; + Q_UNUSED(check); + + // socket initialisation + QSslSocket *socket = new QSslSocket(this); + setSocket(socket); + + check = connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), + this, SLOT(socketError(QAbstractSocket::SocketError))); + Q_ASSERT(check); + + // DNS lookups + check = connect(&d->dns, SIGNAL(finished()), + this, SLOT(_q_dnsLookupFinished())); + Q_ASSERT(check); + + d->dialbackTimer = new QTimer(this); + d->dialbackTimer->setInterval(5000); + d->dialbackTimer->setSingleShot(true); + check = connect(d->dialbackTimer, SIGNAL(timeout()), + this, SLOT(sendDialback())); + Q_ASSERT(check); + + d->localDomain = domain; + d->ready = false; + + check = connect(socket, SIGNAL(sslErrors(QList<QSslError>)), + this, SLOT(slotSslErrors(QList<QSslError>))); + Q_ASSERT(check); +} + +/// Destroys the stream. +/// + +QXmppOutgoingServer::~QXmppOutgoingServer() +{ + delete d; +} + +/// Attempts to connect to an XMPP server for the specified \a domain. +/// +/// \param domain + +void QXmppOutgoingServer::connectToHost(const QString &domain) +{ + d->remoteDomain = domain; + + // lookup server for domain + debug(QString("Looking up server for domain %1").arg(domain)); + d->dns.setName("_xmpp-server._tcp." + domain); + d->dns.setType(QDnsLookup::SRV); + d->dns.lookup(); +} + +void QXmppOutgoingServer::_q_dnsLookupFinished() +{ + QString host; + quint16 port; + + if (d->dns.error() == QDnsLookup::NoError && + !d->dns.serviceRecords().isEmpty()) { + // take the first returned record + host = d->dns.serviceRecords().first().target(); + port = d->dns.serviceRecords().first().port(); + } else { + // as a fallback, use domain as the host name + warning(QString("Lookup for domain %1 failed: %2") + .arg(d->dns.name(), d->dns.errorString())); + host = d->remoteDomain; + port = 5269; + } + + // connect to server + info(QString("Connecting to %1:%2").arg(host, QString::number(port))); + socket()->connectToHost(host, port); +} + +void QXmppOutgoingServer::handleStart() +{ + QXmppStream::handleStart(); + + QString data = QString("<?xml version='1.0'?><stream:stream" + " xmlns='%1' xmlns:db='%2' xmlns:stream='%3' version='1.0'>").arg( + ns_server, + ns_server_dialback, + ns_stream); + sendData(data.toUtf8()); +} + +void QXmppOutgoingServer::handleStream(const QDomElement &streamElement) +{ + Q_UNUSED(streamElement); + + // gmail.com servers are broken: they never send <stream:features>, + // so we schedule sending the dialback in a couple of seconds + d->dialbackTimer->start(); +} + +void QXmppOutgoingServer::handleStanza(const QDomElement &stanza) +{ + const QString ns = stanza.namespaceURI(); + + if(QXmppStreamFeatures::isStreamFeatures(stanza)) + { + QXmppStreamFeatures features; + features.parse(stanza); + + if (!socket()->isEncrypted()) + { + // check we can satisfy TLS constraints + if (!socket()->supportsSsl() && + features.tlsMode() == QXmppStreamFeatures::Required) + { + warning("Disconnecting as TLS is required, but SSL support is not available"); + disconnectFromHost(); + return; + } + + // enable TLS if possible + if (socket()->supportsSsl() && + features.tlsMode() != QXmppStreamFeatures::Disabled) + { + sendData("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>"); + return; + } + } + + // send dialback if needed + d->dialbackTimer->stop(); + sendDialback(); + } + else if (ns == ns_tls) + { + if (stanza.tagName() == QLatin1String("proceed")) + { + debug("Starting encryption"); + socket()->startClientEncryption(); + return; + } + } + else if (QXmppDialback::isDialback(stanza)) + { + QXmppDialback response; + response.parse(stanza); + + // check the request is valid + if (response.from().isEmpty() || + response.to() != d->localDomain || + response.type().isEmpty()) + { + warning("Invalid dialback response received"); + return; + } + if (response.command() == QXmppDialback::Result) + { + if (response.type() == QLatin1String("valid")) + { + info(QString("Outgoing server stream to %1 is ready").arg(response.from())); + d->ready = true; + + // send queued data + foreach (const QByteArray &data, d->dataQueue) + sendData(data); + d->dataQueue.clear(); + + // emit signal + emit connected(); + } + } + else if (response.command() == QXmppDialback::Verify) + { + emit dialbackResponseReceived(response); + } + + } +} + +/// Returns true if the socket is connected and authentication succeeded. +/// + +bool QXmppOutgoingServer::isConnected() const +{ + return QXmppStream::isConnected() && d->ready; +} + +/// Returns the stream's local dialback key. + +QString QXmppOutgoingServer::localStreamKey() const +{ + return d->localStreamKey; +} + +/// Sets the stream's local dialback key. +/// +/// \param key + +void QXmppOutgoingServer::setLocalStreamKey(const QString &key) +{ + d->localStreamKey = key; +} + +/// Sets the stream's verification information. +/// +/// \param id +/// \param key + +void QXmppOutgoingServer::setVerify(const QString &id, const QString &key) +{ + d->verifyId = id; + d->verifyKey = key; +} + +/// Sends or queues data until connected. +/// +/// \param data + +void QXmppOutgoingServer::queueData(const QByteArray &data) +{ + if (isConnected()) + sendData(data); + else + d->dataQueue.append(data); +} + +/// Returns the remote server's domain. + +QString QXmppOutgoingServer::remoteDomain() const +{ + return d->remoteDomain; +} + +void QXmppOutgoingServer::sendDialback() +{ + if (!d->localStreamKey.isEmpty()) + { + // send dialback key + debug(QString("Sending dialback result to %1").arg(d->remoteDomain)); + QXmppDialback dialback; + dialback.setCommand(QXmppDialback::Result); + dialback.setFrom(d->localDomain); + dialback.setTo(d->remoteDomain); + dialback.setKey(d->localStreamKey); + sendPacket(dialback); + } + else if (!d->verifyId.isEmpty() && !d->verifyKey.isEmpty()) + { + // send dialback verify + debug(QString("Sending dialback verify to %1").arg(d->remoteDomain)); + QXmppDialback verify; + verify.setCommand(QXmppDialback::Verify); + verify.setId(d->verifyId); + verify.setFrom(d->localDomain); + verify.setTo(d->remoteDomain); + verify.setKey(d->verifyKey); + sendPacket(verify); + } +} + +void QXmppOutgoingServer::slotSslErrors(const QList<QSslError> &errors) +{ + warning("SSL errors"); + for(int i = 0; i < errors.count(); ++i) + warning(errors.at(i).errorString()); + socket()->ignoreSslErrors(); +} + +void QXmppOutgoingServer::socketError(QAbstractSocket::SocketError error) +{ + Q_UNUSED(error); + emit disconnected(); +} + diff --git a/src/server/QXmppOutgoingServer.h b/src/server/QXmppOutgoingServer.h new file mode 100644 index 00000000..4e117c0e --- /dev/null +++ b/src/server/QXmppOutgoingServer.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#ifndef QXMPPOUTGOINGSERVER_H +#define QXMPPOUTGOINGSERVER_H + +#include <QAbstractSocket> + +#include "QXmppStream.h" + +class QSslError; +class QXmppDialback; +class QXmppOutgoingServer; +class QXmppOutgoingServerPrivate; + +/// \brief The QXmppOutgoingServer class represents an outgoing XMPP stream +/// to another XMPP server. +/// + +class QXmppOutgoingServer : public QXmppStream +{ + Q_OBJECT + +public: + QXmppOutgoingServer(const QString &domain, QObject *parent); + ~QXmppOutgoingServer(); + + bool isConnected() const; + + QString localStreamKey() const; + void setLocalStreamKey(const QString &key); + void setVerify(const QString &id, const QString &key); + + QString remoteDomain() const; + +signals: + /// This signal is emitted when a dialback verify response is received. + void dialbackResponseReceived(const QXmppDialback &response); + +protected: + /// \cond + void handleStart(); + void handleStream(const QDomElement &streamElement); + void handleStanza(const QDomElement &stanzaElement); + /// \endcond + +public slots: + void connectToHost(const QString &domain); + void queueData(const QByteArray &data); + +private slots: + void _q_dnsLookupFinished(); + void sendDialback(); + void slotSslErrors(const QList<QSslError> &errors); + void socketError(QAbstractSocket::SocketError error); + +private: + Q_DISABLE_COPY(QXmppOutgoingServer) + QXmppOutgoingServerPrivate* const d; +}; + +#endif diff --git a/src/server/QXmppServer.cpp b/src/server/QXmppServer.cpp new file mode 100644 index 00000000..4515914e --- /dev/null +++ b/src/server/QXmppServer.cpp @@ -0,0 +1,807 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#include <QCoreApplication> +#include <QDomElement> +#include <QFileInfo> +#include <QPluginLoader> +#include <QSslCertificate> +#include <QSslKey> +#include <QSslSocket> + +#include "QXmppConstants.h" +#include "QXmppDialback.h" +#include "QXmppIq.h" +#include "QXmppIncomingClient.h" +#include "QXmppIncomingServer.h" +#include "QXmppOutgoingServer.h" +#include "QXmppPresence.h" +#include "QXmppServer.h" +#include "QXmppServerExtension.h" +#include "QXmppServerPlugin.h" +#include "QXmppUtils.h" + +#include "server/mod_presence.h" + +// Core plugins +Q_IMPORT_PLUGIN(mod_disco) +Q_IMPORT_PLUGIN(mod_ping) +Q_IMPORT_PLUGIN(mod_proxy65) +Q_IMPORT_PLUGIN(mod_stats) +Q_IMPORT_PLUGIN(mod_time) +Q_IMPORT_PLUGIN(mod_version) + +class QXmppServerPrivate +{ +public: + QXmppServerPrivate(QXmppServer *qq); + void loadExtensions(QXmppServer *server); + bool routeData(const QString &to, const QByteArray &data); + void startExtensions(); + void stopExtensions(); + + void info(const QString &message); + void warning(const QString &message); + + QString domain; + QList<QXmppServerExtension*> extensions; + QXmppLogger *logger; + QXmppPasswordChecker *passwordChecker; + + // client-to-server + QXmppSslServer *serverForClients; + QSet<QXmppIncomingClient*> incomingClients; + QHash<QString, QXmppIncomingClient*> incomingClientsByJid; + QHash<QString, QSet<QXmppIncomingClient*> > incomingClientsByBareJid; + + // server-to-server + QSet<QXmppIncomingServer*> incomingServers; + QSet<QXmppOutgoingServer*> outgoingServers; + QXmppSslServer *serverForServers; + +private: + bool loaded; + bool started; + QXmppServer *q; +}; + +QXmppServerPrivate::QXmppServerPrivate(QXmppServer *qq) + : logger(0), + passwordChecker(0), + loaded(false), + started(false), + q(qq) +{ +} + +/// Routes XMPP data to the given recipient. +/// +/// \param to +/// \param data +/// + +bool QXmppServerPrivate::routeData(const QString &to, const QByteArray &data) +{ + // refuse to route packets to empty destination, own domain or sub-domains + const QString toDomain = jidToDomain(to); + if (to.isEmpty() || to == domain || toDomain.endsWith("." + domain)) + return false; + + if (toDomain == domain) { + + // look for a client connection + QList<QXmppIncomingClient*> found; + if (jidToResource(to).isEmpty()) { + foreach (QXmppIncomingClient *conn, incomingClientsByBareJid.value(to)) + found << conn; + } else { + QXmppIncomingClient *conn = incomingClientsByJid.value(to); + if (conn) + found << conn; + } + + // send data + foreach (QXmppStream *conn, found) + QMetaObject::invokeMethod(conn, "sendData", Q_ARG(QByteArray, data)); + return !found.isEmpty(); + + } else if (serverForServers->isListening()) { + + bool check; + Q_UNUSED(check); + + // look for an outgoing S2S connection + foreach (QXmppOutgoingServer *conn, outgoingServers) { + if (conn->remoteDomain() == toDomain) { + // send or queue data + QMetaObject::invokeMethod(conn, "queueData", Q_ARG(QByteArray, data)); + return true; + } + } + + // if we did not find an outgoing server, + // we need to establish the S2S connection + QXmppOutgoingServer *conn = new QXmppOutgoingServer(domain, 0); + conn->setLocalStreamKey(generateStanzaHash().toAscii()); + conn->moveToThread(q->thread()); + conn->setParent(q); + + check = QObject::connect(conn, SIGNAL(disconnected()), + q, SLOT(_q_outgoingServerDisconnected())); + Q_UNUSED(check); + + // add stream + outgoingServers.insert(conn); + + // queue data and connect to remote server + QMetaObject::invokeMethod(conn, "queueData", Q_ARG(QByteArray, data)); + QMetaObject::invokeMethod(conn, "connectToHost", Q_ARG(QString, toDomain)); + return true; + + } else { + + // S2S is disabled, failed to route data + return false; + + } +} + +/// Handles an incoming XML element. +/// +/// \param server +/// \param stream +/// \param element + +static void handleStanza(QXmppServer *server, const QDomElement &element) +{ + // try extensions + foreach (QXmppServerExtension *extension, server->extensions()) + if (extension->handleStanza(element)) + return; + + // default handlers + const QString domain = server->domain(); + const QString to = element.attribute("to"); + if (to == domain) { + if (element.tagName() == QLatin1String("iq")) { + // we do not support the given IQ + QXmppIq request; + request.parse(element); + + if (request.type() != QXmppIq::Error && request.type() != QXmppIq::Result) { + QXmppIq response(QXmppIq::Error); + response.setId(request.id()); + response.setFrom(domain); + response.setTo(request.from()); + QXmppStanza::Error error(QXmppStanza::Error::Cancel, + QXmppStanza::Error::FeatureNotImplemented); + response.setError(error); + server->sendPacket(response); + } + } + + } else { + + // route element or reply on behalf of missing peer + if (!server->sendElement(element) && element.tagName() == QLatin1String("iq")) { + QXmppIq request; + request.parse(element); + + QXmppIq response(QXmppIq::Error); + response.setId(request.id()); + response.setFrom(request.to()); + response.setTo(request.from()); + QXmppStanza::Error error(QXmppStanza::Error::Cancel, + QXmppStanza::Error::ServiceUnavailable); + response.setError(error); + server->sendPacket(response); + } + } +} + +void QXmppServerPrivate::info(const QString &message) +{ + if (logger) + logger->log(QXmppLogger::InformationMessage, message); +} + +void QXmppServerPrivate::warning(const QString &message) +{ + if (logger) + logger->log(QXmppLogger::WarningMessage, message); +} + +/// Load the server's extensions. +/// +/// \param server + +void QXmppServerPrivate::loadExtensions(QXmppServer *server) +{ + if (!loaded) + { + QObjectList plugins = QPluginLoader::staticInstances(); + foreach (QObject *object, plugins) + { + QXmppServerPlugin *plugin = qobject_cast<QXmppServerPlugin*>(object); + if (!plugin) + continue; + + foreach (const QString &key, plugin->keys()) + server->addExtension(plugin->create(key)); + } + + // FIXME: until we can handle presence errors, we need to + // keep this extension last. + server->addExtension(new QXmppServerPresence); + loaded = true; + } +} + +/// Start the server's extensions. + +void QXmppServerPrivate::startExtensions() +{ + if (!started) + { + foreach (QXmppServerExtension *extension, extensions) + if (!extension->start()) + warning(QString("Could not start extension %1").arg(extension->extensionName())); + started = true; + } +} + +/// Stop the server's extensions (in reverse order). +/// + +void QXmppServerPrivate::stopExtensions() +{ + if (started) + { + for (int i = extensions.size() - 1; i >= 0; --i) + extensions[i]->stop(); + started = false; + } +} + +/// Constructs a new XMPP server instance. +/// +/// \param parent + +QXmppServer::QXmppServer(QObject *parent) + : QXmppLoggable(parent) +{ + bool check; + Q_UNUSED(check); + + qRegisterMetaType<QDomElement>("QDomElement"); + + d = new QXmppServerPrivate(this); + d->serverForClients = new QXmppSslServer(this); + check = connect(d->serverForClients, SIGNAL(newConnection(QSslSocket*)), + this, SLOT(_q_clientConnection(QSslSocket*))); + Q_ASSERT(check); + + d->serverForServers = new QXmppSslServer(this); + check = connect(d->serverForServers, SIGNAL(newConnection(QSslSocket*)), + this, SLOT(_q_serverConnection(QSslSocket*))); + Q_ASSERT(check); +} + +/// Destroys an XMPP server instance. +/// + +QXmppServer::~QXmppServer() +{ + close(); + delete d; +} + +/// Registers a new extension with the server. +/// +/// \param extension + +void QXmppServer::addExtension(QXmppServerExtension *extension) +{ + if (!extension || d->extensions.contains(extension)) + return; + d->info(QString("Added extension %1").arg(extension->extensionName())); + extension->setParent(this); + extension->setServer(this); + d->extensions << extension; +} + +/// Returns the list of loaded extensions. +/// + +QList<QXmppServerExtension*> QXmppServer::extensions() +{ + d->loadExtensions(this); + return d->extensions; +} + +/// Returns the server's domain. +/// + +QString QXmppServer::domain() const +{ + return d->domain; +} + +/// Sets the server's domain. +/// +/// \param domain + +void QXmppServer::setDomain(const QString &domain) +{ + d->domain = domain; +} + +/// Returns the QXmppLogger associated with the server. +/// + +QXmppLogger *QXmppServer::logger() +{ + return d->logger; +} + +/// Sets the QXmppLogger associated with the server. +/// +/// \param logger + +void QXmppServer::setLogger(QXmppLogger *logger) +{ + if (d->logger) + QObject::disconnect(this, SIGNAL(logMessage(QXmppLogger::MessageType,QString)), + d->logger, SLOT(log(QXmppLogger::MessageType,QString))); + d->logger = logger; + d->logger = logger; + if (d->logger) + connect(this, SIGNAL(logMessage(QXmppLogger::MessageType,QString)), + d->logger, SLOT(log(QXmppLogger::MessageType,QString))); +} + +/// Returns the password checker used to verify client credentials. +/// + +QXmppPasswordChecker *QXmppServer::passwordChecker() +{ + return d->passwordChecker; +} + +/// Sets the password checker used to verify client credentials. +/// +/// \param checker +/// + +void QXmppServer::setPasswordChecker(QXmppPasswordChecker *checker) +{ + d->passwordChecker = checker; +} + +/// Returns the statistics for the server. + +QVariantMap QXmppServer::statistics() const +{ + QVariantMap stats; + stats["version"] = qApp->applicationVersion(); + stats["incoming-clients"] = d->incomingClients.size(); + stats["incoming-servers"] = d->incomingServers.size(); + stats["outgoing-servers"] = d->outgoingServers.size(); + return stats; +} + +/// Sets the path for additional SSL CA certificates. +/// +/// \param path + +void QXmppServer::addCaCertificates(const QString &path) +{ + if (!path.isEmpty() && !QFileInfo(path).isReadable()) + d->warning(QString("SSL CA certificates are not readable %1").arg(path)); + QList<QSslCertificate> certificates = QSslCertificate::fromPath(path); + d->serverForClients->addCaCertificates(certificates); + d->serverForServers->addCaCertificates(certificates); +} + +/// Sets the path for the local SSL certificate. +/// +/// \param path + +void QXmppServer::setLocalCertificate(const QString &path) +{ + QSslCertificate certificate; + QFile file(path); + if (!path.isEmpty() && file.open(QIODevice::ReadOnly | QIODevice::Text)) + certificate = QSslCertificate(file.readAll()); + else + d->warning(QString("SSL certificate is not readable %1").arg(path)); + d->serverForClients->setLocalCertificate(certificate); + d->serverForServers->setLocalCertificate(certificate); +} + +/// Sets the path for the local SSL private key. +/// +/// \param path + +void QXmppServer::setPrivateKey(const QString &path) +{ + QSslKey key; + QFile file(path); + if (!path.isEmpty() && file.open(QIODevice::ReadOnly)) + key = QSslKey(file.readAll(), QSsl::Rsa); + else + d->warning(QString("SSL key is not readable %1").arg(path)); + d->serverForClients->setPrivateKey(key); + d->serverForServers->setPrivateKey(key); +} + +/// Listen for incoming XMPP client connections. +/// +/// \param address +/// \param port + +bool QXmppServer::listenForClients(const QHostAddress &address, quint16 port) +{ + if (!d->serverForClients->listen(address, port)) + { + d->warning(QString("Could not start listening for C2S on port %1").arg(QString::number(port))); + return false; + } + + // start extensions + d->loadExtensions(this); + d->startExtensions(); + return true; +} + +/// Closes the server. +/// + +void QXmppServer::close() +{ + // prevent new connections + d->serverForClients->close(); + d->serverForServers->close(); + + // stop extensions + d->stopExtensions(); + + // close XMPP streams + foreach (QXmppIncomingClient *stream, d->incomingClients) + stream->disconnectFromHost(); + foreach (QXmppIncomingServer *stream, d->incomingServers) + stream->disconnectFromHost(); + foreach (QXmppOutgoingServer *stream, d->outgoingServers) + stream->disconnectFromHost(); +} + +/// Listen for incoming XMPP server connections. +/// +/// \param address +/// \param port + +bool QXmppServer::listenForServers(const QHostAddress &address, quint16 port) +{ + if (!d->serverForServers->listen(address, port)) + { + d->warning(QString("Could not start listening for S2S on port %1").arg(QString::number(port))); + return false; + } + + // start extensions + d->loadExtensions(this); + d->startExtensions(); + return true; +} + +/// Route an XMPP stanza. +/// +/// \param element + +bool QXmppServer::sendElement(const QDomElement &element) +{ + // serialize data + QByteArray data; + QXmlStreamWriter xmlStream(&data); + const QStringList omitNamespaces = QStringList() << ns_client << ns_server; + helperToXmlAddDomElement(&xmlStream, element, omitNamespaces); + + // route data + return d->routeData(element.attribute("to"), data); +} + +/// Route an XMPP packet. +/// +/// \param packet + +bool QXmppServer::sendPacket(const QXmppStanza &packet) +{ + // serialize data + QByteArray data; + QXmlStreamWriter xmlStream(&data); + packet.toXml(&xmlStream); + + // route data + return d->routeData(packet.to(), data); +} + +/// Add a new incoming client stream. +/// +/// \param stream + +void QXmppServer::addIncomingClient(QXmppIncomingClient *stream) +{ + bool check; + Q_UNUSED(check); + + stream->setPasswordChecker(d->passwordChecker); + + check = connect(stream, SIGNAL(connected()), + this, SLOT(_q_clientConnected())); + Q_ASSERT(check); + + check = connect(stream, SIGNAL(disconnected()), + this, SLOT(_q_clientDisconnected())); + Q_ASSERT(check); + + check = connect(stream, SIGNAL(elementReceived(QDomElement)), + this, SLOT(handleElement(QDomElement))); + Q_ASSERT(check); + + // add stream + d->incomingClients.insert(stream); +} + +/// Handle a new incoming TCP connection from a client. +/// +/// \param socket + +void QXmppServer::_q_clientConnection(QSslSocket *socket) +{ + // check the socket didn't die since the signal was emitted + if (socket->state() != QAbstractSocket::ConnectedState) { + delete socket; + return; + } + + QXmppIncomingClient *stream = new QXmppIncomingClient(socket, d->domain, this); + stream->setInactivityTimeout(120); + socket->setParent(stream); + addIncomingClient(stream); +} + +/// Handle a successful stream connection for a client. +/// + +void QXmppServer::_q_clientConnected() +{ + QXmppIncomingClient *client = qobject_cast<QXmppIncomingClient*>(sender()); + if (!client) + return; + + // FIXME: at this point the JID must contain a resource, assert it? + const QString jid = client->jid(); + + // check whether the connection conflicts with another one + QXmppIncomingClient *old = d->incomingClientsByJid.value(jid); + if (old && old != client) { + old->sendData("<stream:error><conflict xmlns='urn:ietf:params:xml:ns:xmpp-streams'/><text xmlns='urn:ietf:params:xml:ns:xmpp-streams'>Replaced by new connection</text></stream:error>"); + old->disconnectFromHost(); + } + d->incomingClientsByJid.insert(jid, client); + d->incomingClientsByBareJid[jidToBareJid(jid)].insert(client); + + // emit signal + emit clientConnected(jid); +} + +/// Handle a stream disconnection for a client. + +void QXmppServer::_q_clientDisconnected() +{ + QXmppIncomingClient *client = qobject_cast<QXmppIncomingClient *>(sender()); + if (!client) + return; + + if (d->incomingClients.remove(client)) { + // remove stream from routing tables + const QString jid = client->jid(); + if (!jid.isEmpty()) { + if (d->incomingClientsByJid.value(jid) == client) + d->incomingClientsByJid.remove(jid); + const QString bareJid = jidToBareJid(jid); + if (d->incomingClientsByBareJid.contains(bareJid)) + d->incomingClientsByBareJid[bareJid].remove(client); + } + + // destroy client + client->deleteLater(); + + // emit signal + if (!jid.isEmpty()) + emit clientDisconnected(jid); + } +} + +void QXmppServer::_q_dialbackRequestReceived(const QXmppDialback &dialback) +{ + QXmppIncomingServer *stream = qobject_cast<QXmppIncomingServer *>(sender()); + if (!stream) + return; + + if (dialback.command() == QXmppDialback::Verify) + { + // handle a verify request + foreach (QXmppOutgoingServer *out, d->outgoingServers) { + if (out->remoteDomain() != dialback.from()) + continue; + + bool isValid = dialback.key() == out->localStreamKey(); + QXmppDialback verify; + verify.setCommand(QXmppDialback::Verify); + verify.setId(dialback.id()); + verify.setTo(dialback.from()); + verify.setFrom(d->domain); + verify.setType(isValid ? "valid" : "invalid"); + stream->sendPacket(verify); + return; + } + } +} + +/// Handle an incoming XML element. + +void QXmppServer::handleElement(const QDomElement &element) +{ + handleStanza(this, element); +} + +/// Handle a stream disconnection for an outgoing server. + +void QXmppServer::_q_outgoingServerDisconnected() +{ + QXmppOutgoingServer *outgoing = qobject_cast<QXmppOutgoingServer *>(sender()); + if (!outgoing) + return; + + if (d->outgoingServers.remove(outgoing)) + outgoing->deleteLater(); +} + +/// Handle a new incoming TCP connection from a server. +/// +/// \param socket + +void QXmppServer::_q_serverConnection(QSslSocket *socket) +{ + bool check; + Q_UNUSED(check); + + // check the socket didn't die since the signal was emitted + if (socket->state() != QAbstractSocket::ConnectedState) { + delete socket; + return; + } + + QXmppIncomingServer *stream = new QXmppIncomingServer(socket, d->domain, this); + socket->setParent(stream); + + check = connect(stream, SIGNAL(disconnected()), + this, SLOT(_q_serverDisconnected())); + Q_ASSERT(check); + + check = connect(stream, SIGNAL(dialbackRequestReceived(QXmppDialback)), + this, SLOT(_q_dialbackRequestReceived(QXmppDialback))); + Q_ASSERT(check); + + check = connect(stream, SIGNAL(elementReceived(QDomElement)), + this, SLOT(handleElement(QDomElement))); + Q_ASSERT(check); + + // add stream + d->incomingServers.insert(stream); +} + +/// Handle a stream disconnection for an incoming server. + +void QXmppServer::_q_serverDisconnected() +{ + QXmppIncomingServer *incoming = qobject_cast<QXmppIncomingServer *>(sender()); + if (!incoming) + return; + + if (d->incomingServers.remove(incoming)) + incoming->deleteLater(); +} + +class QXmppSslServerPrivate +{ +public: + QList<QSslCertificate> caCertificates; + QSslCertificate localCertificate; + QSslKey privateKey; +}; + +/// Constructs a new SSL server instance. +/// +/// \param parent + +QXmppSslServer::QXmppSslServer(QObject *parent) + : QTcpServer(parent), + d(new QXmppSslServerPrivate) +{ +} + +/// Destroys an SSL server instance. +/// + +QXmppSslServer::~QXmppSslServer() +{ + delete d; +} + +void QXmppSslServer::incomingConnection(int socketDescriptor) +{ + QSslSocket *socket = new QSslSocket; + if (!socket->setSocketDescriptor(socketDescriptor)) { + delete socket; + return; + } + + if (!d->localCertificate.isNull() && !d->privateKey.isNull()) { + socket->setProtocol(QSsl::AnyProtocol); + socket->addCaCertificates(d->caCertificates); + socket->setLocalCertificate(d->localCertificate); + socket->setPrivateKey(d->privateKey); + } + emit newConnection(socket); +} + +/// Adds the given certificates to the CA certificate database to be used +/// for incoming connnections. +/// +/// \param certificates + +void QXmppSslServer::addCaCertificates(const QList<QSslCertificate> &certificates) +{ + d->caCertificates += certificates; +} + +/// Sets the local certificate to be used for incoming connections. +/// +/// \param certificate + +void QXmppSslServer::setLocalCertificate(const QSslCertificate &certificate) +{ + d->localCertificate = certificate; +} + +/// Sets the local private key to be used for incoming connections. +/// +/// \param key + +void QXmppSslServer::setPrivateKey(const QSslKey &key) +{ + d->privateKey = key; +} + diff --git a/src/server/QXmppServer.h b/src/server/QXmppServer.h new file mode 100644 index 00000000..cb5fcedd --- /dev/null +++ b/src/server/QXmppServer.h @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#ifndef QXMPPSERVER_H +#define QXMPPSERVER_H + +#include <QTcpServer> +#include <QVariantMap> + +#include "QXmppLogger.h" + +class QDomElement; +class QSslCertificate; +class QSslKey; +class QSslSocket; + +class QXmppDialback; +class QXmppIncomingClient; +class QXmppOutgoingServer; +class QXmppPasswordChecker; +class QXmppPresence; +class QXmppServerExtension; +class QXmppServerPrivate; +class QXmppSslServer; +class QXmppStanza; +class QXmppStream; + +/// \brief The QXmppServer class represents an XMPP server. +/// +/// It provides support for both client-to-server and server-to-server +/// communications, SSL encryption and logging facilities. +/// +/// QXmppServer comes with a number of modules for service discovery, +/// XMPP ping, statistics and file transfer proxy support. You can write +/// your own extensions for QXmppServer by subclassing QXmppServerExtension. +/// +/// \ingroup Core + +class QXmppServer : public QXmppLoggable +{ + Q_OBJECT + +public: + QXmppServer(QObject *parent = 0); + ~QXmppServer(); + + void addExtension(QXmppServerExtension *extension); + QList<QXmppServerExtension*> extensions(); + + QString domain() const; + void setDomain(const QString &domain); + + QXmppLogger *logger(); + void setLogger(QXmppLogger *logger); + + QXmppPasswordChecker *passwordChecker(); + void setPasswordChecker(QXmppPasswordChecker *checker); + + QVariantMap statistics() const; + + void addCaCertificates(const QString &caCertificates); + void setLocalCertificate(const QString &path); + void setPrivateKey(const QString &path); + + void close(); + bool listenForClients(const QHostAddress &address = QHostAddress::Any, quint16 port = 5222); + bool listenForServers(const QHostAddress &address = QHostAddress::Any, quint16 port = 5269); + + bool sendElement(const QDomElement &element); + bool sendPacket(const QXmppStanza &stanza); + + /// \cond + // FIXME: this method should not be public, but it is needed to + // implement BOSH support as an extension. + void addIncomingClient(QXmppIncomingClient *stream); + /// \endcond + +signals: + /// This signal is emitted when a client has connected. + void clientConnected(const QString &jid); + + /// This signal is emitted when a client has disconnected. + void clientDisconnected(const QString &jid); + +public slots: + void handleElement(const QDomElement &element); + +private slots: + void _q_clientConnection(QSslSocket *socket); + void _q_clientConnected(); + void _q_clientDisconnected(); + void _q_dialbackRequestReceived(const QXmppDialback &dialback); + void _q_outgoingServerDisconnected(); + void _q_serverConnection(QSslSocket *socket); + void _q_serverDisconnected(); + +private: + friend class QXmppServerPrivate; + QXmppServerPrivate *d; +}; + +class QXmppSslServerPrivate; + +/// \brief The QXmppSslServer class represents an SSL-enabled TCP server. +/// + +class QXmppSslServer : public QTcpServer +{ + Q_OBJECT + +public: + QXmppSslServer(QObject *parent = 0); + ~QXmppSslServer(); + + void addCaCertificates(const QList<QSslCertificate> &certificates); + void setLocalCertificate(const QSslCertificate &certificate); + void setPrivateKey(const QSslKey &key); + +signals: + /// This signal is emitted when a new connection is established. + void newConnection(QSslSocket *socket); + +private: + void incomingConnection(int socketDescriptor); + QXmppSslServerPrivate * const d; +}; + +#endif diff --git a/src/server/QXmppServerExtension.cpp b/src/server/QXmppServerExtension.cpp new file mode 100644 index 00000000..7369a4f7 --- /dev/null +++ b/src/server/QXmppServerExtension.cpp @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#include <QDomElement> +#include <QMetaClassInfo> +#include <QStringList> + +#include "QXmppLogger.h" +#include "QXmppServer.h" +#include "QXmppServerExtension.h" + +class QXmppServerExtensionPrivate +{ +public: + QXmppServer *server; +}; + +QXmppServerExtension::QXmppServerExtension() + : d(new QXmppServerExtensionPrivate) +{ + d->server = 0; +} + +QXmppServerExtension::~QXmppServerExtension() +{ + delete d; +} + +/// Returns the discovery features to add to the server. +/// + +QStringList QXmppServerExtension::discoveryFeatures() const +{ + return QStringList(); +} + +/// Returns the discovery items to add to the server. +/// + +QStringList QXmppServerExtension::discoveryItems() const +{ + return QStringList(); +} + +/// Returns the extension's name. +/// + +QString QXmppServerExtension::extensionName() const +{ + int index = metaObject()->indexOfClassInfo("ExtensionName"); + if (index < 0) + return QString(); + const char *name = metaObject()->classInfo(index).value(); + return QString::fromLatin1(name); +} + +/// Handles an incoming XMPP stanza. +/// +/// Return true if no further processing should occur, false otherwise. +/// +/// \param stanza The received stanza. + +bool QXmppServerExtension::handleStanza(const QDomElement &stanza) +{ + Q_UNUSED(stanza); + return false; +} + +/// Returns the list of subscribers for the given JID. +/// +/// \param jid + +QSet<QString> QXmppServerExtension::presenceSubscribers(const QString &jid) +{ + Q_UNUSED(jid); + return QSet<QString>(); +} + +/// Returns the list of subscriptions for the given JID. +/// +/// \param jid + +QSet<QString> QXmppServerExtension::presenceSubscriptions(const QString &jid) +{ + Q_UNUSED(jid); + return QSet<QString>(); +} + +/// Returns the extension's statistics. +/// + +QVariantMap QXmppServerExtension::statistics() const +{ + return QVariantMap(); +} + +/// Sets the extension's statistics. +/// + +void QXmppServerExtension::setStatistics(const QVariantMap &statistics) +{ + Q_UNUSED(statistics); +} + +/// Starts the extension. +/// +/// Return true if the extension was started, false otherwise. + +bool QXmppServerExtension::start() +{ + return true; +} + +/// Stops the extension. + +void QXmppServerExtension::stop() +{ +} + +/// Returns the server which loaded this extension. + +QXmppServer *QXmppServerExtension::server() +{ + return d->server; +} + +/// Sets the server which loaded this extension. +/// +/// \param server + +void QXmppServerExtension::setServer(QXmppServer *server) +{ + d->server = server; +} + diff --git a/src/server/QXmppServerExtension.h b/src/server/QXmppServerExtension.h new file mode 100644 index 00000000..d908e17b --- /dev/null +++ b/src/server/QXmppServerExtension.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#ifndef QXMPPSERVEREXTENSION_H +#define QXMPPSERVEREXTENSION_H + +#include <QVariant> + +#include "QXmppLogger.h" + +class QDomElement; +class QStringList; + +class QXmppServer; +class QXmppServerExtensionPrivate; +class QXmppStream; + +/// \brief The QXmppServerExtension class is the base class for QXmppServer +/// extensions. +/// +/// If you want to extend QXmppServer, for instance to support an IQ type +/// which is not natively supported, you can subclass QXmppServerExtension +/// and implement handleStanza(). You can then add your extension to the +/// client instance using QXmppServer::addExtension(). +/// +/// \ingroup Core + +class QXmppServerExtension : public QXmppLoggable +{ + Q_OBJECT + +public: + QXmppServerExtension(); + ~QXmppServerExtension(); + QString extensionName() const; + + virtual QStringList discoveryFeatures() const; + virtual QStringList discoveryItems() const; + virtual bool handleStanza(const QDomElement &stanza); + virtual QSet<QString> presenceSubscribers(const QString &jid); + virtual QSet<QString> presenceSubscriptions(const QString &jid); + + virtual QVariantMap statistics() const; + virtual void setStatistics(const QVariantMap &statistics); + + virtual bool start(); + virtual void stop(); + +protected: + QXmppServer *server(); + +private: + void setServer(QXmppServer *server); + QXmppServerExtensionPrivate * const d; + + friend class QXmppServer; +}; + +#endif diff --git a/src/server/QXmppServerPlugin.h b/src/server/QXmppServerPlugin.h new file mode 100644 index 00000000..d247dd9e --- /dev/null +++ b/src/server/QXmppServerPlugin.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#ifndef QXMPPSERVERPLUGIN_H +#define QXMPPSERVERPLUGIN_H + +#include <QtPlugin> + +class QXmppServer; +class QXmppServerExtension; + +class QXmppServerPluginInterface +{ +public: + virtual QXmppServerExtension *create(const QString &key) = 0; + virtual QStringList keys() const = 0; +}; + +Q_DECLARE_INTERFACE(QXmppServerPluginInterface, "com.googlecode.qxmpp.ServerPlugin/1.0") + +/// \brief The QXmppServerPlugin class is the base class for QXmppServer plugins. +/// + +class QXmppServerPlugin : public QObject, public QXmppServerPluginInterface +{ + Q_OBJECT + Q_INTERFACES(QXmppServerPluginInterface) + +public: + /// Creates and returns the specified QXmppServerExtension. + /// + /// \param key The key for the QXmppServerExtension. + virtual QXmppServerExtension *create(const QString &key) = 0; + + /// Returns the list of keys supported by this plugin. + /// + virtual QStringList keys() const = 0; +}; + +#endif |
