Quassel IRC  Pre-Release
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros
clientauthhandler.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 "clientauthhandler.h"
22 
23 // TODO: support system application proxy (new in Qt 4.6)
24 
25 #include <QtEndian>
26 
27 #ifdef HAVE_SSL
28  #include <QSslSocket>
29 #else
30  #include <QTcpSocket>
31 #endif
32 
33 #include "client.h"
34 #include "clientsettings.h"
35 #include "peerfactory.h"
36 
37 using namespace Protocol;
38 
40  : AuthHandler(parent),
41  _peer(0),
42  _account(account),
43  _probing(false),
44  _legacy(false),
45  _connectionFeatures(0)
46 {
47 
48 }
49 
50 
52 {
54 
55 #ifdef HAVE_SSL
56  QSslSocket *socket = new QSslSocket(this);
57  // make sure the warning is shown if we happen to connect without SSL support later
58  s.setAccountValue("ShowNoClientSslWarning", true);
59 #else
60  if (_account.useSsl()) {
61  if (s.accountValue("ShowNoClientSslWarning", true).toBool()) {
62  bool accepted = false;
63  emit handleNoSslInClient(&accepted);
64  if (!accepted) {
65  emit errorMessage(tr("Unencrypted connection canceled"));
66  return;
67  }
68  s.setAccountValue("ShowNoClientSslWarning", false);
69  }
70  }
71  QTcpSocket *socket = new QTcpSocket(this);
72 #endif
73 
74 // TODO: Handle system proxy
75 #ifndef QT_NO_NETWORKPROXY
76  if (_account.useProxy()) {
78  socket->setProxy(proxy);
79  }
80 #endif
81 
82  setSocket(socket);
83  connect(socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), SLOT(onSocketStateChanged(QAbstractSocket::SocketState)));
84  connect(socket, SIGNAL(readyRead()), SLOT(onReadyRead()));
85  connect(socket, SIGNAL(connected()), SLOT(onSocketConnected()));
86 
87  emit statusMessage(tr("Connecting to %1...").arg(_account.accountName()));
88  socket->connectToHost(_account.hostName(), _account.port());
89 }
90 
91 
92 void ClientAuthHandler::onSocketStateChanged(QAbstractSocket::SocketState socketState)
93 {
94  QString text;
95 
96  switch(socketState) {
97  case QAbstractSocket::HostLookupState:
98  if (!_legacy)
99  text = tr("Looking up %1...").arg(_account.hostName());
100  break;
101  case QAbstractSocket::ConnectingState:
102  if (!_legacy)
103  text = tr("Connecting to %1...").arg(_account.hostName());
104  break;
105  case QAbstractSocket::ConnectedState:
106  text = tr("Connected to %1").arg(_account.hostName());
107  break;
108  case QAbstractSocket::ClosingState:
109  if (!_probing)
110  text = tr("Disconnecting from %1...").arg(_account.hostName());
111  break;
112  case QAbstractSocket::UnconnectedState:
113  if (!_probing) {
114  text = tr("Disconnected");
115  // Ensure the disconnected() signal is sent even if we haven't reached the Connected state yet.
116  // The baseclass implementation will make sure to only send the signal once.
117  // However, we do want to prefer a potential socket error signal that may be on route already, so
118  // give this a chance to overtake us by spinning the loop...
119  QTimer::singleShot(0, this, SLOT(onSocketDisconnected()));
120  }
121  break;
122  default:
123  break;
124  }
125 
126  if (!text.isEmpty()) {
127  emit statusMessage(text);
128  }
129 }
130 
131 void ClientAuthHandler::onSocketError(QAbstractSocket::SocketError error)
132 {
133  if (_probing && error == QAbstractSocket::RemoteHostClosedError) {
134  _legacy = true;
135  return;
136  }
137 
138  _probing = false; // all other errors are unrelated to probing and should be handled
140 }
141 
142 
144 {
145  if (_probing && _legacy) {
146  // Remote host has closed the connection while probing
147  _probing = false;
148  disconnect(socket(), SIGNAL(readyRead()), this, SLOT(onReadyRead()));
149  emit statusMessage(tr("Reconnecting in compatibility mode..."));
150  socket()->connectToHost(_account.hostName(), _account.port());
151  return;
152  }
153 
155 }
156 
157 
159 {
160  if (_peer) {
161  qWarning() << Q_FUNC_INFO << "Peer already exists!";
162  return;
163  }
164 
165  socket()->setSocketOption(QAbstractSocket::KeepAliveOption, true);
166 
167  if (!_legacy) {
168  // First connection attempt, try probing for a capable core
169  _probing = true;
170 
171  QDataStream stream(socket()); // stream handles the endianness for us
172  stream.setVersion(QDataStream::Qt_4_2);
173 
174  quint32 magic = Protocol::magic;
175 #ifdef HAVE_SSL
176  if (_account.useSsl())
177  magic |= Protocol::Encryption;
178 #endif
179  magic |= Protocol::Compression;
180 
181  stream << magic;
182 
183  // here goes the list of protocols we support, in order of preference
185  for (int i = 0; i < protos.count(); ++i) {
186  quint32 reply = protos[i].first;
187  reply |= protos[i].second<<8;
188  if (i == protos.count() - 1)
189  reply |= 0x80000000; // end list
190  stream << reply;
191  }
192 
193  socket()->flush(); // make sure the probing data is sent immediately
194  return;
195  }
196 
197  // If we arrive here, it's the second connection attempt, meaning probing was not successful -> enable legacy support
198 
199  qDebug() << "Legacy core detected, switching to compatibility mode";
200 
202  // Only needed for the legacy peer, as all others check the protocol version before instantiation
203  connect(peer, SIGNAL(protocolVersionMismatch(int,int)), SLOT(onProtocolVersionMismatch(int,int)));
204 
205  setPeer(peer);
206 }
207 
208 
210 {
211  if (socket()->bytesAvailable() < 4)
212  return;
213 
214  if (!_probing)
215  return; // make sure to not read more data than needed
216 
217  _probing = false;
218  disconnect(socket(), SIGNAL(readyRead()), this, SLOT(onReadyRead()));
219 
220  quint32 reply;
221  socket()->read((char *)&reply, 4);
222  reply = qFromBigEndian<quint32>(reply);
223 
224  Protocol::Type type = static_cast<Protocol::Type>(reply & 0xff);
225  quint16 protoFeatures = static_cast<quint16>(reply>>8 & 0xffff);
226  _connectionFeatures = static_cast<quint8>(reply>>24);
227 
231  else
233 
234  RemotePeer *peer = PeerFactory::createPeer(PeerFactory::ProtoDescriptor(type, protoFeatures), this, socket(), level, this);
235  if (!peer) {
236  qWarning() << "No valid protocol supported for this core!";
237  emit errorPopup(tr("<b>Incompatible Quassel Core!</b><br>"
238  "None of the protocols this client speaks are supported by the core you are trying to connect to."));
239 
240  requestDisconnect(tr("Core speaks none of the protocols we support"));
241  return;
242  }
243 
244  if (peer->protocol() == Protocol::LegacyProtocol) {
245  connect(peer, SIGNAL(protocolVersionMismatch(int,int)), SLOT(onProtocolVersionMismatch(int,int)));
246  _legacy = true;
247  }
248 
249  setPeer(peer);
250 }
251 
252 
253 void ClientAuthHandler::onProtocolVersionMismatch(int actual, int expected)
254 {
255  emit errorPopup(tr("<b>The Quassel Core you are trying to connect to is too old!</b><br>"
256  "We need at least protocol v%1, but the core speaks v%2 only.").arg(expected, actual));
257  requestDisconnect(tr("Incompatible protocol version, connection to core refused"));
258 }
259 
260 
262 {
263  qDebug().nospace() << "Using " << qPrintable(peer->protocolName()) << "...";
264 
265  _peer = peer;
266  connect(_peer, SIGNAL(transferProgress(int,int)), SIGNAL(transferProgress(int,int)));
267 
268  // The legacy protocol enables SSL later, after registration
269  if (!_account.useSsl() || _legacy)
271  // otherwise, do it now
272  else
274 }
275 
276 
278 {
279  emit statusMessage(tr("Synchronizing to core..."));
280 
281  // useSsl will be ignored by non-legacy peers
282  bool useSsl = false;
283 #ifdef HAVE_SSL
284  useSsl = _account.useSsl();
285 #endif
286 
287  _peer->dispatch(RegisterClient(Quassel::buildInfo().fancyVersionString, Quassel::buildInfo().buildDate, useSsl));
288 }
289 
290 
292 {
293  emit errorPopup(msg.errorString);
294  requestDisconnect(tr("The core refused connection from this client"));
295 }
296 
297 
299 {
302 
303  Client::setCoreFeatures(static_cast<Quassel::Features>(msg.coreFeatures));
304 
305  // The legacy protocol enables SSL at this point
306  if(_legacy && _account.useSsl())
308  else
310 }
311 
312 
314 {
315  emit connectionReady();
316  emit statusMessage(tr("Connected to %1").arg(_account.accountName()));
317 
318  if (!_coreConfigured) {
319  // start wizard
321  }
322  else // TODO: check if we need LoginEnabled
323  login();
324 }
325 
326 
328 {
329  _peer->dispatch(setupData);
330 }
331 
332 
334 {
335  emit coreSetupFailed(msg.errorString);
336 }
337 
338 
340 {
341  Q_UNUSED(msg)
342 
343  emit coreSetupSuccessful();
344 }
345 
346 
347 void ClientAuthHandler::login(const QString &user, const QString &password, bool remember)
348 {
349  _account.setUser(user);
350  _account.setPassword(password);
351  _account.setStorePassword(remember);
352  login();
353 }
354 
355 
356 void ClientAuthHandler::login(const QString &previousError)
357 {
358  emit statusMessage(tr("Logging in..."));
359  if (_account.user().isEmpty() || _account.password().isEmpty() || !previousError.isEmpty()) {
360  bool valid = false;
361  emit userAuthenticationRequired(&_account, &valid, previousError); // *must* be a synchronous call
362  if (!valid || _account.user().isEmpty() || _account.password().isEmpty()) {
363  requestDisconnect(tr("Login canceled"));
364  return;
365  }
366  }
367 
369 }
370 
371 
373 {
374  login(msg.errorString);
375 }
376 
377 
379 {
380  Q_UNUSED(msg)
381 
383 }
384 
385 
387 {
388  disconnect(socket(), 0, this, 0); // this is the last message we shall ever get
389 
390  // give up ownership of the peer; CoreSession takes responsibility now
391  _peer->setParent(0);
392  emit handshakeComplete(_peer, msg);
393 }
394 
395 
396 /*** SSL Stuff ***/
397 
398 void ClientAuthHandler::checkAndEnableSsl(bool coreSupportsSsl)
399 {
400 #ifndef HAVE_SSL
401  Q_UNUSED(coreSupportsSsl);
402 #else
404  if (coreSupportsSsl && _account.useSsl()) {
405  // Make sure the warning is shown next time we don't have SSL in the core
406  s.setAccountValue("ShowNoCoreSslWarning", true);
407 
408  QSslSocket *sslSocket = qobject_cast<QSslSocket *>(socket());
409  Q_ASSERT(sslSocket);
410  connect(sslSocket, SIGNAL(encrypted()), SLOT(onSslSocketEncrypted()));
411  connect(sslSocket, SIGNAL(sslErrors(QList<QSslError>)), SLOT(onSslErrors()));
412  qDebug() << "Starting encryption...";
413  sslSocket->flush();
414  sslSocket->startClientEncryption();
415  }
416  else {
417  if (s.accountValue("ShowNoCoreSslWarning", true).toBool()) {
418  bool accepted = false;
419  emit handleNoSslInCore(&accepted);
420  if (!accepted) {
421  requestDisconnect(tr("Unencrypted connection cancelled"));
422  return;
423  }
424  s.setAccountValue("ShowNoCoreSslWarning", false);
425  s.setAccountValue("SslCert", QString());
426  }
427  if (_legacy)
429  else
431  }
432 #endif
433 }
434 
435 #ifdef HAVE_SSL
436 
437 void ClientAuthHandler::onSslSocketEncrypted()
438 {
439  QSslSocket *socket = qobject_cast<QSslSocket *>(sender());
440  Q_ASSERT(socket);
441 
442  if (!socket->sslErrors().count()) {
443  // Cert is valid, so we don't want to store it as known
444  // That way, a warning will appear in case it becomes invalid at some point
446  s.setAccountValue("SSLCert", QString());
447  }
448 
449  emit encrypted(true);
450 
451  if (_legacy)
453  else
455 }
456 
457 
458 void ClientAuthHandler::onSslErrors()
459 {
460  QSslSocket *socket = qobject_cast<QSslSocket *>(sender());
461  Q_ASSERT(socket);
462 
464  QByteArray knownDigest = s.accountValue("SslCert").toByteArray();
465 
466  if (knownDigest != socket->peerCertificate().digest()) {
467  bool accepted = false;
468  bool permanently = false;
469  emit handleSslErrors(socket, &accepted, &permanently);
470 
471  if (!accepted) {
472  requestDisconnect(tr("Unencrypted connection canceled"));
473  return;
474  }
475 
476  if (permanently)
477  s.setAccountValue("SslCert", socket->peerCertificate().digest());
478  else
479  s.setAccountValue("SslCert", QString());
480  }
481 
482  socket->ignoreSslErrors();
483 }
484 
485 #endif /* HAVE_SSL */