cmake: Don't needlessly sync translations
[quassel.git] / src / core / coreauthhandler.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2019 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 #ifdef HAVE_SSL
24 #    include <QSslSocket>
25 #endif
26
27 #include "core.h"
28
29 CoreAuthHandler::CoreAuthHandler(QTcpSocket* socket, QObject* parent)
30     : AuthHandler(parent)
31     , _peer(nullptr)
32     , _metricsServer(Core::instance()->metricsServer())
33     , _magicReceived(false)
34     , _legacy(false)
35     , _clientRegistered(false)
36     , _connectionFeatures(0)
37 {
38     setSocket(socket);
39     connect(socket, &QIODevice::readyRead, this, &CoreAuthHandler::onReadyRead);
40
41     // TODO: Timeout for the handshake phase
42 }
43
44 void CoreAuthHandler::onReadyRead()
45 {
46     if (socket()->bytesAvailable() < 4)
47         return;
48
49     // once we have selected a peer, we certainly don't want to read more data!
50     if (_peer)
51         return;
52
53     if (!_magicReceived) {
54         quint32 magic;
55         socket()->peek((char*)&magic, 4);
56         magic = qFromBigEndian<quint32>(magic);
57
58         if ((magic & 0xffffff00) != Protocol::magic) {
59             // no magic, assume legacy protocol
60             qDebug() << "Legacy client detected, switching to compatibility mode";
61             _legacy = true;
62             RemotePeer* peer = PeerFactory::createPeer(PeerFactory::ProtoDescriptor(Protocol::LegacyProtocol, 0),
63                                                        this,
64                                                        socket(),
65                                                        Compressor::NoCompression,
66                                                        this);
67             connect(peer, &RemotePeer::protocolVersionMismatch, this, &CoreAuthHandler::onProtocolVersionMismatch);
68             setPeer(peer);
69             return;
70         }
71
72         _magicReceived = true;
73         quint8 features = magic & 0xff;
74         // figure out which connection features we'll use based on the client's support
75         if (Core::sslSupported() && (features & Protocol::Encryption))
76             _connectionFeatures |= Protocol::Encryption;
77         if (features & Protocol::Compression)
78             _connectionFeatures |= Protocol::Compression;
79
80         socket()->read((char*)&magic, 4);  // read the 4 bytes we've just peeked at
81     }
82
83     // read the list of protocols supported by the client
84     while (socket()->bytesAvailable() >= 4 && _supportedProtos.size() < 16) {  // sanity check
85         quint32 data;
86         socket()->read((char*)&data, 4);
87         data = qFromBigEndian<quint32>(data);
88
89         auto type = static_cast<Protocol::Type>(data & 0xff);
90         auto protoFeatures = static_cast<quint16>(data >> 8 & 0xffff);
91         _supportedProtos.append(PeerFactory::ProtoDescriptor(type, protoFeatures));
92
93         if (data >= 0x80000000) {  // last protocol
94             Compressor::CompressionLevel level;
95             if (_connectionFeatures & Protocol::Compression)
96                 level = Compressor::BestCompression;
97             else
98                 level = Compressor::NoCompression;
99
100             RemotePeer* peer = PeerFactory::createPeer(_supportedProtos, this, socket(), level, this);
101             if (!peer) {
102                 qWarning() << "Received invalid handshake data from client" << socket()->peerAddress().toString();
103                 close();
104                 return;
105             }
106
107             if (peer->protocol() == Protocol::LegacyProtocol) {
108                 _legacy = true;
109                 connect(peer, &RemotePeer::protocolVersionMismatch, this, &CoreAuthHandler::onProtocolVersionMismatch);
110             }
111             setPeer(peer);
112
113             // inform the client
114             quint32 reply = peer->protocol() | peer->enabledFeatures() << 8 | _connectionFeatures << 24;
115             reply = qToBigEndian<quint32>(reply);
116             socket()->write((char*)&reply, 4);
117             socket()->flush();
118
119             if (!_legacy && (_connectionFeatures & Protocol::Encryption))
120                 startSsl();  // legacy peer enables it later
121             return;
122         }
123     }
124 }
125
126 void CoreAuthHandler::setPeer(RemotePeer* peer)
127 {
128     qDebug().nospace() << "Using " << qPrintable(peer->protocolName()) << "...";
129
130     _peer = peer;
131     disconnect(socket(), &QIODevice::readyRead, this, &CoreAuthHandler::onReadyRead);
132 }
133
134 // only in compat mode
135 void CoreAuthHandler::onProtocolVersionMismatch(int actual, int expected)
136 {
137     qWarning() << qPrintable(tr("Client")) << _peer->description() << qPrintable(tr("too old, rejecting."));
138     QString errorString = tr("<b>Your Quassel Client is too old!</b><br>"
139                              "This core needs at least client/core protocol version %1 (got: %2).<br>"
140                              "Please consider upgrading your client.")
141                               .arg(expected, actual);
142     _peer->dispatch(Protocol::ClientDenied(errorString));
143     _peer->close();
144 }
145
146 bool CoreAuthHandler::checkClientRegistered()
147 {
148     if (!_clientRegistered) {
149         qWarning() << qPrintable(tr("Client")) << qPrintable(socket()->peerAddress().toString())
150                    << qPrintable(tr("did not send a registration message before trying to login, rejecting."));
151         _peer->dispatch(
152             Protocol::ClientDenied(tr("<b>Client not initialized!</b><br>You need to send a registration message before trying to login.")));
153         _peer->close();
154         return false;
155     }
156     return true;
157 }
158
159 void CoreAuthHandler::handle(const Protocol::RegisterClient& msg)
160 {
161     bool useSsl;
162     if (_legacy)
163         useSsl = Core::sslSupported() && msg.sslSupported;
164     else
165         useSsl = _connectionFeatures & Protocol::Encryption;
166
167     if (Quassel::isOptionSet("require-ssl") && !useSsl && !_peer->isLocal()) {
168         qInfo() << qPrintable(tr("SSL required but non-SSL connection attempt from %1").arg(socket()->peerAddress().toString()));
169         _peer->dispatch(Protocol::ClientDenied(tr("<b>SSL is required!</b><br>You need to use SSL in order to connect to this core.")));
170         _peer->close();
171         return;
172     }
173
174     _peer->setFeatures(std::move(msg.features));
175     _peer->setBuildDate(msg.buildDate);
176     _peer->setClientVersion(msg.clientVersion);
177
178     QVariantList backends;
179     QVariantList authenticators;
180     bool configured = Core::isConfigured();
181     if (!configured) {
182         backends = Core::backendInfo();
183         if (_peer->hasFeature(Quassel::Feature::Authenticators)) {
184             authenticators = Core::authenticatorInfo();
185         }
186     }
187
188     _peer->dispatch(Protocol::ClientRegistered(Quassel::Features{}, configured, backends, authenticators, useSsl));
189
190     // useSsl is only used for the legacy protocol
191     if (_legacy && useSsl)
192         startSsl();
193
194     _clientRegistered = true;
195 }
196
197 void CoreAuthHandler::handle(const Protocol::SetupData& msg)
198 {
199     if (!checkClientRegistered())
200         return;
201
202     // The default parameter to authenticator is Database.
203     // Maybe this should be hardcoded elsewhere, i.e. as a define.
204     QString authenticator = msg.authenticator;
205     qInfo() << "[" << authenticator << "]";
206     if (authenticator.trimmed().isEmpty()) {
207         authenticator = QString("Database");
208     }
209
210     QString result = Core::setup(msg.adminUser, msg.adminPassword, msg.backend, msg.setupData, authenticator, msg.authSetupData);
211     if (!result.isEmpty())
212         _peer->dispatch(Protocol::SetupFailed(result));
213     else
214         _peer->dispatch(Protocol::SetupDone());
215 }
216
217 void CoreAuthHandler::handle(const Protocol::Login& msg)
218 {
219     if (!checkClientRegistered())
220         return;
221
222     if (!Core::isConfigured()) {
223         qWarning() << qPrintable(tr("Client")) << qPrintable(socket()->peerAddress().toString())
224                    << qPrintable(tr("attempted to login before the core was configured, rejecting."));
225         _peer->dispatch(Protocol::ClientDenied(
226             tr("<b>Attempted to login before core was configured!</b><br>The core must be configured before attempting to login.")));
227         return;
228     }
229
230     // First attempt local auth using the real username and password.
231     // If that fails, move onto the auth provider.
232
233     // Check to see if the user has the "Database" authenticator configured.
234     UserId uid = 0;
235     if (Core::getUserAuthenticator(msg.user) == "Database") {
236         uid = Core::validateUser(msg.user, msg.password);
237     }
238
239     // If they did not, *or* if the database login fails, try to use a different authenticator.
240     // TODO: this logic should likely be moved into Core::authenticateUser in the future.
241     // Right now a core can only have one authenticator configured; this might be something
242     // to change in the future.
243     if (uid == 0) {
244         uid = Core::authenticateUser(msg.user, msg.password);
245     }
246
247     if (uid == 0) {
248         qInfo() << qPrintable(tr("Invalid login attempt from %1 as \"%2\"").arg(socket()->peerAddress().toString(), msg.user));
249         _peer->dispatch(Protocol::LoginFailed(tr(
250             "<b>Invalid username or password!</b><br>The username/password combination you supplied could not be found in the database.")));
251         if (_metricsServer) {
252             _metricsServer->addLoginAttempt(msg.user, false);
253         }
254         return;
255     }
256     _peer->dispatch(Protocol::LoginSuccess());
257     if (_metricsServer) {
258         _metricsServer->addLoginAttempt(uid, true);
259     }
260
261     qInfo() << qPrintable(tr("Client %1 initialized and authenticated successfully as \"%2\" (UserId: %3).")
262                           .arg(socket()->peerAddress().toString(), msg.user, QString::number(uid.toInt())));
263
264     const auto& clientFeatures = _peer->features();
265     auto unsupported = clientFeatures.toStringList(false);
266     if (!unsupported.isEmpty()) {
267         if (unsupported.contains("NoFeatures"))
268             qInfo() << qPrintable(tr("Client does not support extended features."));
269         else
270             qInfo() << qPrintable(tr("Client does not support the following features: %1").arg(unsupported.join(", ")));
271     }
272
273     if (!clientFeatures.unknownFeatures().isEmpty()) {
274         qInfo() << qPrintable(tr("Client supports unknown features: %1").arg(clientFeatures.unknownFeatures().join(", ")));
275     }
276
277     disconnect(socket(), nullptr, this, nullptr);
278     disconnect(_peer, nullptr, this, nullptr);
279     _peer->setParent(nullptr);  // Core needs to take care of this one now!
280
281     socket()->flush();  // Make sure all data is sent before handing over the peer (and socket) to the session thread (bug 682)
282     emit handshakeComplete(_peer, uid);
283 }
284
285 /*** SSL Stuff ***/
286
287 void CoreAuthHandler::startSsl()
288 {
289 #ifdef HAVE_SSL
290     auto* sslSocket = qobject_cast<QSslSocket*>(socket());
291     Q_ASSERT(sslSocket);
292
293     qDebug() << qPrintable(tr("Starting encryption for Client:")) << _peer->description();
294     connect(sslSocket, selectOverload<const QList<QSslError>&>(&QSslSocket::sslErrors), this, &CoreAuthHandler::onSslErrors);
295     sslSocket->flush();  // ensure that the write cache is flushed before we switch to ssl (bug 682)
296     sslSocket->startServerEncryption();
297 #endif /* HAVE_SSL */
298 }
299
300 #ifdef HAVE_SSL
301 void CoreAuthHandler::onSslErrors()
302 {
303     auto* sslSocket = qobject_cast<QSslSocket*>(socket());
304     Q_ASSERT(sslSocket);
305     sslSocket->ignoreSslErrors();
306 }
307 #endif