8d39d06d1a97122c3a0872efa0c1a8580633094a
[quassel.git] / src / core / coreauthhandler.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2020 by the Quassel Project                        *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
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.                                           *
9  *                                                                         *
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.                          *
14  *                                                                         *
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  ***************************************************************************/
20
21 #include "coreauthhandler.h"
22
23 #include <QtEndian>
24
25 #ifdef HAVE_SSL
26 #    include <QSslSocket>
27 #endif
28
29 #include "core.h"
30
31 CoreAuthHandler::CoreAuthHandler(QTcpSocket* socket, QObject* parent)
32     : AuthHandler(parent)
33     , _peer(nullptr)
34     , _metricsServer(Core::instance()->metricsServer())
35     , _proxyReceived(false)
36     , _proxyLine({})
37     , _useProxyLine(false)
38     , _magicReceived(false)
39     , _legacy(false)
40     , _clientRegistered(false)
41     , _connectionFeatures(0)
42 {
43     setSocket(socket);
44     connect(socket, &QIODevice::readyRead, this, &CoreAuthHandler::onReadyRead);
45
46     // TODO: Timeout for the handshake phase
47 }
48
49 void CoreAuthHandler::onReadyRead()
50 {
51     // once we have selected a peer, we certainly don't want to read more data!
52     if (_peer)
53         return;
54
55     if (!_proxyReceived) {
56         quint32 magic;
57         socket()->peek((char*) &magic, 4);
58         magic = qFromBigEndian<quint32>(magic);
59
60         if (magic == Protocol::proxyMagic) {
61             if (!socket()->canReadLine()) {
62                 return;
63             }
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))) {
70                         _useProxyLine = true;
71                         break;
72                     }
73                 }
74             }
75         }
76         _proxyReceived = true;
77     }
78
79     if (socket()->bytesAvailable() < 4)
80         return;
81
82     if (!_magicReceived) {
83         quint32 magic;
84         socket()->peek((char*)&magic, 4);
85         magic = qFromBigEndian<quint32>(magic);
86
87         if ((magic & 0xffffff00) != Protocol::magic) {
88             // no magic, assume legacy protocol
89             qDebug() << "Legacy client detected, switching to compatibility mode";
90             _legacy = true;
91             RemotePeer* peer = PeerFactory::createPeer(PeerFactory::ProtoDescriptor(Protocol::LegacyProtocol, 0),
92                                                        this,
93                                                        socket(),
94                                                        Compressor::NoCompression,
95                                                        this);
96             connect(peer, &RemotePeer::protocolVersionMismatch, this, &CoreAuthHandler::onProtocolVersionMismatch);
97             setPeer(peer);
98             return;
99         }
100
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;
108
109         socket()->read((char*)&magic, 4);  // read the 4 bytes we've just peeked at
110     }
111
112     // read the list of protocols supported by the client
113     while (socket()->bytesAvailable() >= 4 && _supportedProtos.size() < 16) {  // sanity check
114         quint32 data;
115         socket()->read((char*)&data, 4);
116         data = qFromBigEndian<quint32>(data);
117
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));
121
122         if (data >= 0x80000000) {  // last protocol
123             Compressor::CompressionLevel level;
124             if (_connectionFeatures & Protocol::Compression)
125                 level = Compressor::BestCompression;
126             else
127                 level = Compressor::NoCompression;
128
129             RemotePeer* peer = PeerFactory::createPeer(_supportedProtos, this, socket(), level, this);
130             if (!peer) {
131                 qWarning() << "Received invalid handshake data from client" << hostAddress().toString();
132                 close();
133                 return;
134             }
135
136             if (peer->protocol() == Protocol::LegacyProtocol) {
137                 _legacy = true;
138                 connect(peer, &RemotePeer::protocolVersionMismatch, this, &CoreAuthHandler::onProtocolVersionMismatch);
139             }
140             setPeer(peer);
141
142             // inform the client
143             quint32 reply = peer->protocol() | peer->enabledFeatures() << 8 | _connectionFeatures << 24;
144             reply = qToBigEndian<quint32>(reply);
145             socket()->write((char*)&reply, 4);
146             socket()->flush();
147
148             if (!_legacy && (_connectionFeatures & Protocol::Encryption))
149                 startSsl();  // legacy peer enables it later
150             return;
151         }
152     }
153 }
154
155 void CoreAuthHandler::setPeer(RemotePeer* peer)
156 {
157     qDebug().nospace() << "Using " << qPrintable(peer->protocolName()) << "...";
158
159     _peer = peer;
160     if (_proxyLine.protocol != QAbstractSocket::UnknownNetworkLayerProtocol) {
161         _peer->setProxyLine(_proxyLine);
162     }
163     disconnect(socket(), &QIODevice::readyRead, this, &CoreAuthHandler::onReadyRead);
164 }
165
166 // only in compat mode
167 void CoreAuthHandler::onProtocolVersionMismatch(int actual, int expected)
168 {
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));
175     _peer->close();
176 }
177
178 bool CoreAuthHandler::checkClientRegistered()
179 {
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."));
183         _peer->dispatch(
184             Protocol::ClientDenied(tr("<b>Client not initialized!</b><br>You need to send a registration message before trying to login.")));
185         _peer->close();
186         return false;
187     }
188     return true;
189 }
190
191 void CoreAuthHandler::handle(const Protocol::RegisterClient& msg)
192 {
193     bool useSsl;
194     if (_legacy)
195         useSsl = Core::sslSupported() && msg.sslSupported;
196     else
197         useSsl = _connectionFeatures & Protocol::Encryption;
198
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.")));
202         _peer->close();
203         return;
204     }
205
206     _peer->setFeatures(std::move(msg.features));
207     _peer->setBuildDate(msg.buildDate);
208     _peer->setClientVersion(msg.clientVersion);
209
210     QVariantList backends;
211     QVariantList authenticators;
212     bool configured = Core::isConfigured();
213     if (!configured) {
214         backends = Core::backendInfo();
215         if (_peer->hasFeature(Quassel::Feature::Authenticators)) {
216             authenticators = Core::authenticatorInfo();
217         }
218     }
219
220     _peer->dispatch(Protocol::ClientRegistered(Quassel::Features{}, configured, backends, authenticators, useSsl));
221
222     // useSsl is only used for the legacy protocol
223     if (_legacy && useSsl)
224         startSsl();
225
226     _clientRegistered = true;
227 }
228
229 void CoreAuthHandler::handle(const Protocol::SetupData& msg)
230 {
231     if (!checkClientRegistered())
232         return;
233
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");
240     }
241
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));
245     else
246         _peer->dispatch(Protocol::SetupDone());
247 }
248
249 void CoreAuthHandler::handle(const Protocol::Login& msg)
250 {
251     if (!checkClientRegistered())
252         return;
253
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.")));
259         return;
260     }
261
262     // First attempt local auth using the real username and password.
263     // If that fails, move onto the auth provider.
264
265     // Check to see if the user has the "Database" authenticator configured.
266     UserId uid = 0;
267     if (Core::getUserAuthenticator(msg.user) == "Database") {
268         uid = Core::validateUser(msg.user, msg.password);
269     }
270
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.
275     if (uid == 0) {
276         uid = Core::authenticateUser(msg.user, msg.password);
277     }
278
279     if (uid == 0) {
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);
285         }
286         return;
287     }
288     _peer->dispatch(Protocol::LoginSuccess());
289     if (_metricsServer) {
290         _metricsServer->addLoginAttempt(uid, true);
291     }
292
293     qInfo() << qPrintable(tr("Client %1 initialized and authenticated successfully as \"%2\" (UserId: %3).")
294                               .arg(_peer->address(), msg.user, QString::number(uid.toInt())));
295
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."));
301         else
302             qInfo() << qPrintable(tr("Client does not support the following features: %1").arg(unsupported.join(", ")));
303     }
304
305     if (!clientFeatures.unknownFeatures().isEmpty()) {
306         qInfo() << qPrintable(tr("Client supports unknown features: %1").arg(clientFeatures.unknownFeatures().join(", ")));
307     }
308
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!
312
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);
315 }
316
317 QHostAddress CoreAuthHandler::hostAddress() const
318 {
319     if (_useProxyLine) {
320         return _proxyLine.sourceHost;
321     }
322     else if (socket()) {
323         return socket()->peerAddress();
324     }
325
326     return {};
327 }
328
329 bool CoreAuthHandler::isLocal() const
330 {
331     return hostAddress() == QHostAddress::LocalHost ||
332            hostAddress() == QHostAddress::LocalHostIPv6;
333 }
334
335 /*** SSL Stuff ***/
336
337 void CoreAuthHandler::startSsl()
338 {
339 #ifdef HAVE_SSL
340     auto* sslSocket = qobject_cast<QSslSocket*>(socket());
341     Q_ASSERT(sslSocket);
342
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 */
348 }
349
350 #ifdef HAVE_SSL
351 void CoreAuthHandler::onSslErrors()
352 {
353     auto* sslSocket = qobject_cast<QSslSocket*>(socket());
354     Q_ASSERT(sslSocket);
355     sslSocket->ignoreSslErrors();
356 }
357 #endif