Fixes #510 - Network item shows wrong buffer
[quassel.git] / src / client / clientsyncer.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-09 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  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
19  ***************************************************************************/
20
21 #include "clientsyncer.h"
22
23 #ifndef QT_NO_NETWORKPROXY
24 #  include <QNetworkProxy>
25 #endif
26
27 #include "client.h"
28 #include "identity.h"
29 #include "network.h"
30 #include "networkmodel.h"
31 #include "quassel.h"
32 #include "signalproxy.h"
33 #include "util.h"
34
35 ClientSyncer::ClientSyncer(QObject *parent)
36   : QObject(parent),
37     _socket(0),
38     _blockSize(0)
39 {
40 }
41
42 ClientSyncer::~ClientSyncer() {
43 }
44
45 void ClientSyncer::coreHasData() {
46   QVariant item;
47   while(SignalProxy::readDataFromDevice(_socket, _blockSize, item)) {
48     emit recvPartialItem(1,1);
49     QVariantMap msg = item.toMap();
50     if(!msg.contains("MsgType")) {
51       // This core is way too old and does not even speak our init protocol...
52       emit connectionError(tr("The Quassel Core you try to connect to is too old! Please consider upgrading."));
53       disconnectFromCore();
54       return;
55     }
56     if(msg["MsgType"] == "ClientInitAck") {
57       clientInitAck(msg);
58     } else if(msg["MsgType"] == "ClientInitReject") {
59       emit connectionError(msg["Error"].toString());
60       disconnectFromCore();
61       return;
62     } else if(msg["MsgType"] == "CoreSetupAck") {
63       emit coreSetupSuccess();
64     } else if(msg["MsgType"] == "CoreSetupReject") {
65       emit coreSetupFailed(msg["Error"].toString());
66     } else if(msg["MsgType"] == "ClientLoginReject") {
67       emit loginFailed(msg["Error"].toString());
68     } else if(msg["MsgType"] == "ClientLoginAck") {
69       // prevent multiple signal connections
70       disconnect(this, SIGNAL(recvPartialItem(quint32, quint32)), this, SIGNAL(sessionProgress(quint32, quint32)));
71       connect(this, SIGNAL(recvPartialItem(quint32, quint32)), this, SIGNAL(sessionProgress(quint32, quint32)));
72       emit loginSuccess();
73     } else if(msg["MsgType"] == "SessionInit") {
74       sessionStateReceived(msg["SessionState"].toMap());
75       break; // this is definitively the last message we process here!
76     } else {
77       emit connectionError(tr("<b>Invalid data received from core!</b><br>Disconnecting."));
78       disconnectFromCore();
79       return;
80     }
81   }
82   if(_blockSize > 0) {
83     emit recvPartialItem(_socket->bytesAvailable(), _blockSize);
84   }
85 }
86
87 void ClientSyncer::coreSocketError(QAbstractSocket::SocketError) {
88   qDebug() << "coreSocketError" << _socket << _socket->errorString();
89   emit connectionError(_socket->errorString());
90   resetConnection();
91 }
92
93 void ClientSyncer::disconnectFromCore() {
94   if(_socket)
95     _socket->close();
96   resetConnection();
97 }
98
99 void ClientSyncer::connectToCore(const QVariantMap &conn) {
100   resetConnection();
101   coreConnectionInfo = conn;
102
103   if(conn["Host"].toString().isEmpty()) {
104     emit connectionError(tr("No Host to connect to specified."));
105     return;
106   }
107
108   Q_ASSERT(!_socket);
109 #ifdef HAVE_SSL
110   QSslSocket *sock = new QSslSocket(Client::instance());
111 #else
112   if(conn["useSsl"].toBool()) {
113     emit connectionError(tr("<b>This client is built without SSL Support!</b><br />Disable the usage of SSL in the account settings."));
114     return;
115   }
116   QTcpSocket *sock = new QTcpSocket(Client::instance());
117 #endif
118
119 #ifndef QT_NO_NETWORKPROXY
120   if(conn.contains("useProxy") && conn["useProxy"].toBool()) {
121     QNetworkProxy proxy((QNetworkProxy::ProxyType)conn["proxyType"].toInt(), conn["proxyHost"].toString(), conn["proxyPort"].toUInt(), conn["proxyUser"].toString(), conn["proxyPassword"].toString());
122     sock->setProxy(proxy);
123   }
124 #endif
125
126   _socket = sock;
127   connect(sock, SIGNAL(readyRead()), this, SLOT(coreHasData()));
128   connect(sock, SIGNAL(connected()), this, SLOT(coreSocketConnected()));
129   connect(sock, SIGNAL(disconnected()), this, SLOT(coreSocketDisconnected()));
130   connect(sock, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(coreSocketError(QAbstractSocket::SocketError)));
131   connect(sock, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SIGNAL(socketStateChanged(QAbstractSocket::SocketState)));
132   sock->connectToHost(conn["Host"].toString(), conn["Port"].toUInt());
133 }
134
135 void ClientSyncer::coreSocketConnected() {
136   //connect(this, SIGNAL(recvPartialItem(uint, uint)), this, SIGNAL(coreConnectionProgress(uint, uint)));
137   // Phase One: Send client info and wait for core info
138
139   //emit coreConnectionMsg(tr("Synchronizing to core..."));
140   QVariantMap clientInit;
141   clientInit["MsgType"] = "ClientInit";
142   clientInit["ClientVersion"] = Quassel::buildInfo().fancyVersionString;
143   clientInit["ClientDate"] = Quassel::buildInfo().buildDate;
144   clientInit["ProtocolVersion"] = Quassel::buildInfo().protocolVersion;
145   clientInit["UseSsl"] = coreConnectionInfo["useSsl"];
146 #ifndef QT_NO_COMPRESS
147   clientInit["UseCompression"] = true;
148 #else
149   clientInit["UseCompression"] = false;
150 #endif
151
152   SignalProxy::writeDataToDevice(_socket, clientInit);
153 }
154
155 void ClientSyncer::useInternalCore() {
156   AccountId internalAccountId;
157
158   CoreAccountSettings accountSettings;
159   QList<AccountId> knownAccounts = accountSettings.knownAccounts();
160   foreach(AccountId id, knownAccounts) {
161     if(!id.isValid())
162       continue;
163     QVariantMap data = accountSettings.retrieveAccountData(id);
164     if(data.contains("InternalAccount") && data["InternalAccount"].toBool()) {
165       internalAccountId = id;
166       break;
167     }
168   }
169
170   if(!internalAccountId.isValid()) {
171     for(AccountId i = 1;; i++) {
172       if(!knownAccounts.contains(i)) {
173         internalAccountId = i;
174         break;
175       }
176     }
177     QVariantMap data;
178     data["InternalAccount"] = true;
179     accountSettings.storeAccountData(internalAccountId, data);
180   }
181
182   coreConnectionInfo["AccountId"] = QVariant::fromValue<AccountId>(internalAccountId);
183   emit startInternalCore(this);
184   emit connectToInternalCore(Client::instance()->signalProxy());
185 }
186
187 void ClientSyncer::coreSocketDisconnected() {
188   emit socketDisconnected();
189   resetConnection();
190   // FIXME handle disconnects gracefully
191 }
192
193 void ClientSyncer::clientInitAck(const QVariantMap &msg) {
194   // Core has accepted our version info and sent its own. Let's see if we accept it as well...
195   uint ver = msg["ProtocolVersion"].toUInt();
196   if(ver < Quassel::buildInfo().clientNeedsProtocol) {
197     emit connectionError(tr("<b>The Quassel Core you are trying to connect to is too old!</b><br>"
198         "Need at least core/client protocol v%1 to connect.").arg(Quassel::buildInfo().clientNeedsProtocol));
199     disconnectFromCore();
200     return;
201   }
202   emit connectionMsg(msg["CoreInfo"].toString());
203
204 #ifndef QT_NO_COMPRESS
205   if(msg["SupportsCompression"].toBool()) {
206     _socket->setProperty("UseCompression", true);
207   }
208 #endif
209
210   _coreMsgBuffer = msg;
211 #ifdef HAVE_SSL
212   if(coreConnectionInfo["useSsl"].toBool()) {
213     if(msg["SupportSsl"].toBool()) {
214       QSslSocket *sslSocket = qobject_cast<QSslSocket *>(_socket);
215       Q_ASSERT(sslSocket);
216       connect(sslSocket, SIGNAL(encrypted()), this, SLOT(sslSocketEncrypted()));
217       connect(sslSocket, SIGNAL(sslErrors(const QList<QSslError> &)), this, SLOT(sslErrors(const QList<QSslError> &)));
218
219       sslSocket->startClientEncryption();
220     } else {
221       emit connectionError(tr("<b>The Quassel Core you are trying to connect to does not support SSL!</b><br />If you want to connect anyways, disable the usage of SSL in the account settings."));
222       disconnectFromCore();
223     }
224     return;
225   }
226 #endif
227   // if we use SSL we wait for the next step until every SSL warning has been cleared
228   connectionReady();
229 }
230
231 void ClientSyncer::connectionReady() {
232   if(!_coreMsgBuffer["Configured"].toBool()) {
233     // start wizard
234     emit startCoreSetup(_coreMsgBuffer["StorageBackends"].toList());
235   } else if(_coreMsgBuffer["LoginEnabled"].toBool()) {
236     emit startLogin();
237   }
238   _coreMsgBuffer.clear();
239   resetWarningsHandler();
240 }
241
242 void ClientSyncer::doCoreSetup(const QVariant &setupData) {
243   QVariantMap setup;
244   setup["MsgType"] = "CoreSetupData";
245   setup["SetupData"] = setupData;
246   SignalProxy::writeDataToDevice(_socket, setup);
247 }
248
249 void ClientSyncer::loginToCore(const QString &user, const QString &passwd) {
250   emit connectionMsg(tr("Logging in..."));
251   QVariantMap clientLogin;
252   clientLogin["MsgType"] = "ClientLogin";
253   clientLogin["User"] = user;
254   clientLogin["Password"] = passwd;
255   SignalProxy::writeDataToDevice(_socket, clientLogin);
256 }
257
258 void ClientSyncer::internalSessionStateReceived(const QVariant &packedState) {
259   QVariantMap state = packedState.toMap();
260   emit sessionProgress(1, 1);
261   Client::instance()->setConnectedToCore(coreConnectionInfo["AccountId"].value<AccountId>());
262   syncToCore(state);
263 }
264
265 void ClientSyncer::sessionStateReceived(const QVariantMap &state) {
266   emit sessionProgress(1, 1);
267   disconnect(this, SIGNAL(recvPartialItem(quint32, quint32)), this, SIGNAL(sessionProgress(quint32, quint32)));
268
269   // rest of communication happens through SignalProxy...
270   disconnect(_socket, 0, this, 0);
271   // ... but we still want to be notified about errors...
272   connect(_socket, SIGNAL(disconnected()), this, SLOT(coreSocketDisconnected()));
273   connect(_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(coreSocketError(QAbstractSocket::SocketError)));
274
275   Client::instance()->setConnectedToCore(coreConnectionInfo["AccountId"].value<AccountId>(), _socket);
276   syncToCore(state);
277 }
278
279 void ClientSyncer::syncToCore(const QVariantMap &sessionState) {
280   // create identities
281   foreach(QVariant vid, sessionState["Identities"].toList()) {
282     Client::instance()->coreIdentityCreated(vid.value<Identity>());
283   }
284
285   // create buffers
286   // FIXME: get rid of this crap
287   QVariantList bufferinfos = sessionState["BufferInfos"].toList();
288   NetworkModel *networkModel = Client::networkModel();
289   Q_ASSERT(networkModel);
290   foreach(QVariant vinfo, bufferinfos)
291     networkModel->bufferUpdated(vinfo.value<BufferInfo>());  // create BufferItems
292
293   QVariantList networkids = sessionState["NetworkIds"].toList();
294
295   // prepare sync progress thingys...
296   // FIXME: Care about removal of networks
297   numNetsToSync = networkids.count();
298   emit networksProgress(0, numNetsToSync);
299
300   // create network objects
301   foreach(QVariant networkid, networkids) {
302     NetworkId netid = networkid.value<NetworkId>();
303     if(Client::network(netid))
304       continue;
305     Network *net = new Network(netid, Client::instance());
306     netsToSync.insert(net);
307     connect(net, SIGNAL(initDone()), this, SLOT(networkInitDone()));
308     Client::addNetwork(net);
309   }
310   checkSyncState();
311 }
312
313 void ClientSyncer::networkInitDone() {
314   netsToSync.remove(sender());
315   emit networksProgress(numNetsToSync - netsToSync.count(), numNetsToSync);
316   checkSyncState();
317 }
318
319 void ClientSyncer::checkSyncState() {
320   if(netsToSync.isEmpty()) {
321     Client::instance()->setSyncedToCore();
322     emit syncFinished();
323   }
324 }
325
326 void ClientSyncer::setWarningsHandler(const char *slot) {
327   resetWarningsHandler();
328   connect(this, SIGNAL(handleIgnoreWarnings(bool)), this, slot);
329 }
330
331 void ClientSyncer::resetWarningsHandler() {
332   disconnect(this, SIGNAL(handleIgnoreWarnings(bool)), this, 0);
333 }
334
335 void ClientSyncer::resetConnection() {
336   if(_socket) {
337     disconnect(_socket, 0, this, 0);
338     _socket->deleteLater();
339     _socket = 0;
340   }
341   _blockSize = 0;
342
343   coreConnectionInfo.clear();
344   _coreMsgBuffer.clear();
345
346   netsToSync.clear();
347   numNetsToSync = 0;
348 }
349
350 #ifdef HAVE_SSL
351 void ClientSyncer::ignoreSslWarnings(bool permanently) {
352   QSslSocket *sock = qobject_cast<QSslSocket *>(_socket);
353   if(sock) {
354     // ensure that a proper state is displayed and no longer a warning
355     emit socketStateChanged(sock->state());
356   }
357   if(permanently) {
358     if(!sock)
359       qWarning() << Q_FUNC_INFO << "unable to save cert digest! Socket is either a nullptr or not a QSslSocket";
360     else
361       KnownHostsSettings().saveKnownHost(sock);
362   }
363   emit connectionMsg(_coreMsgBuffer["CoreInfo"].toString());
364   connectionReady();
365 }
366
367 void ClientSyncer::sslSocketEncrypted() {
368   QSslSocket *socket = qobject_cast<QSslSocket *>(sender());
369   Q_ASSERT(socket);
370
371   // if there were sslErrors we already had extensive error handling
372   // no need to check for a digest change again.
373   if(!socket->sslErrors().isEmpty())
374     return;
375
376   QByteArray knownDigest = KnownHostsSettings().knownDigest(socket);
377   if(knownDigest == socket->peerCertificate().digest()) {
378     connectionReady();
379     return;
380   }
381
382   QStringList warnings;
383   if(!knownDigest.isEmpty()) {
384     warnings << tr("Cert Digest changed! was: %1").arg(QString(prettyDigest(knownDigest)));
385   }
386
387   setWarningsHandler(SLOT(ignoreSslWarnings(bool)));
388   emit connectionWarnings(warnings);
389 }
390
391 void ClientSyncer::sslErrors(const QList<QSslError> &errors) {
392   QSslSocket *socket = qobject_cast<QSslSocket *>(sender());
393   Q_ASSERT(socket);
394
395   socket->ignoreSslErrors();
396
397   QByteArray knownDigest = KnownHostsSettings().knownDigest(socket);
398   if(knownDigest == socket->peerCertificate().digest()) {
399     connectionReady();
400     return;
401   }
402
403   QStringList warnings;
404
405   foreach(QSslError err, errors)
406     warnings << err.errorString();
407
408   if(!knownDigest.isEmpty()) {
409     warnings << tr("Cert Digest changed! was: %1").arg(QString(prettyDigest(knownDigest)));
410   }
411
412   setWarningsHandler(SLOT(ignoreSslWarnings(bool)));
413   emit connectionWarnings(warnings);
414 }
415 #endif