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