void TestPackets::testBindNoResource()
{
    const QByteArray xml(
        "<iq id=\"bind_1\" type=\"set\">"
        "<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"/>"
        "</iq>");

    QXmppBindIq bind;
    parsePacket(bind, xml);
    QCOMPARE(bind.type(), QXmppIq::Set);
    QCOMPARE(bind.id(), QString("bind_1"));
    QCOMPARE(bind.jid(), QString());
    QCOMPARE(bind.resource(), QString());
    serializePacket(bind, xml);
}
void TestPackets::testBindResult()
{
    const QByteArray xml(
        "<iq id=\"bind_2\" type=\"result\">"
        "<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\">"
        "<jid>[email protected]/someresource</jid>"
        "</bind>"
        "</iq>");

    QXmppBindIq bind;
    parsePacket(bind, xml);
    QCOMPARE(bind.type(), QXmppIq::Result);
    QCOMPARE(bind.id(), QString("bind_2"));
    QCOMPARE(bind.jid(), QString("[email protected]/someresource"));
    QCOMPARE(bind.resource(), QString());
    serializePacket(bind, xml);
}
void tst_QXmppBindIq::testResource()
{
    const QByteArray xml(
        "<iq id=\"bind_2\" type=\"set\">"
        "<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\">"
        "<resource>someresource</resource>"
        "</bind>"
        "</iq>");

    QXmppBindIq bind;
    parsePacket(bind, xml);
    QCOMPARE(bind.type(), QXmppIq::Set);
    QCOMPARE(bind.id(), QString("bind_2"));
    QCOMPARE(bind.jid(), QString());
    QCOMPARE(bind.resource(), QString("someresource"));
    serializePacket(bind, xml);
}
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 (!d->passwordChecker)
        {
            warning ("Cannot perform authentication, no password checker");
            sendPacket (QXmppSaslFailure ("temporary-auth-failure"));
            disconnectFromHost();
            return;
        }

        if (nodeRecv.tagName() == QLatin1String ("auth"))
        {
            QXmppSaslAuth auth;
            auth.parse (nodeRecv);

            d->saslServer = QXmppSaslServer::create (auth.mechanism(), this);

            if (!d->saslServer)
            {
                sendPacket (QXmppSaslFailure ("invalid-mechanism"));
                disconnectFromHost();
                return;
            }

            d->saslServer->setRealm (d->domain.toUtf8());

            QByteArray challenge;
            QXmppSaslServer::Response result = d->saslServer->respond (auth.value(), challenge);

            if (result == QXmppSaslServer::InputNeeded)
            {
                // check credentials
                d->checkCredentials (auth.value());
            }

            else if (result == QXmppSaslServer::Challenge)
                sendPacket (QXmppSaslChallenge (challenge));

            else
            {
                // FIXME: what condition?
                sendPacket (QXmppSaslFailure());
                disconnectFromHost();
                return;
            }
        }

        else if (nodeRecv.tagName() == QLatin1String ("response"))
        {
            QXmppSaslResponse response;
            response.parse (nodeRecv);

            if (!d->saslServer)
            {
                warning ("SASL response received, but no mechanism selected");
                sendPacket (QXmppSaslFailure());
                disconnectFromHost();
                return;
            }

            QByteArray challenge;
            QXmppSaslServer::Response result = d->saslServer->respond (response.value(), challenge);

            if (result == QXmppSaslServer::InputNeeded)
            {
                // check credentials
                d->checkCredentials (response.value());
            }

            else if (result == QXmppSaslServer::Succeeded)
            {
                // authentication succeeded
                d->jid = QString ("%1@%2").arg (d->saslServer->username(), d->domain);
                info (QString ("Authentication succeeded for '%1' from %2").arg (d->jid, d->origin()));
                updateCounter ("incoming-client.auth.success");
                sendPacket (QXmppSaslSuccess());
                handleStart();
            }

            else
            {
                // FIXME: what condition?
                sendPacket (QXmppSaslFailure());
                disconnectFromHost();
            }
        }
    }

    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 = QXmppUtils::generateStanzaHash();

                d->jid = QString ("%1/%2").arg (QXmppUtils::jidToBareJid (d->jid), 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 != QXmppUtils::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", QXmppUtils::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::handleStanza(const QDomElement &nodeRecv)
{
    const QString ns = nodeRecv.namespaceURI();

    if (d->idleTimer->interval())
        d->idleTimer->start();

    if (ns == ns_tls && nodeRecv.tagName() == "starttls")
    {
        sendData("<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
        socket()->flush();
        socket()->startServerEncryption();
        return;
    }
    else if (ns == ns_sasl)
    {
        if (nodeRecv.tagName() == "auth")
        {
            const QString mechanism = nodeRecv.attribute("mechanism");
            if (mechanism == "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 == "DIGEST-MD5")
            {
                // generate nonce
                d->saslDigest.setNonce(QXmppSaslDigestMd5::generateNonce());
                d->saslDigest.setQop("auth");
                d->saslStep = 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() == "response")
        {
            if (d->saslStep == 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->saslStep == 2)
            {
                // authentication succeeded
                d->saslStep = 3;
                info(QString("Authentication succeeded for '%1'").arg(d->username));
                sendData("<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>");
            }
        }
    }
    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().trimmed();
                if (d->resource.isEmpty())
                    d->resource = generateStanzaHash();

                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);

                QXmppIq sessionResult;
                sessionResult.setType(QXmppIq::Result);
                sessionResult.setId(sessionSet.id());
                sessionResult.setTo(jid());
                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);
        }
    }
}