void QXmppIncomingClientPrivate::checkCredentials (const QByteArray &response) { QXmppPasswordRequest request; request.setDomain (domain); request.setUsername (saslServer->username()); if (saslServer->mechanism() == "PLAIN") { request.setPassword (saslServer->password()); QXmppPasswordReply *reply = passwordChecker->checkPassword (request); reply->setParent (q); reply->setProperty ("__sasl_raw", response); QObject::connect (reply, SIGNAL (finished()), q, SLOT (onPasswordReply())); } else if (saslServer->mechanism() == "DIGEST-MD5") { QXmppPasswordReply *reply = passwordChecker->getDigest (request); reply->setParent (q); reply->setProperty ("__sasl_raw", response); QObject::connect (reply, SIGNAL (finished()), q, SLOT (onDigestReply())); } }
void QXmppIncomingClient::onDigestReply() { QXmppPasswordReply *reply = qobject_cast<QXmppPasswordReply *> (sender()); if (!reply) return; reply->deleteLater(); if (reply->error() == QXmppPasswordReply::TemporaryError) { warning (QString ("Temporary authentication failure for '%1' from %2").arg (d->saslServer->username(), d->origin())); updateCounter ("incoming-client.auth.temporary-auth-failure"); sendPacket (QXmppSaslFailure ("temporary-auth-failure")); disconnectFromHost(); return; } QByteArray challenge; d->saslServer->setPasswordDigest (reply->digest()); QXmppSaslServer::Response result = d->saslServer->respond (reply->property ("__sasl_raw").toByteArray(), challenge); if (result != QXmppSaslServer::Challenge) { warning (QString ("Authentication failed for '%1' from %2").arg (d->saslServer->username(), d->origin())); updateCounter ("incoming-client.auth.not-authorized"); sendPacket (QXmppSaslFailure ("not-authorized")); disconnectFromHost(); return; } // send new challenge sendPacket (QXmppSaslChallenge (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; info(QString("Authentication succeeded for '%1'").arg(username)); sendData("<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>"); 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; } }
QXmppPasswordReply *QXmppPasswordChecker::checkPassword(const QXmppPasswordRequest &request) { QXmppPasswordReply *reply = new QXmppPasswordReply; QString secret; QXmppPasswordReply::Error error = getPassword(request, secret); if (error == QXmppPasswordReply::NoError) { if (request.password() != secret) reply->setError(QXmppPasswordReply::AuthorizationError); } else { reply->setError(error); } // reply is finished reply->finishLater(); return reply; }
QXmppPasswordReply *QXmppPasswordChecker::getDigest(const QXmppPasswordRequest &request) { QXmppPasswordReply *reply = new QXmppPasswordReply; QString secret; QXmppPasswordReply::Error error = getPassword(request, secret); if (error == QXmppPasswordReply::NoError) { reply->setDigest(QCryptographicHash::hash( (request.username() + ":" + request.domain() + ":" + secret).toUtf8(), QCryptographicHash::Md5)); } else { reply->setError(error); } // reply is finished reply->finishLater(); return reply; }
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->username = username; d->saslStep = 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 jid = QString ("%1@%2").arg (d->saslServer->username(), d->domain); switch (reply->error()) { case QXmppPasswordReply::NoError: d->jid = jid; info (QString ("Authentication succeeded for '%1' from %2").arg (d->jid, d->origin())); updateCounter ("incoming-client.auth.success"); sendPacket (QXmppSaslSuccess()); handleStart(); break; case QXmppPasswordReply::AuthorizationError: warning (QString ("Authentication failed for '%1' from %2").arg (jid, d->origin())); updateCounter ("incoming-client.auth.not-authorized"); sendPacket (QXmppSaslFailure ("not-authorized")); disconnectFromHost(); break; case QXmppPasswordReply::TemporaryError: warning (QString ("Temporary authentication failure for '%1' from %2").arg (jid, d->origin())); updateCounter ("incoming-client.auth.temporary-auth-failure"); sendPacket (QXmppSaslFailure ("temporary-auth-failure")); disconnectFromHost(); break; } }
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); } } }