1 /***************************************************************************
2 * Copyright (C) 2009 by the Quassel Project *
3 * devel@quassel-irc.org *
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. *
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. *
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 ***************************************************************************/
21 #include "coreconnection.h"
23 #ifndef QT_NO_NETWORKPROXY
24 # include <QNetworkProxy>
28 #include "clientsettings.h"
29 #include "coreaccountmodel.h"
32 #include "networkmodel.h"
34 #include "signalproxy.h"
37 CoreConnection::CoreConnection(CoreAccountModel *model, QObject *parent)
46 qRegisterMetaType<ConnectionState>("CoreConnection::ConnectionState");
50 void CoreConnection::init() {
51 connect(Client::signalProxy(), SIGNAL(disconnected()), SLOT(coreSocketDisconnected()));
54 void CoreConnection::setProgressText(const QString &text) {
55 if(_progressText != text) {
57 emit progressTextChanged(text);
61 void CoreConnection::setProgressValue(int value) {
62 if(_progressValue != value) {
63 _progressValue = value;
64 emit progressValueChanged(value);
68 void CoreConnection::setProgressMinimum(int minimum) {
69 if(_progressMinimum != minimum) {
70 _progressMinimum = minimum;
71 emit progressRangeChanged(minimum, _progressMaximum);
75 void CoreConnection::setProgressMaximum(int maximum) {
76 if(_progressMaximum != maximum) {
77 _progressMaximum = maximum;
78 emit progressRangeChanged(_progressMinimum, maximum);
82 void CoreConnection::updateProgress(int value, int max) {
83 if(max != _progressMaximum) {
84 _progressMaximum = max;
85 emit progressRangeChanged(_progressMinimum, _progressMaximum);
87 setProgressValue(value);
90 void CoreConnection::resetConnection() {
92 disconnect(_socket, 0, this, 0);
93 _socket->deleteLater();
98 _coreMsgBuffer.clear();
103 setProgressMaximum(-1); // disable
104 setState(Disconnected);
105 emit connectionMsg(tr("Disconnected from core."));
106 emit encrypted(false);
109 bool CoreConnection::isEncrypted() const {
113 QSslSocket *sock = qobject_cast<QSslSocket *>(_socket);
114 return isConnected() && sock && sock->isEncrypted();
118 void CoreConnection::socketStateChanged(QAbstractSocket::SocketState socketState) {
121 switch(socketState) {
122 case QAbstractSocket::UnconnectedState:
123 text = tr("Disconnected");
125 case QAbstractSocket::HostLookupState:
126 text = tr("Looking up %1...").arg(currentAccount().hostName());
128 case QAbstractSocket::ConnectingState:
129 text = tr("Connecting to %1...").arg(currentAccount().hostName());
131 case QAbstractSocket::ConnectedState:
132 text = tr("Connected to %1").arg(currentAccount().hostName());
134 case QAbstractSocket::ClosingState:
135 text = tr("Disconnecting from %1...").arg(currentAccount().hostName());
142 emit progressTextChanged(text);
144 setState(socketState);
147 void CoreConnection::setState(QAbstractSocket::SocketState socketState) {
148 ConnectionState state;
150 switch(socketState) {
151 case QAbstractSocket::UnconnectedState:
152 state = Disconnected;
154 case QAbstractSocket::HostLookupState:
155 case QAbstractSocket::ConnectingState:
159 state = Disconnected;
165 void CoreConnection::setState(ConnectionState state) {
166 if(state != _state) {
168 emit stateChanged(state);
169 if(state == Disconnected)
174 void CoreConnection::coreSocketError(QAbstractSocket::SocketError) {
175 qDebug() << "coreSocketError" << _socket << _socket->errorString();
176 emit connectionError(_socket->errorString());
180 void CoreConnection::coreSocketDisconnected() {
183 // FIXME handle disconnects gracefully
186 void CoreConnection::coreHasData() {
188 while(SignalProxy::readDataFromDevice(_socket, _blockSize, item)) {
189 QVariantMap msg = item.toMap();
190 if(!msg.contains("MsgType")) {
191 // This core is way too old and does not even speak our init protocol...
192 emit connectionErrorPopup(tr("The Quassel Core you try to connect to is too old! Please consider upgrading."));
193 disconnectFromCore();
196 if(msg["MsgType"] == "ClientInitAck") {
198 } else if(msg["MsgType"] == "ClientInitReject") {
199 emit connectionErrorPopup(msg["Error"].toString());
200 disconnectFromCore();
202 } else if(msg["MsgType"] == "CoreSetupAck") {
203 emit coreSetupSuccess();
204 } else if(msg["MsgType"] == "CoreSetupReject") {
205 emit coreSetupFailed(msg["Error"].toString());
206 } else if(msg["MsgType"] == "ClientLoginReject") {
207 loginFailed(msg["Error"].toString());
208 } else if(msg["MsgType"] == "ClientLoginAck") {
210 } else if(msg["MsgType"] == "SessionInit") {
211 // that's it, let's hand over to the signal proxy
212 // if the socket is an orphan, the signalProxy adopts it.
213 // -> we don't need to care about it anymore
214 _socket->setParent(0);
215 Client::signalProxy()->addPeer(_socket);
217 sessionStateReceived(msg["SessionState"].toMap());
218 break; // this is definitively the last message we process here!
220 disconnectFromCore(tr("Invalid data received from core"));
225 updateProgress(_socket->bytesAvailable(), _blockSize);
229 void CoreConnection::disconnectFromCore(const QString &errorString) {
230 if(errorString.isEmpty())
231 emit connectionError(tr("Disconnected"));
233 emit connectionError(errorString);
235 Client::signalProxy()->removeAllPeers();
239 void CoreConnection::reconnectToCore() {
240 if(currentAccount().isValid())
241 connectToCore(currentAccount().accountId());
244 bool CoreConnection::connectToCore(AccountId accId) {
248 CoreAccountSettings s;
250 // FIXME: Don't force connection to internal core in mono client
251 if(Quassel::runMode() == Quassel::Monolithic) {
252 _account = accountModel()->account(accountModel()->internalAccount());
253 Q_ASSERT(_account.isValid());
255 if(!accId.isValid()) {
256 // check our settings and figure out what to do
257 if(!s.autoConnectOnStartup())
259 if(s.autoConnectToFixedAccount())
260 accId = s.autoConnectAccount();
262 accId = s.lastAccount();
266 _account = accountModel()->account(accId);
267 if(!_account.accountId().isValid()) {
270 if(Quassel::runMode() != Quassel::Monolithic) {
271 if(_account.isInternal())
276 s.setLastAccount(accId);
277 connectToCurrentAccount();
281 void CoreConnection::connectToCurrentAccount() {
284 if(currentAccount().isInternal()) {
285 if(Quassel::runMode() != Quassel::Monolithic) {
286 qWarning() << "Cannot connect to internal core in client-only mode!";
289 emit startInternalCore();
290 emit connectToInternalCore(Client::instance()->signalProxy());
294 CoreAccountSettings s;
298 QSslSocket *sock = new QSslSocket(Client::instance());
299 // make sure the warning is shown if we happen to connect without SSL support later
300 s.setAccountValue("ShowNoClientSslWarning", true);
302 if(_account.useSsl()) {
303 if(s.accountValue("ShowNoClientSslWarning", true).toBool()) {
304 bool accepted = false;
305 emit handleNoSslInClient(&accepted);
307 emit connectionError(tr("Unencrypted connection canceled"));
310 s.setAccountValue("ShowNoClientSslWarning", false);
313 QTcpSocket *sock = new QTcpSocket(Client::instance());
316 #ifndef QT_NO_NETWORKPROXY
317 if(_account.useProxy()) {
318 QNetworkProxy proxy(_account.proxyType(), _account.proxyHostName(), _account.proxyPort(), _account.proxyUser(), _account.proxyPassword());
319 sock->setProxy(proxy);
324 connect(sock, SIGNAL(readyRead()), SLOT(coreHasData()));
325 connect(sock, SIGNAL(connected()), SLOT(coreSocketConnected()));
326 connect(sock, SIGNAL(disconnected()), SLOT(coreSocketDisconnected()));
327 connect(sock, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(coreSocketError(QAbstractSocket::SocketError)));
328 connect(sock, SIGNAL(stateChanged(QAbstractSocket::SocketState)), SLOT(socketStateChanged(QAbstractSocket::SocketState)));
330 emit connectionMsg(tr("Connecting to %1...").arg(currentAccount().accountName()));
331 sock->connectToHost(_account.hostName(), _account.port());
334 void CoreConnection::coreSocketConnected() {
335 // Phase One: Send client info and wait for core info
337 emit connectionMsg(tr("Synchronizing to core..."));
339 QVariantMap clientInit;
340 clientInit["MsgType"] = "ClientInit";
341 clientInit["ClientVersion"] = Quassel::buildInfo().fancyVersionString;
342 clientInit["ClientDate"] = Quassel::buildInfo().buildDate;
343 clientInit["ProtocolVersion"] = Quassel::buildInfo().protocolVersion;
344 clientInit["UseSsl"] = _account.useSsl();
345 #ifndef QT_NO_COMPRESS
346 clientInit["UseCompression"] = true;
348 clientInit["UseCompression"] = false;
351 SignalProxy::writeDataToDevice(_socket, clientInit);
354 void CoreConnection::clientInitAck(const QVariantMap &msg) {
355 // Core has accepted our version info and sent its own. Let's see if we accept it as well...
356 uint ver = msg["ProtocolVersion"].toUInt();
357 if(ver < Quassel::buildInfo().clientNeedsProtocol) {
358 emit connectionErrorPopup(tr("<b>The Quassel Core you are trying to connect to is too old!</b><br>"
359 "Need at least core/client protocol v%1 to connect.").arg(Quassel::buildInfo().clientNeedsProtocol));
360 disconnectFromCore();
364 #ifndef QT_NO_COMPRESS
365 if(msg["SupportsCompression"].toBool()) {
366 _socket->setProperty("UseCompression", true);
370 _coreMsgBuffer = msg;
373 CoreAccountSettings s;
374 if(currentAccount().useSsl()) {
375 if(msg["SupportSsl"].toBool()) {
376 // Make sure the warning is shown next time we don't have SSL in the core
377 s.setAccountValue("ShowNoCoreSslWarning", true);
379 QSslSocket *sslSocket = qobject_cast<QSslSocket *>(_socket);
381 connect(sslSocket, SIGNAL(encrypted()), SLOT(sslSocketEncrypted()));
382 connect(sslSocket, SIGNAL(sslErrors(const QList<QSslError> &)), SLOT(sslErrors()));
383 sslSocket->startClientEncryption();
385 if(s.accountValue("ShowNoCoreSslWarning", true).toBool()) {
386 bool accepted = false;
387 emit handleNoSslInCore(&accepted);
389 disconnectFromCore(tr("Unencrypted connection canceled"));
392 s.setAccountValue("ShowNoCoreSslWarning", false);
393 s.setAccountValue("SslCert", QString());
400 // if we use SSL we wait for the next step until every SSL warning has been cleared
406 void CoreConnection::sslSocketEncrypted() {
407 QSslSocket *socket = qobject_cast<QSslSocket *>(sender());
410 if(!socket->sslErrors().count()) {
411 // Cert is valid, so we don't want to store it as known
412 // That way, a warning will appear in case it becomes invalid at some point
413 CoreAccountSettings s;
414 s.setAccountValue("SSLCert", QString());
417 emit encrypted(true);
421 void CoreConnection::sslErrors() {
422 QSslSocket *socket = qobject_cast<QSslSocket *>(sender());
425 CoreAccountSettings s;
426 QByteArray knownDigest = s.accountValue("SslCert").toByteArray();
428 if(knownDigest != socket->peerCertificate().digest()) {
429 bool accepted = false;
430 bool permanently = false;
431 emit handleSslErrors(socket, &accepted, &permanently);
434 disconnectFromCore(tr("Unencrypted connection canceled"));
439 s.setAccountValue("SslCert", socket->peerCertificate().digest());
441 s.setAccountValue("SslCert", QString());
444 socket->ignoreSslErrors();
447 #endif /* HAVE_SSL */
449 void CoreConnection::connectionReady() {
451 emit connectionMsg(tr("Connected to %1").arg(currentAccount().accountName()));
453 if(!_coreMsgBuffer["Configured"].toBool()) {
455 emit startCoreSetup(_coreMsgBuffer["StorageBackends"].toList());
456 } else if(_coreMsgBuffer["LoginEnabled"].toBool()) {
459 _coreMsgBuffer.clear();
462 void CoreConnection::loginToCore(const QString &user, const QString &password, bool remember) {
463 _account.setUser(user);
464 _account.setPassword(password);
465 _account.setStorePassword(remember);
469 void CoreConnection::loginToCore(const QString &prevError) {
470 emit connectionMsg(tr("Logging in..."));
471 if(currentAccount().user().isEmpty() || currentAccount().password().isEmpty() || !prevError.isEmpty()) {
473 emit userAuthenticationRequired(&_account, &valid, prevError); // *must* be a synchronous call
474 if(!valid || currentAccount().user().isEmpty() || currentAccount().password().isEmpty()) {
475 disconnectFromCore(tr("Login canceled"));
480 QVariantMap clientLogin;
481 clientLogin["MsgType"] = "ClientLogin";
482 clientLogin["User"] = currentAccount().user();
483 clientLogin["Password"] = currentAccount().password();
484 SignalProxy::writeDataToDevice(_socket, clientLogin);
487 void CoreConnection::loginFailed(const QString &error) {
491 void CoreConnection::loginSuccess() {
492 updateProgress(0, 0);
494 // save current account data
495 _model->createOrUpdateAccount(currentAccount());
498 setProgressText(tr("Receiving session state"));
499 setState(Synchronizing);
500 emit connectionMsg(tr("Synchronizing to %1...").arg(currentAccount().accountName()));
503 void CoreConnection::sessionStateReceived(const QVariantMap &state) {
504 updateProgress(100, 100);
506 // rest of communication happens through SignalProxy...
507 disconnect(_socket, SIGNAL(readyRead()), this, 0);
508 disconnect(_socket, SIGNAL(connected()), this, 0);
513 void CoreConnection::internalSessionStateReceived(const QVariant &packedState) {
514 updateProgress(100, 100);
516 setState(Synchronizing);
517 syncToCore(packedState.toMap());
520 void CoreConnection::syncToCore(const QVariantMap &sessionState) {
521 setProgressText(tr("Receiving network states"));
522 updateProgress(0, 100);
525 foreach(QVariant vid, sessionState["Identities"].toList()) {
526 Client::instance()->coreIdentityCreated(vid.value<Identity>());
530 // FIXME: get rid of this crap -- why?
531 QVariantList bufferinfos = sessionState["BufferInfos"].toList();
532 NetworkModel *networkModel = Client::networkModel();
533 Q_ASSERT(networkModel);
534 foreach(QVariant vinfo, bufferinfos)
535 networkModel->bufferUpdated(vinfo.value<BufferInfo>()); // create BufferItems
537 QVariantList networkids = sessionState["NetworkIds"].toList();
539 // prepare sync progress thingys...
540 // FIXME: Care about removal of networks
541 _numNetsToSync = networkids.count();
542 updateProgress(0, _numNetsToSync);
544 // create network objects
545 foreach(QVariant networkid, networkids) {
546 NetworkId netid = networkid.value<NetworkId>();
547 if(Client::network(netid))
549 Network *net = new Network(netid, Client::instance());
550 _netsToSync.insert(net);
551 connect(net, SIGNAL(initDone()), SLOT(networkInitDone()));
552 connect(net, SIGNAL(destroyed()), SLOT(networkInitDone()));
553 Client::addNetwork(net);
558 void CoreConnection::networkInitDone() {
559 Network *net = qobject_cast<Network *>(sender());
561 disconnect(net, 0, this, 0);
562 _netsToSync.remove(net);
563 updateProgress(_numNetsToSync - _netsToSync.count(), _numNetsToSync);
567 void CoreConnection::checkSyncState() {
568 if(_netsToSync.isEmpty()) {
569 setState(Synchronized);
570 setProgressText(tr("Synchronized to %1").arg(currentAccount().accountName()));
571 setProgressMaximum(-1);
576 void CoreConnection::doCoreSetup(const QVariant &setupData) {
578 setup["MsgType"] = "CoreSetupData";
579 setup["SetupData"] = setupData;
580 SignalProxy::writeDataToDevice(_socket, setup);