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