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