Merge pull request #195 from seezer/qt5sessions
[quassel.git] / src / core / coreauthhandler.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2015 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 "logger.h"
29
30 using namespace Protocol;
31
32 CoreAuthHandler::CoreAuthHandler(QTcpSocket *socket, QObject *parent)
33     : AuthHandler(parent),
34     _peer(0),
35     _magicReceived(false),
36     _legacy(false),
37     _clientRegistered(false),
38     _connectionFeatures(0)
39 {
40     setSocket(socket);
41     connect(socket, SIGNAL(readyRead()), SLOT(onReadyRead()));
42
43     // TODO: Timeout for the handshake phase
44
45 }
46
47
48 void CoreAuthHandler::onReadyRead()
49 {
50     if (socket()->bytesAvailable() < 4)
51         return;
52
53     // once we have selected a peer, we certainly don't want to read more data!
54     if (_peer)
55         return;
56
57     if (!_magicReceived) {
58         quint32 magic;
59         socket()->peek((char*)&magic, 4);
60         magic = qFromBigEndian<quint32>(magic);
61
62         if ((magic & 0xffffff00) != Protocol::magic) {
63             // no magic, assume legacy protocol
64             qDebug() << "Legacy client detected, switching to compatibility mode";
65             _legacy = true;
66             RemotePeer *peer = PeerFactory::createPeer(PeerFactory::ProtoDescriptor(Protocol::LegacyProtocol, 0), this, socket(), Compressor::NoCompression, this);
67             connect(peer, SIGNAL(protocolVersionMismatch(int,int)), SLOT(onProtocolVersionMismatch(int,int)));
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) {
85         quint32 data;
86         socket()->read((char*)&data, 4);
87         data = qFromBigEndian<quint32>(data);
88
89         Protocol::Type type = static_cast<Protocol::Type>(data & 0xff);
90         quint16 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->protocol() == Protocol::LegacyProtocol) {
102                 _legacy = true;
103                 connect(peer, SIGNAL(protocolVersionMismatch(int,int)), SLOT(onProtocolVersionMismatch(int,int)));
104             }
105             setPeer(peer);
106
107             // inform the client
108             quint32 reply = peer->protocol() | peer->enabledFeatures()<<8 | _connectionFeatures<<24;
109             reply = qToBigEndian<quint32>(reply);
110             socket()->write((char*)&reply, 4);
111             socket()->flush();
112
113             if (!_legacy && (_connectionFeatures & Protocol::Encryption))
114                 startSsl(); // legacy peer enables it later
115             return;
116         }
117     }
118 }
119
120
121 void CoreAuthHandler::setPeer(RemotePeer *peer)
122 {
123     qDebug().nospace() << "Using " << qPrintable(peer->protocolName()) << "...";
124
125     _peer = peer;
126     disconnect(socket(), SIGNAL(readyRead()), this, SLOT(onReadyRead()));
127 }
128
129 // only in compat mode
130 void CoreAuthHandler::onProtocolVersionMismatch(int actual, int expected)
131 {
132     qWarning() << qPrintable(tr("Client")) << _peer->description() << qPrintable(tr("too old, rejecting."));
133     QString errorString = tr("<b>Your Quassel Client is too old!</b><br>"
134                              "This core needs at least client/core protocol version %1 (got: %2).<br>"
135                              "Please consider upgrading your client.").arg(expected, actual);
136     _peer->dispatch(ClientDenied(errorString));
137     _peer->close();
138 }
139
140
141 bool CoreAuthHandler::checkClientRegistered()
142 {
143     if (!_clientRegistered) {
144         qWarning() << qPrintable(tr("Client")) << qPrintable(socket()->peerAddress().toString()) << qPrintable(tr("did not send a registration message before trying to login, rejecting."));
145         _peer->dispatch(ClientDenied(tr("<b>Client not initialized!</b><br>You need to send a registration message before trying to login.")));
146         _peer->close();
147         return false;
148     }
149     return true;
150 }
151
152
153 void CoreAuthHandler::handle(const RegisterClient &msg)
154 {
155     bool useSsl;
156     if (_legacy)
157         useSsl = Core::sslSupported() && msg.sslSupported;
158     else
159         useSsl = _connectionFeatures & Protocol::Encryption;
160
161     if (Quassel::isOptionSet("require-ssl") && !useSsl && !_peer->isLocal()) {
162         quInfo() << qPrintable(tr("SSL required but non-SSL connection attempt from %1").arg(socket()->peerAddress().toString()));
163         _peer->dispatch(ClientDenied(tr("<b>SSL is required!</b><br>You need to use SSL in order to connect to this core.")));
164         _peer->close();
165         return;
166     }
167
168     QVariantList backends;
169     bool configured = Core::isConfigured();
170     if (!configured)
171         backends = Core::backendInfo();
172
173     int uptime = Core::instance()->startTime().secsTo(QDateTime::currentDateTime().toUTC());
174     int updays = uptime / 86400; uptime %= 86400;
175     int uphours = uptime / 3600; uptime %= 3600;
176     int upmins = uptime / 60;
177     QString coreInfo = tr("<b>Quassel Core Version %1</b><br>"
178                           "Built: %2<br>"
179                           "Up %3d%4h%5m (since %6)").arg(Quassel::buildInfo().fancyVersionString)
180                           .arg(Quassel::buildInfo().buildDate)
181                           .arg(updays).arg(uphours, 2, 10, QChar('0')).arg(upmins, 2, 10, QChar('0')).arg(Core::instance()->startTime().toString(Qt::TextDate));
182
183     // useSsl and coreInfo are only used for the legacy protocol
184     _peer->dispatch(ClientRegistered(Quassel::features(), configured, backends, useSsl, coreInfo));
185
186     if (_legacy && useSsl)
187         startSsl();
188
189     _clientRegistered = true;
190 }
191
192
193 void CoreAuthHandler::handle(const SetupData &msg)
194 {
195     if (!checkClientRegistered())
196         return;
197
198     QString result = Core::setup(msg.adminUser, msg.adminPassword, msg.backend, msg.setupData);
199     if (!result.isEmpty())
200         _peer->dispatch(SetupFailed(result));
201     else
202         _peer->dispatch(SetupDone());
203 }
204
205
206 void CoreAuthHandler::handle(const Login &msg)
207 {
208     if (!checkClientRegistered())
209         return;
210
211     UserId uid = Core::validateUser(msg.user, msg.password);
212     if (uid == 0) {
213         quInfo() << qPrintable(tr("Invalid login attempt from %1 as \"%2\"").arg(socket()->peerAddress().toString(), msg.user));
214         _peer->dispatch(LoginFailed(tr("<b>Invalid username or password!</b><br>The username/password combination you supplied could not be found in the database.")));
215         return;
216     }
217     _peer->dispatch(LoginSuccess());
218
219     quInfo() << qPrintable(tr("Client %1 initialized and authenticated successfully as \"%2\" (UserId: %3).").arg(socket()->peerAddress().toString(), msg.user, QString::number(uid.toInt())));
220
221     disconnect(socket(), 0, this, 0);
222     disconnect(_peer, 0, this, 0);
223     _peer->setParent(0); // Core needs to take care of this one now!
224
225     socket()->flush(); // Make sure all data is sent before handing over the peer (and socket) to the session thread (bug 682)
226     emit handshakeComplete(_peer, uid);
227 }
228
229
230 /*** SSL Stuff ***/
231
232 void CoreAuthHandler::startSsl()
233 {
234     #ifdef HAVE_SSL
235     QSslSocket *sslSocket = qobject_cast<QSslSocket *>(socket());
236     Q_ASSERT(sslSocket);
237
238     qDebug() << qPrintable(tr("Starting encryption for Client:"))  << _peer->description();
239     connect(sslSocket, SIGNAL(sslErrors(const QList<QSslError> &)), SLOT(onSslErrors()));
240     sslSocket->flush(); // ensure that the write cache is flushed before we switch to ssl (bug 682)
241     sslSocket->startServerEncryption();
242     #endif /* HAVE_SSL */
243 }
244
245
246 #ifdef HAVE_SSL
247 void CoreAuthHandler::onSslErrors()
248 {
249     QSslSocket *sslSocket = qobject_cast<QSslSocket *>(socket());
250     Q_ASSERT(sslSocket);
251     sslSocket->ignoreSslErrors();
252 }
253 #endif
254