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