Quassel IRC  Pre-Release
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros
legacypeer.cpp
Go to the documentation of this file.
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 <QHostAddress>
22 #include <QDataStream>
23 #include <QTcpSocket>
24 
25 #include "legacypeer.h"
26 
27 /* version.inc is no longer used for this */
28 const uint protocolVersion = 10;
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 
43 {
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())
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(), QString()));
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 
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 
232  QVariantMap m;
233  m["MsgType"] = "ClientInitReject";
234  m["Error"] = msg.errorString;
235 
236  writeMessage(m);
237 }
238 
239 
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  m["CoreInfo"] = msg.coreInfo;
253 
254  m["LoginEnabled"] = m["Configured"] = msg.coreConfigured;
255 
256  writeMessage(m);
257 }
258 
259 
261 {
262  QVariantMap map;
263  map["AdminUser"] = msg.adminUser;
264  map["AdminPasswd"] = msg.adminPassword;
265  map["Backend"] = msg.backend;
266  map["ConnectionProperties"] = msg.setupData;
267 
268  QVariantMap m;
269  m["MsgType"] = "CoreSetupData";
270  m["SetupData"] = map;
271  writeMessage(m);
272 }
273 
274 
276 {
277  QVariantMap m;
278  m["MsgType"] = "CoreSetupReject";
279  m["Error"] = msg.errorString;
280 
281  writeMessage(m);
282 }
283 
284 
286 {
287  Q_UNUSED(msg)
288 
289  QVariantMap m;
290  m["MsgType"] = "CoreSetupAck";
291 
292  writeMessage(m);
293 }
294 
295 
296 void LegacyPeer::dispatch(const Login &msg)
297 {
298  QVariantMap m;
299  m["MsgType"] = "ClientLogin";
300  m["User"] = msg.user;
301  m["Password"] = msg.password;
302 
303  writeMessage(m);
304 }
305 
306 
308 {
309  QVariantMap m;
310  m["MsgType"] = "ClientLoginReject";
311  m["Error"] = msg.errorString;
312 
313  writeMessage(m);
314 }
315 
316 
318 {
319  Q_UNUSED(msg)
320 
321  QVariantMap m;
322  m["MsgType"] = "ClientLoginAck";
323 
324  writeMessage(m);
325 }
326 
327 
329 {
330  QVariantMap m;
331  m["MsgType"] = "SessionInit";
332 
333  QVariantMap map;
334  map["BufferInfos"] = msg.bufferInfos;
335  map["NetworkIds"] = msg.networkIds;
336  map["Identities"] = msg.identities;
337  m["SessionState"] = map;
338 
339  writeMessage(m);
340 }
341 
342 
343 /*** Standard messages ***/
344 
345 void LegacyPeer::handlePackedFunc(const QVariant &packedFunc)
346 {
347  QVariantList params(packedFunc.toList());
348 
349  if (params.isEmpty()) {
350  qWarning() << Q_FUNC_INFO << "Received incompatible data:" << packedFunc;
351  return;
352  }
353 
354  // TODO: make sure that this is a valid request type
355  RequestType requestType = (RequestType)params.takeFirst().value<int>();
356  switch (requestType) {
357  case Sync: {
358  if (params.count() < 3) {
359  qWarning() << Q_FUNC_INFO << "Received invalid sync call:" << params;
360  return;
361  }
362  QByteArray className = params.takeFirst().toByteArray();
363  QString objectName = params.takeFirst().toString();
364  QByteArray slotName = params.takeFirst().toByteArray();
365  handle(Protocol::SyncMessage(className, objectName, slotName, params));
366  break;
367  }
368  case RpcCall: {
369  if (params.empty()) {
370  qWarning() << Q_FUNC_INFO << "Received empty RPC call!";
371  return;
372  }
373  QByteArray slotName = params.takeFirst().toByteArray();
374  handle(Protocol::RpcCall(slotName, params));
375  break;
376  }
377  case InitRequest: {
378  if (params.count() != 2) {
379  qWarning() << Q_FUNC_INFO << "Received invalid InitRequest:" << params;
380  return;
381  }
382  QByteArray className = params[0].toByteArray();
383  QString objectName = params[1].toString();
384  handle(Protocol::InitRequest(className, objectName));
385  break;
386  }
387  case InitData: {
388  if (params.count() != 3) {
389  qWarning() << Q_FUNC_INFO << "Received invalid InitData:" << params;
390  return;
391  }
392  QByteArray className = params[0].toByteArray();
393  QString objectName = params[1].toString();
394  QVariantMap initData = params[2].toMap();
395 
396  // we need to special-case IrcUsersAndChannels here, since the format changed
397  if (className == "Network")
399  handle(Protocol::InitData(className, objectName, initData));
400  break;
401  }
402  case HeartBeat: {
403  if (params.count() != 1) {
404  qWarning() << Q_FUNC_INFO << "Received invalid HeartBeat:" << params;
405  return;
406  }
407  // The legacy protocol would only send a QTime, no QDateTime
408  // so we assume it's sent today, which works in exactly the same cases as it did in the old implementation
409  QDateTime dateTime = QDateTime::currentDateTime().toUTC();
410  dateTime.setTime(params[0].toTime());
411  handle(Protocol::HeartBeat(dateTime));
412  break;
413  }
414  case HeartBeatReply: {
415  if (params.count() != 1) {
416  qWarning() << Q_FUNC_INFO << "Received invalid HeartBeat:" << params;
417  return;
418  }
419  // The legacy protocol would only send a QTime, no QDateTime
420  // so we assume it's sent today, which works in exactly the same cases as it did in the old implementation
421  QDateTime dateTime = QDateTime::currentDateTime().toUTC();
422  dateTime.setTime(params[0].toTime());
423  handle(Protocol::HeartBeatReply(dateTime));
424  break;
425  }
426 
427  }
428 }
429 
430 
432 {
433  dispatchPackedFunc(QVariantList() << (qint16)Sync << msg.className << msg.objectName << msg.slotName << msg.params);
434 }
435 
436 
438 {
439  dispatchPackedFunc(QVariantList() << (qint16)RpcCall << msg.slotName << msg.params);
440 }
441 
442 
444 {
445  dispatchPackedFunc(QVariantList() << (qint16)InitRequest << msg.className << msg.objectName);
446 }
447 
448 
450 {
451  // We need to special-case IrcUsersAndChannels, as the format changed
452  if (msg.className == "Network") {
453  QVariantMap initData = msg.initData;
454  toLegacyIrcUsersAndChannels(initData);
455  dispatchPackedFunc(QVariantList() << (qint16)InitData << msg.className << msg.objectName << initData);
456  }
457  else
458  dispatchPackedFunc(QVariantList() << (qint16)InitData << msg.className << msg.objectName << msg.initData);
459 }
460 
461 
463 {
464  dispatchPackedFunc(QVariantList() << (qint16)HeartBeat << msg.timestamp.time());
465 }
466 
467 
469 {
470  dispatchPackedFunc(QVariantList() << (qint16)HeartBeatReply << msg.timestamp.time());
471 }
472 
473 
474 void LegacyPeer::dispatchPackedFunc(const QVariantList &packedFunc)
475 {
476  writeMessage(QVariant(packedFunc));
477 }
478 
479 
480 // Handle the changed format for Network's initData
481 // cf. Network::initIrcUsersAndChannels()
482 void LegacyPeer::fromLegacyIrcUsersAndChannels(QVariantMap &initData)
483 {
484  const QVariantMap &legacyMap = initData["IrcUsersAndChannels"].toMap();
485  QVariantMap newMap;
486 
487  QHash<QString, QVariantList> users;
488  foreach(const QVariant &v, legacyMap["users"].toMap().values()) {
489  const QVariantMap &map = v.toMap();
490  foreach(const QString &key, map.keys())
491  users[key] << map[key];
492  }
493  QVariantMap userMap;
494  foreach(const QString &key, users.keys())
495  userMap[key] = users[key];
496  newMap["Users"] = userMap;
497 
498  QHash<QString, QVariantList> channels;
499  foreach(const QVariant &v, legacyMap["channels"].toMap().values()) {
500  const QVariantMap &map = v.toMap();
501  foreach(const QString &key, map.keys())
502  channels[key] << map[key];
503  }
504  QVariantMap channelMap;
505  foreach(const QString &key, channels.keys())
506  channelMap[key] = channels[key];
507  newMap["Channels"] = channelMap;
508 
509  initData["IrcUsersAndChannels"] = newMap;
510 }
511 
512 
513 void LegacyPeer::toLegacyIrcUsersAndChannels(QVariantMap &initData)
514 {
515  const QVariantMap &usersAndChannels = initData["IrcUsersAndChannels"].toMap();
516  QVariantMap legacyMap;
517 
518  // toMap() and toList() are cheap, so no need to copy to a hash
519 
520  QVariantMap userMap;
521  const QVariantMap &users = usersAndChannels["Users"].toMap();
522 
523  int size = users["nick"].toList().size(); // we know this key exists
524  for(int i = 0; i < size; i++) {
525  QVariantMap map;
526  foreach(const QString &key, users.keys())
527  map[key] = users[key].toList().at(i);
528  QString hostmask = QString("%1!%2@%3").arg(map["nick"].toString(), map["user"].toString(), map["host"].toString());
529  userMap[hostmask.toLower()] = map;
530  }
531  legacyMap["users"] = userMap;
532 
533  QVariantMap channelMap;
534  const QVariantMap &channels = usersAndChannels["Channels"].toMap();
535 
536  size = channels["name"].toList().size();
537  for(int i = 0; i < size; i++) {
538  QVariantMap map;
539  foreach(const QString &key, channels.keys())
540  map[key] = channels[key].toList().at(i);
541  channelMap[map["name"].toString().toLower()] = map;
542  }
543  legacyMap["channels"] = channelMap;
544 
545  initData["IrcUsersAndChannels"] = legacyMap;
546 }