1 /***************************************************************************
2 * Copyright (C) 2005-2020 by the Quassel Project *
3 * devel@quassel-irc.org *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) version 3. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
21 #include "coreauthhandler.h"
26 # include <QSslSocket>
31 CoreAuthHandler::CoreAuthHandler(QTcpSocket* socket, QObject* parent)
34 , _metricsServer(Core::instance()->metricsServer())
35 , _proxyReceived(false)
37 , _useProxyLine(false)
38 , _magicReceived(false)
40 , _clientRegistered(false)
41 , _connectionFeatures(0)
44 connect(socket, &QIODevice::readyRead, this, &CoreAuthHandler::onReadyRead);
46 // TODO: Timeout for the handshake phase
49 void CoreAuthHandler::onReadyRead()
51 // once we have selected a peer, we certainly don't want to read more data!
55 if (!_proxyReceived) {
57 socket()->peek((char*) &magic, 4);
58 magic = qFromBigEndian<quint32>(magic);
60 if (magic == Protocol::proxyMagic) {
61 if (!socket()->canReadLine()) {
64 QByteArray line = socket()->readLine(108);
65 _proxyLine = ProxyLine::parseProxyLine(line);
66 if (_proxyLine.protocol != QAbstractSocket::UnknownNetworkLayerProtocol) {
67 QList<QString> subnets = Quassel::optionValue("proxy-cidr").split(",");
68 for (const QString& subnet : subnets) {
69 if (socket()->peerAddress().isInSubnet(QHostAddress::parseSubnet(subnet))) {
76 _proxyReceived = true;
79 if (socket()->bytesAvailable() < 4)
82 if (!_magicReceived) {
84 socket()->peek((char*)&magic, 4);
85 magic = qFromBigEndian<quint32>(magic);
87 if ((magic & 0xffffff00) != Protocol::magic) {
88 // no magic, assume legacy protocol
89 qDebug() << "Legacy client detected, switching to compatibility mode";
91 RemotePeer* peer = PeerFactory::createPeer(PeerFactory::ProtoDescriptor(Protocol::LegacyProtocol, 0),
94 Compressor::NoCompression,
96 connect(peer, &RemotePeer::protocolVersionMismatch, this, &CoreAuthHandler::onProtocolVersionMismatch);
101 _magicReceived = true;
102 quint8 features = magic & 0xff;
103 // figure out which connection features we'll use based on the client's support
104 if (Core::sslSupported() && (features & Protocol::Encryption))
105 _connectionFeatures |= Protocol::Encryption;
106 if (features & Protocol::Compression)
107 _connectionFeatures |= Protocol::Compression;
109 socket()->read((char*)&magic, 4); // read the 4 bytes we've just peeked at
112 // read the list of protocols supported by the client
113 while (socket()->bytesAvailable() >= 4 && _supportedProtos.size() < 16) { // sanity check
115 socket()->read((char*)&data, 4);
116 data = qFromBigEndian<quint32>(data);
118 auto type = static_cast<Protocol::Type>(data & 0xff);
119 auto protoFeatures = static_cast<quint16>(data >> 8 & 0xffff);
120 _supportedProtos.append(PeerFactory::ProtoDescriptor(type, protoFeatures));
122 if (data >= 0x80000000) { // last protocol
123 Compressor::CompressionLevel level;
124 if (_connectionFeatures & Protocol::Compression)
125 level = Compressor::BestCompression;
127 level = Compressor::NoCompression;
129 RemotePeer* peer = PeerFactory::createPeer(_supportedProtos, this, socket(), level, this);
131 qWarning() << "Received invalid handshake data from client" << hostAddress().toString();
136 if (peer->protocol() == Protocol::LegacyProtocol) {
138 connect(peer, &RemotePeer::protocolVersionMismatch, this, &CoreAuthHandler::onProtocolVersionMismatch);
143 quint32 reply = peer->protocol() | peer->enabledFeatures() << 8 | _connectionFeatures << 24;
144 reply = qToBigEndian<quint32>(reply);
145 socket()->write((char*)&reply, 4);
148 if (!_legacy && (_connectionFeatures & Protocol::Encryption))
149 startSsl(); // legacy peer enables it later
155 void CoreAuthHandler::setPeer(RemotePeer* peer)
157 qDebug().nospace() << "Using " << qPrintable(peer->protocolName()) << "...";
160 if (_proxyLine.protocol != QAbstractSocket::UnknownNetworkLayerProtocol) {
161 _peer->setProxyLine(_proxyLine);
163 disconnect(socket(), &QIODevice::readyRead, this, &CoreAuthHandler::onReadyRead);
166 // only in compat mode
167 void CoreAuthHandler::onProtocolVersionMismatch(int actual, int expected)
169 qWarning() << qPrintable(tr("Client")) << _peer->description() << qPrintable(tr("too old, rejecting."));
170 QString errorString = tr("<b>Your Quassel Client is too old!</b><br>"
171 "This core needs at least client/core protocol version %1 (got: %2).<br>"
172 "Please consider upgrading your client.")
173 .arg(expected, actual);
174 _peer->dispatch(Protocol::ClientDenied(errorString));
178 bool CoreAuthHandler::checkClientRegistered()
180 if (!_clientRegistered) {
181 qWarning() << qPrintable(tr("Client")) << qPrintable(hostAddress().toString())
182 << qPrintable(tr("did not send a registration message before trying to login, rejecting."));
184 Protocol::ClientDenied(tr("<b>Client not initialized!</b><br>You need to send a registration message before trying to login.")));
191 void CoreAuthHandler::handle(const Protocol::RegisterClient& msg)
195 useSsl = Core::sslSupported() && msg.sslSupported;
197 useSsl = _connectionFeatures & Protocol::Encryption;
199 if (Quassel::isOptionSet("require-ssl") && !useSsl && !_peer->isLocal()) {
200 qInfo() << qPrintable(tr("SSL required but non-SSL connection attempt from %1").arg(hostAddress().toString()));
201 _peer->dispatch(Protocol::ClientDenied(tr("<b>SSL is required!</b><br>You need to use SSL in order to connect to this core.")));
206 _peer->setFeatures(std::move(msg.features));
207 _peer->setBuildDate(msg.buildDate);
208 _peer->setClientVersion(msg.clientVersion);
210 QVariantList backends;
211 QVariantList authenticators;
212 bool configured = Core::isConfigured();
214 backends = Core::backendInfo();
215 if (_peer->hasFeature(Quassel::Feature::Authenticators)) {
216 authenticators = Core::authenticatorInfo();
220 _peer->dispatch(Protocol::ClientRegistered(Quassel::Features{}, configured, backends, authenticators, useSsl));
222 // useSsl is only used for the legacy protocol
223 if (_legacy && useSsl)
226 _clientRegistered = true;
229 void CoreAuthHandler::handle(const Protocol::SetupData& msg)
231 if (!checkClientRegistered())
234 // The default parameter to authenticator is Database.
235 // Maybe this should be hardcoded elsewhere, i.e. as a define.
236 QString authenticator = msg.authenticator;
237 qInfo() << "[" << authenticator << "]";
238 if (authenticator.trimmed().isEmpty()) {
239 authenticator = QString("Database");
242 QString result = Core::setup(msg.adminUser, msg.adminPassword, msg.backend, msg.setupData, authenticator, msg.authSetupData);
243 if (!result.isEmpty())
244 _peer->dispatch(Protocol::SetupFailed(result));
246 _peer->dispatch(Protocol::SetupDone());
249 void CoreAuthHandler::handle(const Protocol::Login& msg)
251 if (!checkClientRegistered())
254 if (!Core::isConfigured()) {
255 qWarning() << qPrintable(tr("Client")) << qPrintable(hostAddress().toString())
256 << qPrintable(tr("attempted to login before the core was configured, rejecting."));
257 _peer->dispatch(Protocol::ClientDenied(
258 tr("<b>Attempted to login before core was configured!</b><br>The core must be configured before attempting to login.")));
262 // First attempt local auth using the real username and password.
263 // If that fails, move onto the auth provider.
265 // Check to see if the user has the "Database" authenticator configured.
267 if (Core::getUserAuthenticator(msg.user) == "Database") {
268 uid = Core::validateUser(msg.user, msg.password);
271 // If they did not, *or* if the database login fails, try to use a different authenticator.
272 // TODO: this logic should likely be moved into Core::authenticateUser in the future.
273 // Right now a core can only have one authenticator configured; this might be something
274 // to change in the future.
276 uid = Core::authenticateUser(msg.user, msg.password);
280 qInfo() << qPrintable(tr("Invalid login attempt from %1 as \"%2\"").arg(hostAddress().toString(), msg.user));
281 _peer->dispatch(Protocol::LoginFailed(tr(
282 "<b>Invalid username or password!</b><br>The username/password combination you supplied could not be found in the database.")));
283 if (_metricsServer) {
284 _metricsServer->addLoginAttempt(msg.user, false);
288 _peer->dispatch(Protocol::LoginSuccess());
289 if (_metricsServer) {
290 _metricsServer->addLoginAttempt(uid, true);
293 qInfo() << qPrintable(tr("Client %1 initialized and authenticated successfully as \"%2\" (UserId: %3).")
294 .arg(_peer->address(), msg.user, QString::number(uid.toInt())));
296 const auto& clientFeatures = _peer->features();
297 auto unsupported = clientFeatures.toStringList(false);
298 if (!unsupported.isEmpty()) {
299 if (unsupported.contains("NoFeatures"))
300 qInfo() << qPrintable(tr("Client does not support extended features."));
302 qInfo() << qPrintable(tr("Client does not support the following features: %1").arg(unsupported.join(", ")));
305 if (!clientFeatures.unknownFeatures().isEmpty()) {
306 qInfo() << qPrintable(tr("Client supports unknown features: %1").arg(clientFeatures.unknownFeatures().join(", ")));
309 disconnect(socket(), nullptr, this, nullptr);
310 disconnect(_peer, nullptr, this, nullptr);
311 _peer->setParent(nullptr); // Core needs to take care of this one now!
313 socket()->flush(); // Make sure all data is sent before handing over the peer (and socket) to the session thread (bug 682)
314 emit handshakeComplete(_peer, uid);
317 QHostAddress CoreAuthHandler::hostAddress() const
320 return _proxyLine.sourceHost;
323 return socket()->peerAddress();
329 bool CoreAuthHandler::isLocal() const
331 return hostAddress() == QHostAddress::LocalHost ||
332 hostAddress() == QHostAddress::LocalHostIPv6;
337 void CoreAuthHandler::startSsl()
340 auto* sslSocket = qobject_cast<QSslSocket*>(socket());
343 qDebug() << qPrintable(tr("Starting encryption for Client:")) << _peer->description();
344 connect(sslSocket, selectOverload<const QList<QSslError>&>(&QSslSocket::sslErrors), this, &CoreAuthHandler::onSslErrors);
345 sslSocket->flush(); // ensure that the write cache is flushed before we switch to ssl (bug 682)
346 sslSocket->startServerEncryption();
347 #endif /* HAVE_SSL */
351 void CoreAuthHandler::onSslErrors()
353 auto* sslSocket = qobject_cast<QSslSocket*>(socket());
355 sslSocket->ignoreSslErrors();