/* * Copyright (C) 2008-2010 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 #include #include #include #include "QXmppBindIq.h" #include "QXmppConstants.h" #include "QXmppMessage.h" #include "QXmppSessionIq.h" #include "QXmppStreamFeatures.h" #include "QXmppUtils.h" #include "QXmppIncomingClient.h" class QXmppIncomingClientPrivate { public: QTimer *idleTimer; QString domain; QString username; QString resource; QXmppPasswordChecker *passwordChecker; QByteArray saslNonce; }; /// 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; setObjectName("C2S-in"); setSocket(socket); // create inactivity timer d->idleTimer = new QTimer(this); d->idleTimer->setInterval(70000); d->idleTimer->setSingleShot(true); bool check = connect(d->idleTimer, SIGNAL(timeout()), this, SLOT(slotTimeout())); 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 { if (d->username.isEmpty()) return QString(); QString jid = d->username + "@" + d->domain; if (!d->resource.isEmpty()) jid += "/" + d->resource; return jid; } /// 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) { d->idleTimer->start(); // start stream const QByteArray sessionId = generateStanzaHash().toAscii(); QString response = QString("").arg( ns_client, ns_stream, sessionId, d->domain.toAscii()); sendData(response.toUtf8()); // check requested domain if (streamElement.attribute("to") != d->domain) { QString response = QString("" "" "" "This server does not serve %1" "" "").arg(streamElement.attribute("to")); sendData(response.toUtf8()); disconnectFromHost(); return; } // send stream features QXmppStreamFeatures features; if (!socket()->isEncrypted() && !socket()->localCertificate().isNull() && !socket()->privateKey().isNull()) features.setSecurityMode(QXmppConfiguration::TLSEnabled); if (!d->username.isEmpty()) { features.setBindAvailable(true); features.setSessionAvailable(true); } else if (d->passwordChecker) { QList 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(); d->idleTimer->start(); if (ns == ns_tls && nodeRecv.tagName() == "starttls") { sendData(""); socket()->flush(); socket()->startServerEncryption(); return; } else if (ns == ns_sasl) { if (nodeRecv.tagName() == "auth") { const QString mechanism = nodeRecv.attribute("mechanism"); if (mechanism == "PLAIN") { QList auth = QByteArray::fromBase64(nodeRecv.text().toAscii()).split('\0'); if (auth.size() != 3) { sendData(""); disconnectFromHost(); return; } const QString username = QString::fromUtf8(auth[1]); const QString password = QString::fromUtf8(auth[2]); if (d->passwordChecker && d->passwordChecker->checkPassword(username, password)) { d->username = username; sendData(""); } else { sendData(""); disconnectFromHost(); return; } } else if (mechanism == "DIGEST-MD5") { // generate nonce QByteArray nonce(32, 'm'); for(int n = 0; n < nonce.size(); ++n) nonce[n] = (char)(256.0*qrand()/(RAND_MAX+1.0)); d->saslNonce = nonce.toBase64(); QMap challenge; challenge["nonce"] = d->saslNonce; challenge["realm"] = d->domain.toUtf8(); challenge["qop"] = "auth"; challenge["charset"] = "utf-8"; challenge["algorithm"] = "md5-sess"; const QByteArray data = serializeDigestMd5(challenge).toBase64(); sendData("" + data +""); } else { // unsupported method sendData(""); disconnectFromHost(); return; } } else if (nodeRecv.tagName() == "response") { const QByteArray raw = QByteArray::fromBase64(nodeRecv.text().toAscii()); QMap response = parseDigestMd5(raw); // check credentials const QString username = QString::fromUtf8(response.value("username")); QString password; if (!d->passwordChecker || !d->passwordChecker->getPassword(username, password)) { sendData(""); disconnectFromHost(); return; } const QByteArray a1 = username.toUtf8() + ':' + d->domain.toUtf8() + ':' + password.toUtf8(); const QByteArray remote = QByteArray::fromHex(response["response"]); if (remote != calculateDigestMd5(a1, d->saslNonce, response.value("nc"), response.value("cnonce"), response.value("digest-uri"), QByteArray())) { sendData(""); disconnectFromHost(); return; } // authentication succeeded d->username = username; sendData(""); } } else if (ns == ns_client) { if (nodeRecv.tagName() == "iq") { const QString type = nodeRecv.attribute("type"); if (QXmppBindIq::isBindIq(nodeRecv) && type == "set") { QXmppBindIq bindSet; bindSet.parse(nodeRecv); d->resource = bindSet.resource(); QXmppBindIq bindResult; bindResult.setType(QXmppIq::Result); bindResult.setId(bindSet.id()); bindResult.setJid(jid()); sendPacket(bindResult); // bound emit connected(); return; } else if (QXmppSessionIq::isSessionIq(nodeRecv) && type == "set") { QXmppSessionIq sessionSet; sessionSet.parse(nodeRecv); QXmppSessionIq sessionResult; sessionResult.setType(QXmppIq::Result); sessionResult.setId(sessionSet.id()); sendPacket(sessionResult); return; } } // check the sender is legitimate const QString from = nodeRecv.attribute("from"); if (!from.isEmpty() && from != jid() && from != jidToBareJid(jid())) { warning(QString("Received a stanza from unexpected JID %1").arg(from)); return; } // process unhandled stanzas if (nodeRecv.tagName() == "iq" || nodeRecv.tagName() == "message" || nodeRecv.tagName() == "presence") { QDomElement nodeFull(nodeRecv); // if the sender is empty, set it to the appropriate JID if (nodeFull.attribute("from").isEmpty()) { if (nodeFull.tagName() == "presence" && (nodeFull.attribute("type") == "subscribe" || nodeFull.attribute("type") == "subscribed")) nodeFull.setAttribute("from", jidToBareJid(jid())); else nodeFull.setAttribute("from", 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::slotTimeout() { warning(QString("Idle timeout for %1").arg(jid())); disconnectFromHost(); } /// Retrieves the password for the given username. /// /// You need to reimplement this method to support DIGEST-MD5 authentication. /// /// \param username /// \param password bool QXmppPasswordChecker::getPassword(const QString &username, QString &password) { Q_UNUSED(username); Q_UNUSED(password); return false; } /// Returns true if the getPassword() method is implemented. /// bool QXmppPasswordChecker::hasGetPassword() const { return false; }