Display lag and SSL status in CoreConnectionStatusWidget
[quassel.git] / src / client / coreconnection.cpp
index 863f1bd..a982f62 100644 (file)
@@ -38,6 +38,7 @@ CoreConnection::CoreConnection(CoreAccountModel *model, QObject *parent)
   : QObject(parent),
   _model(model),
   _blockSize(0),
+  _state(Disconnected),
   _progressMinimum(0),
   _progressMaximum(-1),
   _progressValue(-1)
@@ -100,7 +101,18 @@ void CoreConnection::resetConnection() {
   _numNetsToSync = 0;
 
   setProgressMaximum(-1); // disable
+  setState(Disconnected);
   emit connectionMsg(tr("Disconnected from core."));
+  emit encrypted(false);
+}
+
+bool CoreConnection::isEncrypted() const {
+#ifndef HAVE_SSL
+  return false;
+#else
+  QSslSocket *sock = qobject_cast<QSslSocket *>(_socket);
+  return isConnected() && sock && sock->isEncrypted();
+#endif
 }
 
 void CoreConnection::socketStateChanged(QAbstractSocket::SocketState socketState) {
@@ -143,9 +155,6 @@ void CoreConnection::setState(QAbstractSocket::SocketState socketState) {
   case QAbstractSocket::ConnectingState:
     state = Connecting;
     break;
-  case QAbstractSocket::ConnectedState:
-    state = Connected;
-    break;
   default:
     state = Disconnected;
   }
@@ -157,18 +166,11 @@ void CoreConnection::setState(ConnectionState state) {
   if(state != _state) {
     _state = state;
     emit stateChanged(state);
+    if(state == Disconnected)
+      emit disconnected();
   }
 }
 
-void CoreConnection::setWarningsHandler(const char *slot) {
-  resetWarningsHandler();
-  connect(this, SIGNAL(handleIgnoreWarnings(bool)), this, slot);
-}
-
-void CoreConnection::resetWarningsHandler() {
-  disconnect(this, SIGNAL(handleIgnoreWarnings(bool)), this, 0);
-}
-
 void CoreConnection::coreSocketError(QAbstractSocket::SocketError) {
   qDebug() << "coreSocketError" << _socket << _socket->errorString();
   emit connectionError(_socket->errorString());
@@ -176,7 +178,6 @@ void CoreConnection::coreSocketError(QAbstractSocket::SocketError) {
 }
 
 void CoreConnection::coreSocketDisconnected() {
-  setState(Disconnected);
   emit disconnected();
   resetConnection();
   // FIXME handle disconnects gracefully
@@ -216,7 +217,7 @@ void CoreConnection::coreHasData() {
       sessionStateReceived(msg["SessionState"].toMap());
       break; // this is definitively the last message we process here!
     } else {
-      emit connectionError(tr("<b>Invalid data received from core!</b><br>Disconnecting."));
+      emit connectionError(tr("Invalid data received from core, disconnecting."));
       disconnectFromCore();
       return;
     }
@@ -227,10 +228,8 @@ void CoreConnection::coreHasData() {
 }
 
 void CoreConnection::disconnectFromCore() {
-  if(isConnected()) {
-    Client::signalProxy()->removeAllPeers();
-    resetConnection();
-  }
+  Client::signalProxy()->removeAllPeers();
+  resetConnection();
 }
 
 void CoreConnection::reconnectToCore() {
@@ -244,20 +243,30 @@ bool CoreConnection::connectToCore(AccountId accId) {
 
   CoreAccountSettings s;
 
-  if(!accId.isValid()) {
-    // check our settings and figure out what to do
-    if(!s.autoConnectOnStartup())
-      return false;
-    if(s.autoConnectToFixedAccount())
-      accId = s.autoConnectAccount();
-    else
-      accId = s.lastAccount();
-    if(!accId.isValid())
+  // FIXME: Don't force connection to internal core in mono client
+  if(Quassel::runMode() == Quassel::Monolithic) {
+    _account = accountModel()->account(accountModel()->internalAccount());
+    Q_ASSERT(_account.isValid());
+  } else {
+    if(!accId.isValid()) {
+      // check our settings and figure out what to do
+      if(!s.autoConnectOnStartup())
+        return false;
+      if(s.autoConnectToFixedAccount())
+        accId = s.autoConnectAccount();
+      else
+        accId = s.lastAccount();
+      if(!accId.isValid())
+        return false;
+    }
+    _account = accountModel()->account(accId);
+    if(!_account.accountId().isValid()) {
       return false;
-  }
-  _account = accountModel()->account(accId);
-  if(!_account.accountId().isValid()) {
-    return false;
+    }
+    if(Quassel::runMode() != Quassel::Monolithic) {
+      if(_account.isInternal())
+        return false;
+    }
   }
 
   s.setLastAccount(accId);
@@ -268,13 +277,34 @@ bool CoreConnection::connectToCore(AccountId accId) {
 void CoreConnection::connectToCurrentAccount() {
   resetConnection();
 
+  if(currentAccount().isInternal()) {
+    if(Quassel::runMode() != Quassel::Monolithic) {
+      qWarning() << "Cannot connect to internal core in client-only mode!";
+      return;
+    }
+    emit startInternalCore();
+    emit connectToInternalCore(Client::instance()->signalProxy());
+    return;
+  }
+
+  CoreAccountSettings s;
+
   Q_ASSERT(!_socket);
 #ifdef HAVE_SSL
   QSslSocket *sock = new QSslSocket(Client::instance());
+  // make sure the warning is shown if we happen to connect without SSL support later
+  s.setAccountValue("ShowNoClientSslWarning", true);
 #else
   if(_account.useSsl()) {
-    emit connectionError(tr("<b>This client is built without SSL Support!</b><br />Disable the usage of SSL in the account settings."));
-    return;
+    if(s.accountValue("ShowNoClientSslWarning", true).toBool()) {
+      bool accepted = false;
+      emit handleNoSslInClient(&accepted);
+      if(!accepted) {
+        emit connectionError(tr("Unencrypted connection canceled"));
+        return;
+      }
+      s.setAccountValue("ShowNoClientSslWarning", false);
+    }
   }
   QTcpSocket *sock = new QTcpSocket(Client::instance());
 #endif
@@ -334,28 +364,90 @@ void CoreConnection::clientInitAck(const QVariantMap &msg) {
 #endif
 
   _coreMsgBuffer = msg;
+
 #ifdef HAVE_SSL
+  CoreAccountSettings s;
   if(currentAccount().useSsl()) {
     if(msg["SupportSsl"].toBool()) {
+      // Make sure the warning is shown next time we don't have SSL in the core
+      s.setAccountValue("ShowNoCoreSslWarning", true);
+
       QSslSocket *sslSocket = qobject_cast<QSslSocket *>(_socket);
       Q_ASSERT(sslSocket);
-      connect(sslSocket, SIGNAL(encrypted()), this, SLOT(sslSocketEncrypted()));
-      connect(sslSocket, SIGNAL(sslErrors(const QList<QSslError> &)), this, SLOT(sslErrors(const QList<QSslError> &)));
-
+      connect(sslSocket, SIGNAL(encrypted()), SLOT(sslSocketEncrypted()));
+      connect(sslSocket, SIGNAL(sslErrors(const QList<QSslError> &)), SLOT(sslErrors()));
       sslSocket->startClientEncryption();
     } else {
-      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."));
-      disconnectFromCore();
+      if(s.accountValue("ShowNoCoreSslWarning", true).toBool()) {
+        bool accepted = false;
+        emit handleNoSslInCore(&accepted);
+        if(!accepted) {
+          emit connectionError(tr("Unencrypted connection canceled"));
+          disconnectFromCore();
+          return;
+        }
+        s.setAccountValue("ShowNoCoreSslWarning", false);
+        s.setAccountValue("SslCert", QString());
+      }
+      connectionReady();
     }
     return;
   }
 #endif
   // if we use SSL we wait for the next step until every SSL warning has been cleared
   connectionReady();
+}
+
+#ifdef HAVE_SSL
+
+void CoreConnection::sslSocketEncrypted() {
+  QSslSocket *socket = qobject_cast<QSslSocket *>(sender());
+  Q_ASSERT(socket);
 
+  if(!socket->sslErrors().count()) {
+    // Cert is valid, so we don't want to store it as known
+    // That way, a warning will appear in case it becomes invalid at some point
+    CoreAccountSettings s;
+    s.setAccountValue("SSLCert", QString());
+  }
+
+  emit encrypted(true);
+  connectionReady();
 }
 
+void CoreConnection::sslErrors() {
+  QSslSocket *socket = qobject_cast<QSslSocket *>(sender());
+  Q_ASSERT(socket);
+
+  CoreAccountSettings s;
+  QByteArray knownDigest = s.accountValue("SslCert").toByteArray();
+
+  if(knownDigest != socket->peerCertificate().digest()) {
+    bool accepted = false;
+    bool permanently = false;
+    emit handleSslErrors(socket, &accepted, &permanently);
+
+    if(!accepted) {
+      emit connectionError(tr("Unencrypted connection canceled"));
+      disconnectFromCore();
+      return;
+    }
+
+    if(permanently)
+      s.setAccountValue("SslCert", socket->peerCertificate().digest());
+    else
+      s.setAccountValue("SslCert", QString());
+  }
+
+  socket->ignoreSslErrors();
+}
+
+#endif /* HAVE_SSL */
+
 void CoreConnection::connectionReady() {
+  setState(Connected);
+  emit connectionMsg(tr("Connected to %1").arg(currentAccount().accountName()));
+
   if(!_coreMsgBuffer["Configured"].toBool()) {
     // start wizard
     emit startCoreSetup(_coreMsgBuffer["StorageBackends"].toList());
@@ -363,15 +455,16 @@ void CoreConnection::connectionReady() {
     loginToCore();
   }
   _coreMsgBuffer.clear();
-  resetWarningsHandler();
 }
 
-void CoreConnection::loginToCore() {
+void CoreConnection::loginToCore(const QString &prevError) {
   emit connectionMsg(tr("Logging in..."));
-  if(currentAccount().user().isEmpty() || currentAccount().password().isEmpty()) {
-    emit userAuthenticationRequired(&_account);  // *must* be a synchronous call
-    if(currentAccount().user().isEmpty() || currentAccount().password().isEmpty()) {
+  if(currentAccount().user().isEmpty() || currentAccount().password().isEmpty() || !prevError.isEmpty()) {
+    bool valid = false;
+    emit userAuthenticationRequired(&_account, &valid, prevError);  // *must* be a synchronous call
+    if(!valid || currentAccount().user().isEmpty() || currentAccount().password().isEmpty()) {
       disconnectFromCore();
+      emit connectionError(tr("Login canceled"));
       return;
     }
   }
@@ -384,16 +477,16 @@ void CoreConnection::loginToCore() {
 }
 
 void CoreConnection::loginFailed(const QString &error) {
-  emit userAuthenticationRequired(&_account, error);  // *must* be a synchronous call
-  if(currentAccount().user().isEmpty() || currentAccount().password().isEmpty()) {
-    disconnectFromCore();
-    return;
-  }
-  loginToCore();
+  loginToCore(error);
 }
 
 void CoreConnection::loginSuccess() {
   updateProgress(0, 0);
+
+  // save current account data
+  _model->createOrUpdateAccount(currentAccount());
+  _model->save();
+
   setProgressText(tr("Receiving session state"));
   setState(Synchronizing);
   emit connectionMsg(tr("Synchronizing to %1...").arg(currentAccount().accountName()));
@@ -406,10 +499,16 @@ void CoreConnection::sessionStateReceived(const QVariantMap &state) {
   disconnect(_socket, SIGNAL(readyRead()), this, 0);
   disconnect(_socket, SIGNAL(connected()), this, 0);
 
-  //Client::instance()->setConnectedToCore(currentAccount().accountId(), _socket);
   syncToCore(state);
 }
 
+void CoreConnection::internalSessionStateReceived(const QVariant &packedState) {
+  updateProgress(100, 100);
+
+  setState(Synchronizing);
+  syncToCore(packedState.toMap());
+}
+
 void CoreConnection::syncToCore(const QVariantMap &sessionState) {
   setProgressText(tr("Receiving network states"));
   updateProgress(0, 100);
@@ -460,7 +559,7 @@ void CoreConnection::networkInitDone() {
 void CoreConnection::checkSyncState() {
   if(_netsToSync.isEmpty()) {
     setState(Synchronized);
-    setProgressText(QString());
+    setProgressText(tr("Synchronized to %1").arg(currentAccount().accountName()));
     setProgressMaximum(-1);
     emit synchronized();
   }