ssl: Use QSslSocket directly to avoid redundant qobject_casts
[quassel.git] / src / core / coreauthhandler.cpp
index 6cccaf8..b289520 100644 (file)
@@ -1,5 +1,5 @@
 /***************************************************************************
- *   Copyright (C) 2005-2018 by the Quassel Project                        *
+ *   Copyright (C) 2005-2020 by the Quassel Project                        *
  *   devel@quassel-irc.org                                                 *
  *                                                                         *
  *   This program is free software; you can redistribute it and/or modify  *
 
 #include "coreauthhandler.h"
 
-#ifdef HAVE_SSL
-#  include <QSslSocket>
-#endif
+#include <QtEndian>
 
-#include "core.h"
-#include "logger.h"
+#include <QSslSocket>
 
-using namespace Protocol;
+#include "core.h"
 
-CoreAuthHandler::CoreAuthHandler(QTcpSocket *socket, QObject *parent)
-    : AuthHandler(parent),
-    _peer(0),
-    _magicReceived(false),
-    _legacy(false),
-    _clientRegistered(false),
-    _connectionFeatures(0)
+CoreAuthHandler::CoreAuthHandler(QSslSocket* socket, QObject* parent)
+    : AuthHandler(parent)
+    , _peer(nullptr)
+    , _metricsServer(Core::instance()->metricsServer())
+    , _proxyReceived(false)
+    , _proxyLine({})
+    , _useProxyLine(false)
+    , _magicReceived(false)
+    , _legacy(false)
+    , _clientRegistered(false)
+    , _connectionFeatures(0)
 {
     setSocket(socket);
-    connect(socket, SIGNAL(readyRead()), SLOT(onReadyRead()));
+    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 (!_proxyReceived) {
+        quint32 magic;
+        socket()->peek((char*) &magic, 4);
+        magic = qFromBigEndian<quint32>(magic);
+
+        if (magic == Protocol::proxyMagic) {
+            if (!socket()->canReadLine()) {
+                return;
+            }
+            QByteArray line = socket()->readLine(108);
+            _proxyLine = ProxyLine::parseProxyLine(line);
+            if (_proxyLine.protocol != QAbstractSocket::UnknownNetworkLayerProtocol) {
+                QList<QString> subnets = Quassel::optionValue("proxy-cidr").split(",");
+                for (const QString& subnet : subnets) {
+                    if (socket()->peerAddress().isInSubnet(QHostAddress::parseSubnet(subnet))) {
+                        _useProxyLine = true;
+                        break;
+                    }
+                }
+            }
+        }
+        _proxyReceived = true;
+    }
+
+    if (socket()->bytesAvailable() < 4)
+        return;
+
     if (!_magicReceived) {
         quint32 magic;
         socket()->peek((char*)&magic, 4);
@@ -63,8 +86,12 @@ void CoreAuthHandler::onReadyRead()
             // 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, SIGNAL(protocolVersionMismatch(int,int)), SLOT(onProtocolVersionMismatch(int,int)));
+            RemotePeer* peer = PeerFactory::createPeer(PeerFactory::ProtoDescriptor(Protocol::LegacyProtocol, 0),
+                                                       this,
+                                                       socket(),
+                                                       Compressor::NoCompression,
+                                                       this);
+            connect(peer, &RemotePeer::protocolVersionMismatch, this, &CoreAuthHandler::onProtocolVersionMismatch);
             setPeer(peer);
             return;
         }
@@ -77,59 +104,61 @@ void CoreAuthHandler::onReadyRead()
         if (features & Protocol::Compression)
             _connectionFeatures |= Protocol::Compression;
 
-        socket()->read((char*)&magic, 4); // read the 4 bytes we've just peeked at
+        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
+    while (socket()->bytesAvailable() >= 4 && _supportedProtos.size() < 16) {  // sanity check
         quint32 data;
         socket()->read((char*)&data, 4);
         data = qFromBigEndian<quint32>(data);
 
-        Protocol::Type type = static_cast<Protocol::Type>(data & 0xff);
-        quint16 protoFeatures = static_cast<quint16>(data>>8 & 0xffff);
+        auto type = static_cast<Protocol::Type>(data & 0xff);
+        auto protoFeatures = static_cast<quint16>(data >> 8 & 0xffff);
         _supportedProtos.append(PeerFactory::ProtoDescriptor(type, protoFeatures));
 
-        if (data >= 0x80000000) { // last protocol
+        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);
+            RemotePeerpeer = PeerFactory::createPeer(_supportedProtos, this, socket(), level, this);
             if (!peer) {
-                qWarning() << "Received invalid handshake data from client" << socket()->peerAddress().toString();
+                qWarning() << "Received invalid handshake data from client" << hostAddress().toString();
                 close();
                 return;
             }
 
             if (peer->protocol() == Protocol::LegacyProtocol) {
                 _legacy = true;
-                connect(peer, SIGNAL(protocolVersionMismatch(int,int)), SLOT(onProtocolVersionMismatch(int,int)));
+                connect(peer, &RemotePeer::protocolVersionMismatch, this, &CoreAuthHandler::onProtocolVersionMismatch);
             }
             setPeer(peer);
 
             // inform the client
-            quint32 reply = peer->protocol() | peer->enabledFeatures()<<8 | _connectionFeatures<<24;
+            quint32 reply = peer->protocol() | peer->enabledFeatures() << 8 | _connectionFeatures << 24;
             reply = qToBigEndian<quint32>(reply);
             socket()->write((char*)&reply, 4);
             socket()->flush();
 
             if (!_legacy && (_connectionFeatures & Protocol::Encryption))
-                startSsl(); // legacy peer enables it later
+                startSsl();  // legacy peer enables it later
             return;
         }
     }
 }
 
-
-void CoreAuthHandler::setPeer(RemotePeer *peer)
+void CoreAuthHandler::setPeer(RemotePeer* peer)
 {
     qDebug().nospace() << "Using " << qPrintable(peer->protocolName()) << "...";
 
     _peer = peer;
-    disconnect(socket(), SIGNAL(readyRead()), this, SLOT(onReadyRead()));
+    if (_proxyLine.protocol != QAbstractSocket::UnknownNetworkLayerProtocol) {
+        _peer->setProxyLine(_proxyLine);
+    }
+    disconnect(socket(), &QIODevice::readyRead, this, &CoreAuthHandler::onReadyRead);
 }
 
 // only in compat mode
@@ -138,25 +167,26 @@ void CoreAuthHandler::onProtocolVersionMismatch(int actual, int expected)
     qWarning() << qPrintable(tr("Client")) << _peer->description() << qPrintable(tr("too old, rejecting."));
     QString errorString = tr("<b>Your Quassel Client is too old!</b><br>"
                              "This core needs at least client/core protocol version %1 (got: %2).<br>"
-                             "Please consider upgrading your client.").arg(expected, actual);
-    _peer->dispatch(ClientDenied(errorString));
+                             "Please consider upgrading your client.")
+                              .arg(expected, actual);
+    _peer->dispatch(Protocol::ClientDenied(errorString));
     _peer->close();
 }
 
-
 bool CoreAuthHandler::checkClientRegistered()
 {
     if (!_clientRegistered) {
-        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("<b>Client not initialized!</b><br>You need to send a registration message before trying to login.")));
+        qWarning() << qPrintable(tr("Client")) << qPrintable(hostAddress().toString())
+                   << qPrintable(tr("did not send a registration message before trying to login, rejecting."));
+        _peer->dispatch(
+            Protocol::ClientDenied(tr("<b>Client not initialized!</b><br>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 Protocol::RegisterClient& msg)
 {
     bool useSsl;
     if (_legacy)
@@ -165,8 +195,8 @@ void CoreAuthHandler::handle(const RegisterClient &msg)
         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("<b>SSL is required!</b><br>You need to use SSL in order to connect to this core.")));
+        qInfo() << qPrintable(tr("SSL required but non-SSL connection attempt from %1").arg(hostAddress().toString()));
+        _peer->dispatch(Protocol::ClientDenied(tr("<b>SSL is required!</b><br>You need to use SSL in order to connect to this core.")));
         _peer->close();
         return;
     }
@@ -185,7 +215,7 @@ void CoreAuthHandler::handle(const RegisterClient &msg)
         }
     }
 
-    _peer->dispatch(ClientRegistered(Quassel::Features{}, configured, backends, authenticators, useSsl));
+    _peer->dispatch(Protocol::ClientRegistered(Quassel::Features{}, configured, backends, authenticators, useSsl));
 
     // useSsl is only used for the legacy protocol
     if (_legacy && useSsl)
@@ -194,8 +224,7 @@ void CoreAuthHandler::handle(const RegisterClient &msg)
     _clientRegistered = true;
 }
 
-
-void CoreAuthHandler::handle(const SetupData &msg)
+void CoreAuthHandler::handle(const Protocol::SetupData& msg)
 {
     if (!checkClientRegistered())
         return;
@@ -203,90 +232,115 @@ void CoreAuthHandler::handle(const SetupData &msg)
     // The default parameter to authenticator is Database.
     // Maybe this should be hardcoded elsewhere, i.e. as a define.
     QString authenticator = msg.authenticator;
-    quInfo() << "[" << authenticator << "]";
+    qInfo() << "[" << 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));
+        _peer->dispatch(Protocol::SetupFailed(result));
     else
-        _peer->dispatch(SetupDone());
+        _peer->dispatch(Protocol::SetupDone());
 }
 
-
-void CoreAuthHandler::handle(const Login &msg)
+void CoreAuthHandler::handle(const Protocol::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("<b>Attempted to login before core was configured!</b><br>The core must be configured before attempting to login.")));
+        qWarning() << qPrintable(tr("Client")) << qPrintable(hostAddress().toString())
+                   << qPrintable(tr("attempted to login before the core was configured, rejecting."));
+        _peer->dispatch(Protocol::ClientDenied(
+            tr("<b>Attempted to login before core was configured!</b><br>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);
+
+    // Check to see if the user has the "Database" authenticator configured.
+    UserId uid = 0;
+    if (Core::getUserAuthenticator(msg.user) == "Database") {
+        uid = Core::validateUser(msg.user, msg.password);
+    }
+
+    // If they did not, *or* if the database login fails, try to use a different authenticator.
+    // TODO: this logic should likely be moved into Core::authenticateUser in the future.
+    // Right now a core can only have one authenticator configured; this might be something
+    // to change in the future.
     if (uid == 0) {
         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("<b>Invalid username or password!</b><br>The username/password combination you supplied could not be found in the database.")));
+        qInfo() << qPrintable(tr("Invalid login attempt from %1 as \"%2\"").arg(hostAddress().toString(), msg.user));
+        _peer->dispatch(Protocol::LoginFailed(tr(
+            "<b>Invalid username or password!</b><br>The username/password combination you supplied could not be found in the database.")));
+        if (_metricsServer) {
+            _metricsServer->addLoginAttempt(msg.user, false);
+        }
         return;
     }
-    _peer->dispatch(LoginSuccess());
+    _peer->dispatch(Protocol::LoginSuccess());
+    if (_metricsServer) {
+        _metricsServer->addLoginAttempt(uid, true);
+    }
 
-    quInfo() << qPrintable(tr("Client %1 initialized and authenticated successfully as \"%2\" (UserId: %3).").arg(socket()->peerAddress().toString(), msg.user, QString::number(uid.toInt())));
+    qInfo() << qPrintable(tr("Client %1 initialized and authenticated successfully as \"%2\" (UserId: %3).")
+                              .arg(_peer->address(), msg.user, QString::number(uid.toInt())));
 
-    const auto &clientFeatures = _peer->features();
+    const autoclientFeatures = _peer->features();
     auto unsupported = clientFeatures.toStringList(false);
     if (!unsupported.isEmpty()) {
         if (unsupported.contains("NoFeatures"))
-            quInfo() << qPrintable(tr("Client does not support extended features."));
+            qInfo() << qPrintable(tr("Client does not support extended features."));
         else
-            quInfo() << qPrintable(tr("Client does not support the following features: %1").arg(unsupported.join(", ")));
+            qInfo() << 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(", ")));
+        qInfo() << qPrintable(tr("Client supports unknown features: %1").arg(clientFeatures.unknownFeatures().join(", ")));
     }
 
-    disconnect(socket(), 0, this, 0);
-    disconnect(_peer, 0, this, 0);
-    _peer->setParent(0); // Core needs to take care of this one now!
+    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)
+    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);
 }
 
+QHostAddress CoreAuthHandler::hostAddress() const
+{
+    if (_useProxyLine) {
+        return _proxyLine.sourceHost;
+    }
+    else if (socket()) {
+        return socket()->peerAddress();
+    }
+
+    return {};
+}
+
+bool CoreAuthHandler::isLocal() const
+{
+    return hostAddress() == QHostAddress::LocalHost ||
+           hostAddress() == QHostAddress::LocalHostIPv6;
+}
 
 /*** SSL Stuff ***/
 
 void CoreAuthHandler::startSsl()
 {
-    #ifdef HAVE_SSL
-    QSslSocket *sslSocket = qobject_cast<QSslSocket *>(socket());
-    Q_ASSERT(sslSocket);
-
-    qDebug() << qPrintable(tr("Starting encryption for Client:"))  << _peer->description();
-    connect(sslSocket, SIGNAL(sslErrors(const QList<QSslError> &)), SLOT(onSslErrors()));
-    sslSocket->flush(); // ensure that the write cache is flushed before we switch to ssl (bug 682)
-    sslSocket->startServerEncryption();
-    #endif /* HAVE_SSL */
+    qDebug() << qPrintable(tr("Starting encryption for Client:")) << _peer->description();
+    connect(socket(), selectOverload<const QList<QSslError>&>(&QSslSocket::sslErrors), this, &CoreAuthHandler::onSslErrors);
+    socket()->flush();  // ensure that the write cache is flushed before we switch to ssl (bug 682)
+    socket()->startServerEncryption();
 }
 
-
-#ifdef HAVE_SSL
 void CoreAuthHandler::onSslErrors()
 {
-    QSslSocket *sslSocket = qobject_cast<QSslSocket *>(socket());
-    Q_ASSERT(sslSocket);
-    sslSocket->ignoreSslErrors();
+    socket()->ignoreSslErrors();
 }
-#endif
-