X-Git-Url: https://git.quassel-irc.org/?p=quassel.git;a=blobdiff_plain;f=src%2Fcore%2Fcoreauthhandler.cpp;h=d559e44e162c505fe6cbbe8b10309e2d5f929bbe;hp=5942676f87b1ad7c8d0d9d7c1388a384bc1c8c8c;hb=c1cf157116de7fc3da96203aa6f03c38c7ebb650;hpb=64cf9f9b8a737dad5f29447805d4004cfd03c454 diff --git a/src/core/coreauthhandler.cpp b/src/core/coreauthhandler.cpp index 5942676f..d559e44e 100644 --- a/src/core/coreauthhandler.cpp +++ b/src/core/coreauthhandler.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2005-2013 by the Quassel Project * + * Copyright (C) 2005-2018 by the Quassel Project * * devel@quassel-irc.org * * * * This program is free software; you can redistribute it and/or modify * @@ -21,32 +21,117 @@ #include "coreauthhandler.h" #ifdef HAVE_SSL -# include +# include #endif #include "core.h" -#include "logger.h" - -#include "protocols/legacy/legacypeer.h" +#include "logmessage.h" using namespace Protocol; -CoreAuthHandler::CoreAuthHandler(QTcpSocket *socket, QObject *parent) +CoreAuthHandler::CoreAuthHandler(QTcpSocket* socket, QObject* parent) : AuthHandler(parent) - , _peer(0) + , _peer(nullptr) + , _magicReceived(false) + , _legacy(false) , _clientRegistered(false) + , _connectionFeatures(0) { setSocket(socket); + connect(socket, &QIODevice::readyRead, this, &CoreAuthHandler::onReadyRead); + + // TODO: Timeout for the handshake phase +} + +void CoreAuthHandler::onReadyRead() +{ + if (socket()->bytesAvailable() < 4) + return; + + // once we have selected a peer, we certainly don't want to read more data! + if (_peer) + return; + + if (!_magicReceived) { + quint32 magic; + socket()->peek((char*)&magic, 4); + magic = qFromBigEndian(magic); + + if ((magic & 0xffffff00) != Protocol::magic) { + // no magic, assume legacy protocol + qDebug() << "Legacy client detected, switching to compatibility mode"; + _legacy = true; + RemotePeer* peer = PeerFactory::createPeer(PeerFactory::ProtoDescriptor(Protocol::LegacyProtocol, 0), + this, + socket(), + Compressor::NoCompression, + this); + connect(peer, &RemotePeer::protocolVersionMismatch, this, &CoreAuthHandler::onProtocolVersionMismatch); + setPeer(peer); + return; + } + + _magicReceived = true; + quint8 features = magic & 0xff; + // figure out which connection features we'll use based on the client's support + if (Core::sslSupported() && (features & Protocol::Encryption)) + _connectionFeatures |= Protocol::Encryption; + if (features & Protocol::Compression) + _connectionFeatures |= Protocol::Compression; - // TODO: protocol detection + socket()->read((char*)&magic, 4); // read the 4 bytes we've just peeked at + } + + // read the list of protocols supported by the client + while (socket()->bytesAvailable() >= 4 && _supportedProtos.size() < 16) { // sanity check + quint32 data; + socket()->read((char*)&data, 4); + data = qFromBigEndian(data); + + auto type = static_cast(data & 0xff); + auto protoFeatures = static_cast(data >> 8 & 0xffff); + _supportedProtos.append(PeerFactory::ProtoDescriptor(type, protoFeatures)); + + if (data >= 0x80000000) { // last protocol + Compressor::CompressionLevel level; + if (_connectionFeatures & Protocol::Compression) + level = Compressor::BestCompression; + else + level = Compressor::NoCompression; + + RemotePeer* peer = PeerFactory::createPeer(_supportedProtos, this, socket(), level, this); + if (!peer) { + qWarning() << "Received invalid handshake data from client" << socket()->peerAddress().toString(); + close(); + return; + } + + if (peer->protocol() == Protocol::LegacyProtocol) { + _legacy = true; + connect(peer, &RemotePeer::protocolVersionMismatch, this, &CoreAuthHandler::onProtocolVersionMismatch); + } + setPeer(peer); - // FIXME: make sure _peer gets deleted - // TODO: socket ownership goes to the peer! (-> use shared ptr later...) - _peer = new LegacyPeer(this, socket, this); - // only in compat mode - connect(_peer, SIGNAL(protocolVersionMismatch(int,int)), SLOT(onProtocolVersionMismatch(int,int))); + // inform the client + quint32 reply = peer->protocol() | peer->enabledFeatures() << 8 | _connectionFeatures << 24; + reply = qToBigEndian(reply); + socket()->write((char*)&reply, 4); + socket()->flush(); + + if (!_legacy && (_connectionFeatures & Protocol::Encryption)) + startSsl(); // legacy peer enables it later + return; + } + } } +void CoreAuthHandler::setPeer(RemotePeer* peer) +{ + qDebug().nospace() << "Using " << qPrintable(peer->protocolName()) << "..."; + + _peer = peer; + disconnect(socket(), &QIODevice::readyRead, this, &CoreAuthHandler::onReadyRead); +} // only in compat mode void CoreAuthHandler::onProtocolVersionMismatch(int actual, int expected) @@ -54,99 +139,155 @@ void CoreAuthHandler::onProtocolVersionMismatch(int actual, int expected) qWarning() << qPrintable(tr("Client")) << _peer->description() << qPrintable(tr("too old, rejecting.")); QString errorString = tr("Your Quassel Client is too old!
" "This core needs at least client/core protocol version %1 (got: %2).
" - "Please consider upgrading your client.").arg(expected, actual); + "Please consider upgrading your client.") + .arg(expected, actual); _peer->dispatch(ClientDenied(errorString)); _peer->close(); } - -void CoreAuthHandler::startSsl() -{ -#ifdef HAVE_SSL - QSslSocket *sslSocket = qobject_cast(socket()); - Q_ASSERT(sslSocket); - - qDebug() << qPrintable(tr("Starting encryption for Client:")) << _peer->description(); - connect(sslSocket, SIGNAL(sslErrors(const QList &)), SLOT(onSslErrors())); - sslSocket->startServerEncryption(); -#endif -} - - -#ifdef HAVE_SSL -void CoreAuthHandler::onSslErrors() -{ - QSslSocket *sslSocket = qobject_cast(socket()); - Q_ASSERT(sslSocket); - sslSocket->ignoreSslErrors(); -} -#endif - - bool CoreAuthHandler::checkClientRegistered() { if (!_clientRegistered) { - qWarning() << qPrintable(tr("Client")) << qPrintable(socket()->peerAddress().toString()) << qPrintable(tr("did not send an init message before trying to login, rejecting.")); - _peer->dispatch(ClientDenied(tr("Client not initialized!
You need to send an init message before trying to login."))); + qWarning() << qPrintable(tr("Client")) << qPrintable(socket()->peerAddress().toString()) + << qPrintable(tr("did not send a registration message before trying to login, rejecting.")); + _peer->dispatch( + ClientDenied(tr("Client not initialized!
You need to send a registration message before trying to login."))); _peer->close(); return false; } return true; } - -void CoreAuthHandler::handle(const RegisterClient &msg) +void CoreAuthHandler::handle(const RegisterClient& msg) { - // TODO: only in compat mode - bool useSsl = false; -#ifdef HAVE_SSL - if (Core::sslSupported() && msg.sslSupported) - useSsl = true; -#endif + bool useSsl; + if (_legacy) + useSsl = Core::sslSupported() && msg.sslSupported; + else + useSsl = _connectionFeatures & Protocol::Encryption; + + if (Quassel::isOptionSet("require-ssl") && !useSsl && !_peer->isLocal()) { + quInfo() << qPrintable(tr("SSL required but non-SSL connection attempt from %1").arg(socket()->peerAddress().toString())); + _peer->dispatch(ClientDenied(tr("SSL is required!
You need to use SSL in order to connect to this core."))); + _peer->close(); + return; + } + + _peer->setFeatures(std::move(msg.features)); + _peer->setBuildDate(msg.buildDate); + _peer->setClientVersion(msg.clientVersion); + QVariantList backends; + QVariantList authenticators; bool configured = Core::isConfigured(); - if (!configured) + if (!configured) { backends = Core::backendInfo(); + if (_peer->hasFeature(Quassel::Feature::Authenticators)) { + authenticators = Core::authenticatorInfo(); + } + } + + _peer->dispatch(ClientRegistered(Quassel::Features{}, configured, backends, authenticators, useSsl)); - _peer->dispatch(ClientRegistered(Quassel::features(), configured, backends, useSsl, Core::instance()->startTime())); - // TODO: only in compat mode - if (useSsl) + // useSsl is only used for the legacy protocol + if (_legacy && useSsl) startSsl(); _clientRegistered = true; } - -void CoreAuthHandler::handle(const SetupData &msg) +void CoreAuthHandler::handle(const SetupData& msg) { if (!checkClientRegistered()) return; - QString result = Core::setup(msg.adminUser, msg.adminPassword, msg.backend, msg.setupData); + // The default parameter to authenticator is Database. + // Maybe this should be hardcoded elsewhere, i.e. as a define. + QString authenticator = msg.authenticator; + quInfo() << "[" << authenticator << "]"; + if (authenticator.trimmed().isEmpty()) { + authenticator = QString("Database"); + } + + QString result = Core::setup(msg.adminUser, msg.adminPassword, msg.backend, msg.setupData, authenticator, msg.authSetupData); if (!result.isEmpty()) _peer->dispatch(SetupFailed(result)); else _peer->dispatch(SetupDone()); } - -void CoreAuthHandler::handle(const Login &msg) +void CoreAuthHandler::handle(const Login& msg) { if (!checkClientRegistered()) return; + if (!Core::isConfigured()) { + qWarning() << qPrintable(tr("Client")) << qPrintable(socket()->peerAddress().toString()) + << qPrintable(tr("attempted to login before the core was configured, rejecting.")); + _peer->dispatch(ClientDenied( + tr("Attempted to login before core was configured!
The core must be configured before attempting to login."))); + return; + } + + // First attempt local auth using the real username and password. + // If that fails, move onto the auth provider. UserId uid = Core::validateUser(msg.user, msg.password); if (uid == 0) { - _peer->dispatch(LoginFailed(tr("Invalid username or password!
The username/password combination you supplied could not be found in the database."))); + uid = Core::authenticateUser(msg.user, msg.password); + } + + if (uid == 0) { + quInfo() << qPrintable(tr("Invalid login attempt from %1 as \"%2\"").arg(socket()->peerAddress().toString(), msg.user)); + _peer->dispatch(LoginFailed(tr( + "Invalid username or password!
The username/password combination you supplied could not be found in the database."))); return; } _peer->dispatch(LoginSuccess()); - quInfo() << qPrintable(tr("Client %1 initialized and authenticated successfully as \"%2\" (UserId: %3).").arg(socket()->peerAddress().toString(), msg.user, QString::number(uid.toInt()))); + quInfo() << qPrintable(tr("Client %1 initialized and authenticated successfully as \"%2\" (UserId: %3).") + .arg(socket()->peerAddress().toString(), msg.user, QString::number(uid.toInt()))); - disconnect(socket(), 0, this, 0); - disconnect(_peer, 0, this, 0); - _peer->setParent(0); // Core needs to take care of this one now! + const auto& clientFeatures = _peer->features(); + auto unsupported = clientFeatures.toStringList(false); + if (!unsupported.isEmpty()) { + if (unsupported.contains("NoFeatures")) + quInfo() << qPrintable(tr("Client does not support extended features.")); + else + quInfo() << qPrintable(tr("Client does not support the following features: %1").arg(unsupported.join(", "))); + } + + if (!clientFeatures.unknownFeatures().isEmpty()) { + quInfo() << qPrintable(tr("Client supports unknown features: %1").arg(clientFeatures.unknownFeatures().join(", "))); + } + disconnect(socket(), nullptr, this, nullptr); + disconnect(_peer, nullptr, this, nullptr); + _peer->setParent(nullptr); // Core needs to take care of this one now! + + socket()->flush(); // Make sure all data is sent before handing over the peer (and socket) to the session thread (bug 682) emit handshakeComplete(_peer, uid); } + +/*** SSL Stuff ***/ + +void CoreAuthHandler::startSsl() +{ +#ifdef HAVE_SSL + auto* sslSocket = qobject_cast(socket()); + Q_ASSERT(sslSocket); + + qDebug() << qPrintable(tr("Starting encryption for Client:")) << _peer->description(); + connect(sslSocket, selectOverload&>(&QSslSocket::sslErrors), this, &CoreAuthHandler::onSslErrors); + sslSocket->flush(); // ensure that the write cache is flushed before we switch to ssl (bug 682) + sslSocket->startServerEncryption(); +#endif /* HAVE_SSL */ +} + +#ifdef HAVE_SSL +void CoreAuthHandler::onSslErrors() +{ + auto* sslSocket = qobject_cast(socket()); + Q_ASSERT(sslSocket); + sslSocket->ignoreSslErrors(); +} +#endif