aboutsummaryrefslogtreecommitdiff
path: root/src/server
diff options
context:
space:
mode:
authorJeremy Lainé <jeremy.laine@m4x.org>2012-02-08 11:53:52 +0000
committerJeremy Lainé <jeremy.laine@m4x.org>2012-02-08 11:53:52 +0000
commit36bbeb02497f5f79ddae56857893c2f71bf08ee9 (patch)
tree34191bb93ff6cb044d5d28d2f630dc0476cc5659 /src/server
parentaa334abcca63101292c48a72c7b1851b8ecc26c7 (diff)
move server code to "server" directory
Diffstat (limited to 'src/server')
-rw-r--r--src/server/QXmppDialback.cpp117
-rw-r--r--src/server/QXmppDialback.h69
-rw-r--r--src/server/QXmppIncomingClient.cpp445
-rw-r--r--src/server/QXmppIncomingClient.h73
-rw-r--r--src/server/QXmppIncomingServer.cpp207
-rw-r--r--src/server/QXmppIncomingServer.h69
-rw-r--r--src/server/QXmppOutgoingServer.cpp331
-rw-r--r--src/server/QXmppOutgoingServer.h82
-rw-r--r--src/server/QXmppServer.cpp807
-rw-r--r--src/server/QXmppServer.h148
-rw-r--r--src/server/QXmppServerExtension.cpp155
-rw-r--r--src/server/QXmppServerExtension.h79
-rw-r--r--src/server/QXmppServerPlugin.h60
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