9b1c56f4cac6ca278472e7b8c3d025020a13497f
[quassel.git] / src / common / protocols / legacy / legacypeer.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 "legacypeer.h"
22
23 #include <QDataStream>
24 #include <QHostAddress>
25 #include <QTcpSocket>
26
27 #include "quassel.h"
28
29 #include "serializers/serializers.h"
30
31 /* version.inc is no longer used for this */
32 const uint protocolVersion = 10;
33 const uint coreNeedsProtocol = protocolVersion;
34 const uint clientNeedsProtocol = protocolVersion;
35
36 using namespace Protocol;
37
38 LegacyPeer::LegacyPeer(::AuthHandler* authHandler, QTcpSocket* socket, Compressor::CompressionLevel level, QObject* parent)
39     : RemotePeer(authHandler, socket, level, parent)
40     , _useCompression(false)
41 {}
42
43 void LegacyPeer::setSignalProxy(::SignalProxy* proxy)
44 {
45     RemotePeer::setSignalProxy(proxy);
46
47     // FIXME only in compat mode
48     if (proxy) {
49         // enable compression now if requested - the initial handshake is uncompressed in the legacy protocol!
50         _useCompression = socket()->property("UseCompression").toBool();
51         if (_useCompression)
52             qDebug() << "Using compression for peer:" << qPrintable(address());
53     }
54 }
55
56 void LegacyPeer::processMessage(const QByteArray& msg)
57 {
58     QDataStream stream(msg);
59     stream.setVersion(QDataStream::Qt_4_2);
60
61     QVariant item;
62     if (_useCompression) {
63         QByteArray rawItem;
64         if (!Serializers::deserialize(stream, features(), rawItem)) {
65             close("Peer sent corrupt data: unable to load QVariant!");
66             return;
67         }
68
69         int nbytes = rawItem.size();
70         if (nbytes <= 4) {
71             const char* data = rawItem.constData();
72             if (nbytes < 4 || (data[0] != 0 || data[1] != 0 || data[2] != 0 || data[3] != 0)) {
73                 close("Peer sent corrupted compressed data!");
74                 return;
75             }
76         }
77
78         rawItem = qUncompress(rawItem);
79
80         QDataStream itemStream(&rawItem, QIODevice::ReadOnly);
81         itemStream.setVersion(QDataStream::Qt_4_2);
82         if (!Serializers::deserialize(itemStream, features(), item)) {
83             close("Peer sent corrupt data: unable to load QVariant!");
84             return;
85         }
86     }
87     else {
88         if (!Serializers::deserialize(stream, features(), item)) {
89             close("Peer sent corrupt data: unable to load QVariant!");
90             return;
91         }
92     }
93
94     if (stream.status() != QDataStream::Ok || !item.isValid()) {
95         close("Peer sent corrupt data: unable to load QVariant!");
96         return;
97     }
98
99     // if no sigproxy is set, we're in handshake mode and let the data be handled elsewhere
100     if (!signalProxy())
101         handleHandshakeMessage(item);
102     else
103         handlePackedFunc(item);
104 }
105
106 void LegacyPeer::writeMessage(const QVariant& item)
107 {
108     QByteArray block;
109     QDataStream out(&block, QIODevice::WriteOnly);
110     out.setVersion(QDataStream::Qt_4_2);
111
112     if (_useCompression) {
113         QByteArray rawItem;
114         QDataStream itemStream(&rawItem, QIODevice::WriteOnly);
115         itemStream.setVersion(QDataStream::Qt_4_2);
116         itemStream << item;
117
118         rawItem = qCompress(rawItem);
119
120         out << rawItem;
121     }
122     else {
123         out << item;
124     }
125
126     writeMessage(block);
127 }
128
129 /*** Handshake messages ***/
130
131 /* These messages are transmitted during handshake phase, which in case of the legacy protocol means they have
132  * a structure different from those being used after the handshake.
133  * Also, the legacy handshake does not fully match the redesigned one, so we'll have to do various mappings here.
134  */
135
136 void LegacyPeer::handleHandshakeMessage(const QVariant& msg)
137 {
138     QVariantMap m = msg.toMap();
139
140     QString msgType = m["MsgType"].toString();
141     if (msgType.isEmpty()) {
142         emit protocolError(tr("Invalid handshake message!"));
143         return;
144     }
145
146     if (msgType == "ClientInit") {
147         // FIXME only in compat mode
148         uint ver = m["ProtocolVersion"].toUInt();
149         if (ver < coreNeedsProtocol) {
150             emit protocolVersionMismatch((int)ver, (int)coreNeedsProtocol);
151             return;
152         }
153
154 #ifndef QT_NO_COMPRESS
155         // FIXME only in compat mode
156         if (m["UseCompression"].toBool()) {
157             socket()->setProperty("UseCompression", true);
158         }
159 #endif
160         handle(RegisterClient{Quassel::Features{m["FeatureList"].toStringList(), Quassel::LegacyFeatures(m["Features"].toUInt())},
161                               m["ClientVersion"].toString(),
162                               m["ClientDate"].toString(),
163                               m["UseSsl"].toBool()});
164     }
165
166     else if (msgType == "ClientInitReject") {
167         handle(ClientDenied(m["Error"].toString()));
168     }
169
170     else if (msgType == "ClientInitAck") {
171         // FIXME only in compat mode
172         uint ver = m["ProtocolVersion"].toUInt();  // actually an UInt
173         if (ver < clientNeedsProtocol) {
174             emit protocolVersionMismatch((int)ver, (int)clientNeedsProtocol);
175             return;
176         }
177 #ifndef QT_NO_COMPRESS
178         if (m["SupportsCompression"].toBool())
179             socket()->setProperty("UseCompression", true);
180 #endif
181
182         handle(ClientRegistered{Quassel::Features{m["FeatureList"].toStringList(), Quassel::LegacyFeatures(m["CoreFeatures"].toUInt())},
183                                 m["Configured"].toBool(),
184                                 m["StorageBackends"].toList(),
185                                 m["Authenticators"].toList(),
186                                 m["SupportSsl"].toBool()});
187     }
188
189     else if (msgType == "CoreSetupData") {
190         QVariantMap map = m["SetupData"].toMap();
191         handle(SetupData(map["AdminUser"].toString(),
192                          map["AdminPasswd"].toString(),
193                          map["Backend"].toString(),
194                          map["ConnectionProperties"].toMap(),
195                          map["Authenticator"].toString(),
196                          map["AuthProperties"].toMap()));
197     }
198
199     else if (msgType == "CoreSetupReject") {
200         handle(SetupFailed(m["Error"].toString()));
201     }
202
203     else if (msgType == "CoreSetupAck") {
204         handle(SetupDone());
205     }
206
207     else if (msgType == "ClientLogin") {
208         handle(Login(m["User"].toString(), m["Password"].toString()));
209     }
210
211     else if (msgType == "ClientLoginReject") {
212         handle(LoginFailed(m["Error"].toString()));
213     }
214
215     else if (msgType == "ClientLoginAck") {
216         handle(LoginSuccess());
217     }
218
219     else if (msgType == "SessionInit") {
220         QVariantMap map = m["SessionState"].toMap();
221         handle(SessionState(map["Identities"].toList(), map["BufferInfos"].toList(), map["NetworkIds"].toList()));
222     }
223
224     else {
225         emit protocolError(tr("Unknown protocol message of type %1").arg(msgType));
226     }
227 }
228
229 void LegacyPeer::dispatch(const RegisterClient& msg)
230 {
231     QVariantMap m;
232     m["MsgType"] = "ClientInit";
233     m["Features"] = static_cast<quint32>(msg.features.toLegacyFeatures());
234     m["FeatureList"] = msg.features.toStringList();
235     m["ClientVersion"] = msg.clientVersion;
236     m["ClientDate"] = msg.buildDate;
237
238     // FIXME only in compat mode
239     m["ProtocolVersion"] = protocolVersion;
240     m["UseSsl"] = msg.sslSupported;
241 #ifndef QT_NO_COMPRESS
242     m["UseCompression"] = true;
243 #else
244     m["UseCompression"] = false;
245 #endif
246
247     writeMessage(m);
248 }
249
250 void LegacyPeer::dispatch(const ClientDenied& msg)
251 {
252     QVariantMap m;
253     m["MsgType"] = "ClientInitReject";
254     m["Error"] = msg.errorString;
255
256     writeMessage(m);
257 }
258
259 void LegacyPeer::dispatch(const ClientRegistered& msg)
260 {
261     QVariantMap m;
262     m["MsgType"] = "ClientInitAck";
263     if (hasFeature(Quassel::Feature::ExtendedFeatures)) {
264         m["FeatureList"] = msg.features.toStringList();
265     }
266     else {
267         m["CoreFeatures"] = static_cast<quint32>(msg.features.toLegacyFeatures());
268     }
269     m["StorageBackends"] = msg.backendInfo;
270     if (hasFeature(Quassel::Feature::Authenticators)) {
271         m["Authenticators"] = msg.authenticatorInfo;
272     }
273
274     // FIXME only in compat mode
275     m["ProtocolVersion"] = protocolVersion;
276     m["SupportSsl"] = msg.sslSupported;
277     m["SupportsCompression"] = socket()->property("UseCompression").toBool();  // this property gets already set in the ClientInit handler
278
279     // This is only used for display by really old v10 clients (pre-0.5), and we no longer set this
280     m["CoreInfo"] = QString();
281
282     m["LoginEnabled"] = m["Configured"] = msg.coreConfigured;
283
284     writeMessage(m);
285 }
286
287 void LegacyPeer::dispatch(const SetupData& msg)
288 {
289     QVariantMap map;
290     map["AdminUser"] = msg.adminUser;
291     map["AdminPasswd"] = msg.adminPassword;
292     map["Backend"] = msg.backend;
293     map["ConnectionProperties"] = msg.setupData;
294
295     // Auth backend properties.
296     map["Authenticator"] = msg.authenticator;
297     map["AuthProperties"] = msg.authSetupData;
298
299     QVariantMap m;
300     m["MsgType"] = "CoreSetupData";
301     m["SetupData"] = map;
302     writeMessage(m);
303 }
304
305 void LegacyPeer::dispatch(const SetupFailed& msg)
306 {
307     QVariantMap m;
308     m["MsgType"] = "CoreSetupReject";
309     m["Error"] = msg.errorString;
310
311     writeMessage(m);
312 }
313
314 void LegacyPeer::dispatch(const SetupDone& msg)
315 {
316     Q_UNUSED(msg)
317
318     QVariantMap m;
319     m["MsgType"] = "CoreSetupAck";
320
321     writeMessage(m);
322 }
323
324 void LegacyPeer::dispatch(const Login& msg)
325 {
326     QVariantMap m;
327     m["MsgType"] = "ClientLogin";
328     m["User"] = msg.user;
329     m["Password"] = msg.password;
330
331     writeMessage(m);
332 }
333
334 void LegacyPeer::dispatch(const LoginFailed& msg)
335 {
336     QVariantMap m;
337     m["MsgType"] = "ClientLoginReject";
338     m["Error"] = msg.errorString;
339
340     writeMessage(m);
341 }
342
343 void LegacyPeer::dispatch(const LoginSuccess& msg)
344 {
345     Q_UNUSED(msg)
346
347     QVariantMap m;
348     m["MsgType"] = "ClientLoginAck";
349
350     writeMessage(m);
351 }
352
353 void LegacyPeer::dispatch(const SessionState& msg)
354 {
355     QVariantMap m;
356     m["MsgType"] = "SessionInit";
357
358     QVariantMap map;
359     map["BufferInfos"] = msg.bufferInfos;
360     map["NetworkIds"] = msg.networkIds;
361     map["Identities"] = msg.identities;
362     m["SessionState"] = map;
363
364     writeMessage(m);
365 }
366
367 /*** Standard messages ***/
368
369 void LegacyPeer::handlePackedFunc(const QVariant& packedFunc)
370 {
371     QVariantList params(packedFunc.toList());
372
373     if (params.isEmpty()) {
374         qWarning() << Q_FUNC_INFO << "Received incompatible data:" << packedFunc;
375         return;
376     }
377
378     // TODO: make sure that this is a valid request type
379     RequestType requestType = (RequestType)params.takeFirst().value<int>();
380     switch (requestType) {
381     case Sync: {
382         if (params.count() < 3) {
383             qWarning() << Q_FUNC_INFO << "Received invalid sync call:" << params;
384             return;
385         }
386         QByteArray className = params.takeFirst().toByteArray();
387         QString objectName = params.takeFirst().toString();
388         QByteArray slotName = params.takeFirst().toByteArray();
389         handle(Protocol::SyncMessage(className, objectName, slotName, params));
390         break;
391     }
392     case RpcCall: {
393         if (params.empty()) {
394             qWarning() << Q_FUNC_INFO << "Received empty RPC call!";
395             return;
396         }
397         QByteArray signalName = params.takeFirst().toByteArray();
398         handle(Protocol::RpcCall(signalName, params));
399         break;
400     }
401     case InitRequest: {
402         if (params.count() != 2) {
403             qWarning() << Q_FUNC_INFO << "Received invalid InitRequest:" << params;
404             return;
405         }
406         QByteArray className = params[0].toByteArray();
407         QString objectName = params[1].toString();
408         handle(Protocol::InitRequest(className, objectName));
409         break;
410     }
411     case InitData: {
412         if (params.count() != 3) {
413             qWarning() << Q_FUNC_INFO << "Received invalid InitData:" << params;
414             return;
415         }
416         QByteArray className = params[0].toByteArray();
417         QString objectName = params[1].toString();
418         QVariantMap initData = params[2].toMap();
419
420         // we need to special-case IrcUsersAndChannels here, since the format changed
421         if (className == "Network")
422             fromLegacyIrcUsersAndChannels(initData);
423         handle(Protocol::InitData(className, objectName, initData));
424         break;
425     }
426     case HeartBeat: {
427         if (params.count() != 1) {
428             qWarning() << Q_FUNC_INFO << "Received invalid HeartBeat:" << params;
429             return;
430         }
431         // The legacy protocol would only send a QTime, no QDateTime
432         // so we assume it's sent today, which works in exactly the same cases as it did in the old implementation
433         QDateTime dateTime = QDateTime::currentDateTime().toUTC();
434         dateTime.setTime(params[0].toTime());
435         handle(Protocol::HeartBeat(dateTime));
436         break;
437     }
438     case HeartBeatReply: {
439         if (params.count() != 1) {
440             qWarning() << Q_FUNC_INFO << "Received invalid HeartBeat:" << params;
441             return;
442         }
443         // The legacy protocol would only send a QTime, no QDateTime
444         // so we assume it's sent today, which works in exactly the same cases as it did in the old implementation
445         QDateTime dateTime = QDateTime::currentDateTime().toUTC();
446         dateTime.setTime(params[0].toTime());
447         handle(Protocol::HeartBeatReply(dateTime));
448         break;
449     }
450     }
451 }
452
453 void LegacyPeer::dispatch(const Protocol::SyncMessage& msg)
454 {
455     dispatchPackedFunc(QVariantList() << (qint16)Sync << msg.className << msg.objectName << msg.slotName << msg.params);
456 }
457
458 void LegacyPeer::dispatch(const Protocol::RpcCall& msg)
459 {
460     dispatchPackedFunc(QVariantList() << (qint16)RpcCall << msg.signalName << msg.params);
461 }
462
463 void LegacyPeer::dispatch(const Protocol::InitRequest& msg)
464 {
465     dispatchPackedFunc(QVariantList() << (qint16)InitRequest << msg.className << msg.objectName);
466 }
467
468 void LegacyPeer::dispatch(const Protocol::InitData& msg)
469 {
470     // We need to special-case IrcUsersAndChannels, as the format changed
471     if (msg.className == "Network") {
472         QVariantMap initData = msg.initData;
473         toLegacyIrcUsersAndChannels(initData);
474         dispatchPackedFunc(QVariantList() << (qint16)InitData << msg.className << msg.objectName << initData);
475     }
476     else
477         dispatchPackedFunc(QVariantList() << (qint16)InitData << msg.className << msg.objectName << msg.initData);
478 }
479
480 void LegacyPeer::dispatch(const Protocol::HeartBeat& msg)
481 {
482     dispatchPackedFunc(QVariantList() << (qint16)HeartBeat << msg.timestamp.time());
483 }
484
485 void LegacyPeer::dispatch(const Protocol::HeartBeatReply& msg)
486 {
487     dispatchPackedFunc(QVariantList() << (qint16)HeartBeatReply << msg.timestamp.time());
488 }
489
490 void LegacyPeer::dispatchPackedFunc(const QVariantList& packedFunc)
491 {
492     writeMessage(QVariant(packedFunc));
493 }
494
495 // Handle the changed format for Network's initData
496 // cf. Network::initIrcUsersAndChannels()
497 void LegacyPeer::fromLegacyIrcUsersAndChannels(QVariantMap& initData)
498 {
499     const QVariantMap& legacyMap = initData["IrcUsersAndChannels"].toMap();
500     QVariantMap newMap;
501
502     QHash<QString, QVariantList> users;
503     foreach (const QVariant& v, legacyMap["users"].toMap().values()) {
504         const QVariantMap& map = v.toMap();
505         foreach (const QString& key, map.keys())
506             users[key] << map[key];
507     }
508     QVariantMap userMap;
509     foreach (const QString& key, users.keys())
510         userMap[key] = users[key];
511     newMap["Users"] = userMap;
512
513     QHash<QString, QVariantList> channels;
514     foreach (const QVariant& v, legacyMap["channels"].toMap().values()) {
515         const QVariantMap& map = v.toMap();
516         foreach (const QString& key, map.keys())
517             channels[key] << map[key];
518     }
519     QVariantMap channelMap;
520     foreach (const QString& key, channels.keys())
521         channelMap[key] = channels[key];
522     newMap["Channels"] = channelMap;
523
524     initData["IrcUsersAndChannels"] = newMap;
525 }
526
527 void LegacyPeer::toLegacyIrcUsersAndChannels(QVariantMap& initData)
528 {
529     const QVariantMap& usersAndChannels = initData["IrcUsersAndChannels"].toMap();
530     QVariantMap legacyMap;
531
532     // toMap() and toList() are cheap, so no need to copy to a hash
533
534     QVariantMap userMap;
535     const QVariantMap& users = usersAndChannels["Users"].toMap();
536
537     int size = users["nick"].toList().size();  // we know this key exists
538     for (int i = 0; i < size; i++) {
539         QVariantMap map;
540         foreach (const QString& key, users.keys())
541             map[key] = users[key].toList().at(i);
542         QString hostmask = QString("%1!%2@%3").arg(map["nick"].toString(), map["user"].toString(), map["host"].toString());
543         userMap[hostmask.toLower()] = map;
544     }
545     legacyMap["users"] = userMap;
546
547     QVariantMap channelMap;
548     const QVariantMap& channels = usersAndChannels["Channels"].toMap();
549
550     size = channels["name"].toList().size();
551     for (int i = 0; i < size; i++) {
552         QVariantMap map;
553         foreach (const QString& key, channels.keys())
554             map[key] = channels[key].toList().at(i);
555         channelMap[map["name"].toString().toLower()] = map;
556     }
557     legacyMap["channels"] = channelMap;
558
559     initData["IrcUsersAndChannels"] = legacyMap;
560 }