Abstract away the protocol handshake code
authorManuel Nickschas <sputnick@quassel-irc.org>
Thu, 7 Nov 2013 19:35:55 +0000 (20:35 +0100)
committerManuel Nickschas <sputnick@quassel-irc.org>
Thu, 7 Nov 2013 22:43:01 +0000 (23:43 +0100)
This continues (and hopefully mostly concludes) the work begun
in 0e1b154 - abstracting away everything protocol-related from the
rest of the code, so we can later add another protocol. It's also
nice to not have all sorts of network- and protocol-related stuff
intertwined into central classes like Core and CoreConnection.

Note that nothing should change for end-users, modulo bugs. So new
and old client and core should work together in all combinations,
with and without SSL and/or compression. Please let me know if
something's wonky. That's also true for things like connection failure
handling and reconnects, as there have been many changes in those areas.

Turns out that abstracting the handshake stuff was much harder and
much more invasive than the previous work in SignalProxy. Turns also
out that the legacy handshake protocol is not the best ever. So
this touches lots of places in the code; sorry for a diff with over
3000 lines, but there was no way to sanely split the commits.

Anyway, here's the gist of it:

* Add auth handlers on both core and client side that process the
  handshake based on the generic message types in protocol.h - lots
  of code moved from CoreConnection and Core into the new classes.
  Note that "AuthHandler" is a stupid name for what it does, so we'll
  probably rename it to HandshakeHandler or something later.

* Extend LegacyPeer to translate the new message types to and from the
  legacy format, moving protocol-specific code out of the existing classes.
  Right now only "compat mode" is supported, i.e. the handshake we've
  been using for what feels like a decade. In the future, some of that
  should be done much nicer whilst keeping the same wire format. For now,
  there's several quirks in LegacyPeer to somehow map the insanities in
  the existing protocol to something that won't bite us in the future once
  we want to clean up that mess.

* Auth handlers will create the peer now - this is because later on the
  peer we need will be determined by protocol detection, which is part
  of the handshake.

* Use the generic protocol message types directly where it makes sense
  (in particular for things like SessionState) - instead of QVariantMaps.

* Lots of cleanups, API changes etc. were done in passing.

* Probably more that I choose to not remember right now.

32 files changed:
src/client/CMakeLists.txt
src/client/client.cpp
src/client/client.h
src/client/clientauthhandler.cpp [new file with mode: 0644]
src/client/clientauthhandler.h [new file with mode: 0644]
src/client/coreaccount.h
src/client/coreaccountmodel.h
src/client/coreconnection.cpp
src/client/coreconnection.h
src/common/CMakeLists.txt
src/common/authhandler.cpp [new file with mode: 0644]
src/common/authhandler.h [new file with mode: 0644]
src/common/internalpeer.cpp
src/common/internalpeer.h
src/common/peer.cpp
src/common/peer.h
src/common/protocols/legacy/legacypeer.cpp
src/common/protocols/legacy/legacypeer.h
src/common/remotepeer.cpp
src/common/remotepeer.h
src/common/signalproxy.h
src/core/CMakeLists.txt
src/core/core.cpp
src/core/core.h
src/core/coreauthhandler.cpp [new file with mode: 0644]
src/core/coreauthhandler.h [new file with mode: 0644]
src/core/coresession.cpp
src/core/coresession.h
src/core/sessionthread.cpp
src/core/sslserver.cpp
src/qtui/coreconfigwizard.cpp
src/qtui/monoapplication.cpp

index b8b14e1..3377771 100644 (file)
@@ -24,6 +24,7 @@ set(SOURCES
     bufferviewoverlay.cpp
     client.cpp
     clientaliasmanager.cpp
+    clientauthhandler.cpp
     clientbacklogmanager.cpp
     clientbufferviewconfig.cpp
     clientbufferviewmanager.cpp
@@ -51,6 +52,7 @@ set(MOC_HDRS
     bufferviewoverlay.h
     client.h
     clientaliasmanager.h
+    clientauthhandler.h
     clientbacklogmanager.h
     clientbufferviewconfig.h
     clientbufferviewmanager.h
index fe49f77..238e001 100644 (file)
@@ -105,7 +105,7 @@ Client::Client(QObject *parent)
     _messageModel(0),
     _messageProcessor(0),
     _coreAccountModel(new CoreAccountModel(this)),
-    _coreConnection(new CoreConnection(_coreAccountModel, this)),
+    _coreConnection(new CoreConnection(this)),
     _connected(false),
     _debugLog(&_debugLogBuffer)
 {
index 460bede..74ad868 100644 (file)
@@ -25,6 +25,7 @@
 #include <QPointer>
 
 #include "bufferinfo.h"
+#include "coreaccount.h"
 #include "coreconnection.h"
 #include "quassel.h"
 #include "types.h"
@@ -122,6 +123,8 @@ public:
     static inline CoreAccount currentCoreAccount() { return coreConnection()->currentAccount(); }
     static inline Quassel::Features coreFeatures() { return _coreFeatures; }
 
+    static void setCoreFeatures(Quassel::Features features);
+
     static bool isConnected();
     static bool internalCore();
 
@@ -214,7 +217,6 @@ private:
     void init();
 
     static void addNetwork(Network *);
-    static void setCoreFeatures(Quassel::Features);
     static inline BufferSyncer *bufferSyncer() { return instance()->_bufferSyncer; }
 
     static QPointer<Client> instanceptr;
diff --git a/src/client/clientauthhandler.cpp b/src/client/clientauthhandler.cpp
new file mode 100644 (file)
index 0000000..a390ab6
--- /dev/null
@@ -0,0 +1,352 @@
+/***************************************************************************
+ *   Copyright (C) 2005-2013 by the Quassel Project                        *
+ *   devel@quassel-irc.org                                                 *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) version 3.                                           *
+ *                                                                         *
+ *   This program is distributed in the hope that it will be useful,       *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this program; if not, write to the                         *
+ *   Free Software Foundation, Inc.,                                       *
+ *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
+ ***************************************************************************/
+
+#include "clientauthhandler.h"
+
+// TODO: support system application proxy (new in Qt 4.6)
+
+#ifdef HAVE_SSL
+    #include <QSslSocket>
+#else
+    #include <QTcpSocket>
+#endif
+
+#include "client.h"
+#include "clientsettings.h"
+
+#include "protocols/legacy/legacypeer.h"
+
+using namespace Protocol;
+
+ClientAuthHandler::ClientAuthHandler(CoreAccount account, QObject *parent)
+    : AuthHandler(parent),
+    _peer(0),
+    _account(account)
+{
+
+}
+
+
+void ClientAuthHandler::connectToCore()
+{
+    CoreAccountSettings s;
+
+#ifdef HAVE_SSL
+    QSslSocket *socket = new QSslSocket(this);
+    // make sure the warning is shown if we happen to connect without SSL support later
+    s.setAccountValue("ShowNoClientSslWarning", true);
+#else
+    if (_account.useSsl()) {
+        if (s.accountValue("ShowNoClientSslWarning", true).toBool()) {
+            bool accepted = false;
+            emit handleNoSslInClient(&accepted);
+            if (!accepted) {
+                emit errorMessage(tr("Unencrypted connection canceled"));
+                return;
+            }
+            s.setAccountValue("ShowNoClientSslWarning", false);
+        }
+    }
+    QTcpSocket *socket = new QTcpSocket(this);
+#endif
+
+// TODO: Handle system proxy
+#ifndef QT_NO_NETWORKPROXY
+    if (_account.useProxy()) {
+        QNetworkProxy proxy(_account.proxyType(), _account.proxyHostName(), _account.proxyPort(), _account.proxyUser(), _account.proxyPassword());
+        socket->setProxy(proxy);
+    }
+#endif
+
+    setSocket(socket);
+    // handled by the base class for now; may need to rethink for protocol detection
+    //connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(onSocketError(QAbstractSocket::SocketError)));
+    //connect(socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), SLOT(onSocketStateChanged(QAbstractSocket::SocketState)));
+    connect(socket, SIGNAL(connected()), SLOT(onSocketConnected()));
+
+    emit statusMessage(tr("Connecting to %1...").arg(_account.accountName()));
+    socket->connectToHost(_account.hostName(), _account.port());
+}
+
+
+// TODO: handle protocol detection
+// This method might go away anyway, unless we really need our own states...
+/*
+void ClientAuthHandler::onSocketStateChanged(QAbstractSocket::SocketState socketState)
+{
+    qDebug() << Q_FUNC_INFO << socketState;
+    QString text;
+    AuthHandler::State state = UnconnectedState;
+
+    switch(socketState) {
+        case QAbstractSocket::UnconnectedState:
+            text = tr("Disconnected");
+            state = UnconnectedState;
+            break;
+        case QAbstractSocket::HostLookupState:
+            text = tr("Looking up %1...").arg(_account.hostName());
+            state = HostLookupState;
+            break;
+        case QAbstractSocket::ConnectingState:
+            text = tr("Connecting to %1...").arg(_account.hostName());
+            state = ConnectingState;
+            break;
+        case QAbstractSocket::ConnectedState:
+            text = tr("Connected to %1").arg(_account.hostName());
+            state = ConnectedState;
+            break;
+        case QAbstractSocket::ClosingState:
+            text = tr("Disconnecting from %1...").arg(_account.hostName());
+            state = ClosingState;
+            break;
+        default:
+            break;
+    }
+
+    if (!text.isEmpty()) {
+        setState(state);
+        emit statusMessage(text);
+    }
+}
+*/
+
+// TODO: handle protocol detection
+/*
+void ClientAuthHandler::onSocketError(QAbstractSocket::SocketError error)
+{
+    emit socketError(error, socket()->errorString());
+}
+*/
+
+void ClientAuthHandler::onSocketConnected()
+{
+    // TODO: protocol detection
+
+    if (_peer) {
+        qWarning() << Q_FUNC_INFO << "Peer already exists!";
+        return;
+    }
+
+    _peer = new LegacyPeer(this, socket(), this);
+
+    connect(_peer, SIGNAL(transferProgress(int,int)), SIGNAL(transferProgress(int,int)));
+
+    // compat only
+    connect(_peer, SIGNAL(protocolVersionMismatch(int,int)), SLOT(onProtocolVersionMismatch(int,int)));
+
+    emit statusMessage(tr("Synchronizing to core..."));
+
+    bool useSsl = false;
+#ifdef HAVE_SSL
+    useSsl = _account.useSsl();
+#endif
+
+    _peer->dispatch(RegisterClient(Quassel::buildInfo().fancyVersionString, useSsl));
+}
+
+
+void ClientAuthHandler::onProtocolVersionMismatch(int actual, int expected)
+{
+    emit errorPopup(tr("<b>The Quassel Core you are trying to connect to is too old!</b><br>"
+           "We need at least protocol v%1, but the core speaks v%2 only.").arg(expected, actual));
+    requestDisconnect();
+}
+
+
+void ClientAuthHandler::handle(const ClientDenied &msg)
+{
+    emit errorPopup(msg.errorString);
+    requestDisconnect();
+}
+
+
+void ClientAuthHandler::handle(const ClientRegistered &msg)
+{
+    _coreConfigured = msg.coreConfigured;
+    _backendInfo = msg.backendInfo;
+
+    Client::setCoreFeatures(static_cast<Quassel::Features>(msg.coreFeatures));
+
+#ifdef HAVE_SSL
+    CoreAccountSettings s;
+    if (_account.useSsl()) {
+        if (msg.sslSupported) {
+            // 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()), SLOT(onSslSocketEncrypted()));
+            connect(sslSocket, SIGNAL(sslErrors(QList<QSslError>)), SLOT(onSslErrors()));
+            qDebug() << "Starting encryption...";
+            sslSocket->flush();
+            sslSocket->startClientEncryption();
+        }
+        else {
+            if (s.accountValue("ShowNoCoreSslWarning", true).toBool()) {
+                bool accepted = false;
+                emit handleNoSslInCore(&accepted);
+                if (!accepted) {
+                    requestDisconnect(tr("Unencrypted connection cancelled"));
+                    return;
+                }
+                s.setAccountValue("ShowNoCoreSslWarning", false);
+                s.setAccountValue("SslCert", QString());
+            }
+            onConnectionReady();
+        }
+        return;
+    }
+#endif
+    // if we use SSL we wait for the next step until every SSL warning has been cleared
+    onConnectionReady();
+}
+
+
+#ifdef HAVE_SSL
+
+void ClientAuthHandler::onSslSocketEncrypted()
+{
+    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);
+    onConnectionReady();
+}
+
+
+void ClientAuthHandler::onSslErrors()
+{
+    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) {
+            requestDisconnect(tr("Unencrypted connection canceled"));
+            return;
+        }
+
+        if (permanently)
+            s.setAccountValue("SslCert", socket->peerCertificate().digest());
+        else
+            s.setAccountValue("SslCert", QString());
+    }
+
+    socket->ignoreSslErrors();
+}
+
+#endif /* HAVE_SSL */
+
+
+void ClientAuthHandler::onConnectionReady()
+{
+    emit connectionReady();
+    emit statusMessage(tr("Connected to %1").arg(_account.accountName()));
+
+    if (!_coreConfigured) {
+        // start wizard
+        emit startCoreSetup(_backendInfo);
+    }
+    else // TODO: check if we need LoginEnabled
+        login();
+}
+
+
+void ClientAuthHandler::setupCore(const SetupData &setupData)
+{
+    _peer->dispatch(setupData);
+}
+
+
+void ClientAuthHandler::handle(const SetupFailed &msg)
+{
+    emit coreSetupFailed(msg.errorString);
+}
+
+
+void ClientAuthHandler::handle(const SetupDone &msg)
+{
+    Q_UNUSED(msg)
+
+    emit coreSetupSuccessful();
+}
+
+
+void ClientAuthHandler::login(const QString &user, const QString &password, bool remember)
+{
+    _account.setUser(user);
+    _account.setPassword(password);
+    _account.setStorePassword(remember);
+    login();
+}
+
+
+void ClientAuthHandler::login(const QString &previousError)
+{
+    emit statusMessage(tr("Logging in..."));
+    if (_account.user().isEmpty() || _account.password().isEmpty() || !previousError.isEmpty()) {
+        bool valid = false;
+        emit userAuthenticationRequired(&_account, &valid, previousError); // *must* be a synchronous call
+        if (!valid || _account.user().isEmpty() || _account.password().isEmpty()) {
+            requestDisconnect(tr("Login canceled"));
+            return;
+        }
+    }
+
+    _peer->dispatch(Login(_account.user(), _account.password()));
+}
+
+
+void ClientAuthHandler::handle(const LoginFailed &msg)
+{
+    login(msg.errorString);
+}
+
+
+void ClientAuthHandler::handle(const LoginSuccess &msg)
+{
+    Q_UNUSED(msg)
+
+    emit loginSuccessful(_account);
+}
+
+
+void ClientAuthHandler::handle(const SessionState &msg)
+{
+    disconnect(socket(), 0, this, 0); // this is the last message we shall ever get
+
+    // give up ownership of the peer; CoreSession takes responsibility now
+    _peer->setParent(0);
+    emit handshakeComplete(_peer, msg);
+}
diff --git a/src/client/clientauthhandler.h b/src/client/clientauthhandler.h
new file mode 100644 (file)
index 0000000..35c741c
--- /dev/null
@@ -0,0 +1,102 @@
+/***************************************************************************
+ *   Copyright (C) 2005-2013 by the Quassel Project                        *
+ *   devel@quassel-irc.org                                                 *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) version 3.                                           *
+ *                                                                         *
+ *   This program is distributed in the hope that it will be useful,       *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this program; if not, write to the                         *
+ *   Free Software Foundation, Inc.,                                       *
+ *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
+ ***************************************************************************/
+
+#ifndef CLIENTAUTHHANDLER_H
+#define CLIENTAUTHHANDLER_H
+
+#include "authhandler.h"
+#include "coreaccount.h"
+
+class QSslSocket;
+
+class RemotePeer;
+
+class ClientAuthHandler : public AuthHandler
+{
+    Q_OBJECT
+
+public:
+    ClientAuthHandler(CoreAccount account, QObject *parent = 0);
+
+public slots:
+    void connectToCore();
+
+    void login(const QString &previousError = QString());
+    void login(const QString &user, const QString &password, bool remember);
+    void setupCore(const Protocol::SetupData &setupData);
+
+signals:
+    void statusMessage(const QString &message);
+    void errorMessage(const QString &message);
+    void errorPopup(const QString &message);
+    void transferProgress(int current, int max);
+
+    void requestDisconnect(const QString &errorString = QString(), bool wantReconnect = false);
+
+    void connectionReady();
+    void loginSuccessful(const CoreAccount &account);
+    void handshakeComplete(RemotePeer *peer, const Protocol::SessionState &sessionState);
+
+    // These signals MUST be handled synchronously!
+    void userAuthenticationRequired(CoreAccount *account, bool *valid, const QString &errorMessage = QString());
+    void handleNoSslInClient(bool *accepted);
+    void handleNoSslInCore(bool *accepted);
+#ifdef HAVE_SSL
+    void handleSslErrors(const QSslSocket *socket, bool *accepted, bool *permanently);
+#endif
+
+    void encrypted(bool isEncrypted = true);
+    void startCoreSetup(const QVariantList &backendInfo);
+    void coreSetupSuccessful();
+    void coreSetupFailed(const QString &error);
+
+private:
+    using AuthHandler::handle;
+
+    void handle(const Protocol::ClientDenied &msg);
+    void handle(const Protocol::ClientRegistered &msg);
+    void handle(const Protocol::SetupFailed &msg);
+    void handle(const Protocol::SetupDone &msg);
+    void handle(const Protocol::LoginFailed &msg);
+    void handle(const Protocol::LoginSuccess &msg);
+    void handle(const Protocol::SessionState &msg);
+
+private slots:
+    void onSocketConnected();
+    //void onSocketStateChanged(QAbstractSocket::SocketState state);
+    //void onSocketError(QAbstractSocket::SocketError);
+#ifdef HAVE_SSL
+    void onSslSocketEncrypted();
+    void onSslErrors();
+#endif
+
+    void onProtocolVersionMismatch(int actual, int expected);
+
+    void onConnectionReady();
+
+private:
+    RemotePeer *_peer;
+    bool _coreConfigured;
+    QVariantList _backendInfo;
+    CoreAccount _account;
+
+};
+
+#endif
index 6da6734..5003b59 100644 (file)
@@ -70,7 +70,7 @@ public:
     void setProxyHostName(const QString &);
     void setProxyPort(uint);
 
-    /* These might be overridden for KWallet support */
+    /* These might be overridden for KWallet/QtKeychain support */
     virtual inline QString password() const { return _password; }
     virtual void setPassword(const QString &password);
     virtual inline QString proxyPassword() const { return _proxyPassword; }
index 24aeffd..41b35c2 100644 (file)
@@ -50,7 +50,7 @@ public:
 
     inline AccountId internalAccount() const;
 
-    AccountId createOrUpdateAccount(const CoreAccount &newAccountData = CoreAccount());
+    AccountId createOrUpdateAccount(const CoreAccount &newAccountData);
     CoreAccount takeAccount(AccountId);
     void removeAccount(AccountId);
 
index 7112b85..7725d86 100644 (file)
 
 #include "coreconnection.h"
 
-#ifndef QT_NO_NETWORKPROXY
-#  include <QNetworkProxy>
-#endif
-
 #include "client.h"
+#include "clientauthhandler.h"
 #include "clientsettings.h"
 #include "coreaccountmodel.h"
 #include "identity.h"
@@ -37,9 +34,9 @@
 
 #include "protocols/legacy/legacypeer.h"
 
-CoreConnection::CoreConnection(CoreAccountModel *model, QObject *parent)
+CoreConnection::CoreConnection(QObject *parent)
     : QObject(parent),
-    _model(model),
+    _authHandler(0),
     _state(Disconnected),
     _wantReconnect(false),
     _wasReconnect(false),
@@ -73,6 +70,12 @@ void CoreConnection::init()
 }
 
 
+CoreAccountModel *CoreConnection::accountModel() const
+{
+    return Client::coreAccountModel();
+}
+
+
 void CoreConnection::setProgressText(const QString &text)
 {
     if (_progressText != text) {
@@ -194,7 +197,6 @@ void CoreConnection::solidNetworkStatusChanged(Solid::Networking::Status status)
     }
 }
 
-
 #endif
 
 bool CoreConnection::isEncrypted() const
@@ -268,6 +270,12 @@ void CoreConnection::setState(QAbstractSocket::SocketState socketState)
 }
 
 
+void CoreConnection::onConnectionReady()
+{
+    setState(Connected);
+}
+
+
 void CoreConnection::setState(ConnectionState state)
 {
     if (state != _state) {
@@ -279,9 +287,11 @@ void CoreConnection::setState(ConnectionState state)
 }
 
 
-void CoreConnection::coreSocketError(QAbstractSocket::SocketError)
+void CoreConnection::coreSocketError(QAbstractSocket::SocketError error, const QString &errorString)
 {
-    disconnectFromCore(_socket->errorString(), true);
+    Q_UNUSED(error)
+
+    disconnectFromCore(errorString, true);
 }
 
 
@@ -293,60 +303,9 @@ void CoreConnection::coreSocketDisconnected()
 }
 
 
-// note: this still expects the legacy protocol
-// noteĀ²: after cleaning this up, we can probably get rid of _socket altogether
-void CoreConnection::coreHasData(const QVariant &item)
-{
-    QVariantMap msg = item.toMap();
-    if (!msg.contains("MsgType")) {
-        // This core is way too old and does not even speak our init protocol...
-        emit connectionErrorPopup(tr("The Quassel Core you try to connect to is too old! Please consider upgrading."));
-        disconnectFromCore(QString(), false);
-        return;
-    }
-    if (msg["MsgType"] == "ClientInitAck") {
-        clientInitAck(msg);
-    }
-    else if (msg["MsgType"] == "ClientInitReject") {
-        emit connectionErrorPopup(msg["Error"].toString());
-        disconnectFromCore(QString(), false);
-        return;
-    }
-    else if (msg["MsgType"] == "CoreSetupAck") {
-        emit coreSetupSuccess();
-    }
-    else if (msg["MsgType"] == "CoreSetupReject") {
-        emit coreSetupFailed(msg["Error"].toString());
-    }
-    else if (msg["MsgType"] == "ClientLoginReject") {
-        loginFailed(msg["Error"].toString());
-    }
-    else if (msg["MsgType"] == "ClientLoginAck") {
-        loginSuccess();
-    }
-    else if (msg["MsgType"] == "SessionInit") {
-        // that's it, let's hand over to the signal proxy
-        // if the connection is an orphan, the signalProxy adopts it.
-        // -> we don't need to care about it anymore
-
-        disconnect(_peer, 0, this, 0);
-
-        _peer->setParent(0);
-        Client::signalProxy()->addPeer(_peer);
-
-        sessionStateReceived(msg["SessionState"].toMap());
-    }
-    else {
-        disconnectFromCore(tr("Invalid data received from core"), false);
-        return;
-    }
-}
-
-
 void CoreConnection::disconnectFromCore()
 {
-    if (_socket)
-        disconnectFromCore(QString(), false); // requested disconnect, so don't try to reconnect
+    disconnectFromCore(QString(), false); // requested disconnect, so don't try to reconnect
 }
 
 
@@ -360,8 +319,10 @@ void CoreConnection::disconnectFromCore(const QString &errorString, bool wantRec
     _wantReconnect = wantReconnect; // store if disconnect was requested
     _wasReconnect = false;
 
-    if (_socket)
-        _socket->close();
+    if (_authHandler)
+        _authHandler->close();
+    else if(_peer)
+        _peer->close();
 
     if (errorString.isEmpty())
         emit connectionError(tr("Disconnected"));
@@ -378,23 +339,20 @@ void CoreConnection::resetConnection(bool wantReconnect)
 
     _wantReconnect = wantReconnect;
 
+    if (_authHandler) {
+        disconnect(_authHandler, 0, this, 0);
+        _authHandler->close();
+        _authHandler->deleteLater();
+        _authHandler = 0;
+    }
+
     if (_peer) {
-        disconnect(_socket, 0, this, 0);
         disconnect(_peer, 0, this, 0);
+        // peer belongs to the sigproxy and thus gets deleted by it
         _peer->close();
-
-        if (_peer->parent() == this)
-            _peer->deleteLater(); // if it's not us, it belongs to the sigproxy which will delete it
-        _socket = 0;      // socket is owned and will be deleted by RemoteConnection
         _peer = 0;
     }
-    else if (_socket) {
-        disconnect(_socket, 0, this, 0);
-        _socket->deleteLater();
-        _socket = 0;
-    }
 
-    _coreMsgBuffer.clear();
     _netsToSync.clear();
     _numNetsToSync = 0;
 
@@ -466,6 +424,11 @@ bool CoreConnection::connectToCore(AccountId accId)
 
 void CoreConnection::connectToCurrentAccount()
 {
+    if (_authHandler) {
+        qWarning() << Q_FUNC_INFO << "Already connected!";
+        return;
+    }
+
     resetConnection(false);
 
     if (currentAccount().isInternal()) {
@@ -476,298 +439,125 @@ void CoreConnection::connectToCurrentAccount()
         emit startInternalCore();
 
         InternalPeer *peer = new InternalPeer();
+        _peer = peer;
         Client::instance()->signalProxy()->addPeer(peer); // sigproxy will take ownership
         emit connectToInternalCore(peer);
 
         return;
     }
 
-    CoreAccountSettings s;
-
-    Q_ASSERT(!_socket);
-#ifdef HAVE_SSL
-    QSslSocket *sock = new QSslSocket(this);
-    // make sure the warning is shown if we happen to connect without SSL support later
-    s.setAccountValue("ShowNoClientSslWarning", true);
-#else
-    if (_account.useSsl()) {
-        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(this);
-#endif
-
-#ifndef QT_NO_NETWORKPROXY
-    if (_account.useProxy()) {
-        QNetworkProxy proxy(_account.proxyType(), _account.proxyHostName(), _account.proxyPort(), _account.proxyUser(), _account.proxyPassword());
-        sock->setProxy(proxy);
-    }
-#endif
-
-    _socket = sock;
-    connect(sock, SIGNAL(connected()), SLOT(coreSocketConnected()));
-    connect(sock, SIGNAL(disconnected()), SLOT(coreSocketDisconnected()));
-    connect(sock, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(coreSocketError(QAbstractSocket::SocketError)));
-    connect(sock, SIGNAL(stateChanged(QAbstractSocket::SocketState)), SLOT(socketStateChanged(QAbstractSocket::SocketState)));
-
-    emit connectionMsg(tr("Connecting to %1...").arg(currentAccount().accountName()));
-    sock->connectToHost(_account.hostName(), _account.port());
-}
-
-
-void CoreConnection::coreSocketConnected()
-{
-    // Create the connection which will handle the incoming data
-    Q_ASSERT(!_peer);
-    _peer = new LegacyPeer(_socket, this);
-    connect(_peer, SIGNAL(dataReceived(QVariant)), SLOT(coreHasData(QVariant)));
-    connect(_peer, SIGNAL(transferProgress(int,int)), SLOT(updateProgress(int,int)));
-
-    // Phase One: Send client info and wait for core info
-
-    emit connectionMsg(tr("Synchronizing to core..."));
-
-    QVariantMap clientInit;
-    clientInit["MsgType"] = "ClientInit";
-    clientInit["ClientVersion"] = Quassel::buildInfo().fancyVersionString;
-    clientInit["ClientDate"] = Quassel::buildInfo().buildDate;
-    clientInit["ProtocolVersion"] = Quassel::buildInfo().protocolVersion;
-    clientInit["UseSsl"] = _account.useSsl();
-#ifndef QT_NO_COMPRESS
-    clientInit["UseCompression"] = true;
-#else
-    clientInit["UseCompression"] = false;
-#endif
-
-    qobject_cast<RemotePeer *>(_peer)->writeSocketData(clientInit);
-}
-
-
-void CoreConnection::clientInitAck(const QVariantMap &msg)
-{
-    // Core has accepted our version info and sent its own. Let's see if we accept it as well...
-    uint ver = msg["ProtocolVersion"].toUInt();
-    if (ver < Quassel::buildInfo().clientNeedsProtocol) {
-        emit connectionErrorPopup(tr("<b>The Quassel Core you are trying to connect to is too old!</b><br>"
-                                     "Need at least core/client protocol v%1 to connect.").arg(Quassel::buildInfo().clientNeedsProtocol));
-        disconnectFromCore(QString(), false);
-        return;
-    }
-
-    Client::setCoreFeatures((Quassel::Features)msg["CoreFeatures"].toUInt());
-
-#ifndef QT_NO_COMPRESS
-    if (msg["SupportsCompression"].toBool()) {
-        _socket->setProperty("UseCompression", true);
-    }
-#endif
-
-    _coreMsgBuffer = msg;
-
+    _authHandler = new ClientAuthHandler(currentAccount(), this);
+
+    connect(_authHandler, SIGNAL(disconnected()), SLOT(coreSocketDisconnected()));
+    connect(_authHandler, SIGNAL(connectionReady()), SLOT(onConnectionReady()));
+    connect(_authHandler, SIGNAL(socketStateChanged(QAbstractSocket::SocketState)), SLOT(socketStateChanged(QAbstractSocket::SocketState)));
+    connect(_authHandler, SIGNAL(socketError(QAbstractSocket::SocketError,QString)), SLOT(coreSocketError(QAbstractSocket::SocketError,QString)));
+    connect(_authHandler, SIGNAL(transferProgress(int,int)), SLOT(updateProgress(int,int)));
+    connect(_authHandler, SIGNAL(requestDisconnect(QString,bool)), SLOT(disconnectFromCore(QString,bool)));
+
+    connect(_authHandler, SIGNAL(errorMessage(QString)), SIGNAL(connectionError(QString)));
+    connect(_authHandler, SIGNAL(errorPopup(QString)), SIGNAL(connectionErrorPopup(QString)));
+    connect(_authHandler, SIGNAL(statusMessage(QString)), SIGNAL(connectionMsg(QString)));
+    connect(_authHandler, SIGNAL(encrypted(bool)), SIGNAL(encrypted(bool)));
+    connect(_authHandler, SIGNAL(startCoreSetup(QVariantList)), SIGNAL(startCoreSetup(QVariantList)));
+    connect(_authHandler, SIGNAL(coreSetupFailed(QString)), SIGNAL(coreSetupFailed(QString)));
+    connect(_authHandler, SIGNAL(coreSetupSuccessful()), SIGNAL(coreSetupSuccess()));
+    connect(_authHandler, SIGNAL(userAuthenticationRequired(CoreAccount*,bool*,QString)), SIGNAL(userAuthenticationRequired(CoreAccount*,bool*,QString)));
+    connect(_authHandler, SIGNAL(handleNoSslInClient(bool*)), SIGNAL(handleNoSslInClient(bool*)));
+    connect(_authHandler, SIGNAL(handleNoSslInCore(bool*)), SIGNAL(handleNoSslInCore(bool*)));
 #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()), SLOT(sslSocketEncrypted()));
-            connect(sslSocket, SIGNAL(sslErrors(const QList<QSslError> &)), SLOT(sslErrors()));
-            sslSocket->startClientEncryption();
-        }
-        else {
-            if (s.accountValue("ShowNoCoreSslWarning", true).toBool()) {
-                bool accepted = false;
-                emit handleNoSslInCore(&accepted);
-                if (!accepted) {
-                    disconnectFromCore(tr("Unencrypted connection canceled"), false);
-                    return;
-                }
-                s.setAccountValue("ShowNoCoreSslWarning", false);
-                s.setAccountValue("SslCert", QString());
-            }
-            connectionReady();
-        }
-        return;
-    }
+    connect(_authHandler, SIGNAL(handleSslErrors(const QSslSocket*,bool*,bool*)), SIGNAL(handleSslErrors(const QSslSocket*,bool*,bool*)));
 #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());
-    }
+    connect(_authHandler, SIGNAL(loginSuccessful(CoreAccount)), SLOT(onLoginSuccessful(CoreAccount)));
+    connect(_authHandler, SIGNAL(handshakeComplete(RemotePeer*,Protocol::SessionState)), SLOT(onHandshakeComplete(RemotePeer*,Protocol::SessionState)));
 
-    emit encrypted(true);
-    connectionReady();
+    _authHandler->connectToCore();
 }
 
 
-void CoreConnection::sslErrors()
+void CoreConnection::setupCore(const Protocol::SetupData &setupData)
 {
-    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) {
-            disconnectFromCore(tr("Unencrypted connection canceled"), false);
-            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());
-    }
-    else if (_coreMsgBuffer["LoginEnabled"].toBool()) {
-        loginToCore();
-    }
-    _coreMsgBuffer.clear();
+    _authHandler->setupCore(setupData);
 }
 
 
 void CoreConnection::loginToCore(const QString &user, const QString &password, bool remember)
 {
-    _account.setUser(user);
-    _account.setPassword(password);
-    _account.setStorePassword(remember);
-    loginToCore();
-}
-
-
-void CoreConnection::loginToCore(const QString &prevError)
-{
-    emit connectionMsg(tr("Logging in..."));
-    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(tr("Login canceled"), false);
-            return;
-        }
-    }
-
-    QVariantMap clientLogin;
-    clientLogin["MsgType"] = "ClientLogin";
-    clientLogin["User"] = currentAccount().user();
-    clientLogin["Password"] = currentAccount().password();
-    qobject_cast<RemotePeer*>(_peer)->writeSocketData(clientLogin);
-}
-
-
-void CoreConnection::loginFailed(const QString &error)
-{
-    loginToCore(error);
+    _authHandler->login(user, password, remember);
 }
 
 
-void CoreConnection::loginSuccess()
+void CoreConnection::onLoginSuccessful(const CoreAccount &account)
 {
     updateProgress(0, 0);
 
     // save current account data
-    _model->createOrUpdateAccount(currentAccount());
-    _model->save();
+    accountModel()->createOrUpdateAccount(account);
+    accountModel()->save();
 
     _reconnectTimer.stop();
 
     setProgressText(tr("Receiving session state"));
     setState(Synchronizing);
-    emit connectionMsg(tr("Synchronizing to %1...").arg(currentAccount().accountName()));
+    emit connectionMsg(tr("Synchronizing to %1...").arg(account.accountName()));
 }
 
 
-void CoreConnection::sessionStateReceived(const QVariantMap &state)
+void CoreConnection::onHandshakeComplete(RemotePeer *peer, const Protocol::SessionState &sessionState)
 {
     updateProgress(100, 100);
 
-    syncToCore(state);
+    disconnect(_authHandler, 0, this, 0);
+    _authHandler->deleteLater();
+    _authHandler = 0;
+
+    _peer = peer;
+    connect(peer, SIGNAL(disconnected()), SLOT(coreSocketDisconnected()));
+    connect(peer, SIGNAL(socketStateChanged(QAbstractSocket::SocketState)), SLOT(socketStateChanged(QAbstractSocket::SocketState)));
+    connect(peer, SIGNAL(socketError(QAbstractSocket::SocketError,QString)), SLOT(coreSocketError(QAbstractSocket::SocketError,QString)));
+
+    Client::signalProxy()->addPeer(_peer);  // sigproxy takes ownership of the peer!
+
+    syncToCore(sessionState);
 }
 
 
-void CoreConnection::internalSessionStateReceived(const QVariant &packedState)
+void CoreConnection::internalSessionStateReceived(const Protocol::SessionState &sessionState)
 {
     updateProgress(100, 100);
 
+    Client::setCoreFeatures(Quassel::features()); // mono connection...
+
     setState(Synchronizing);
-    syncToCore(packedState.toMap());
+    syncToCore(sessionState);
 }
 
 
-void CoreConnection::syncToCore(const QVariantMap &sessionState)
+void CoreConnection::syncToCore(const Protocol::SessionState &sessionState)
 {
-    if (sessionState.contains("CoreFeatures"))
-        Client::setCoreFeatures((Quassel::Features)sessionState["CoreFeatures"].toUInt());
-
     setProgressText(tr("Receiving network states"));
     updateProgress(0, 100);
 
     // create identities
-    foreach(QVariant vid, sessionState["Identities"].toList()) {
+    foreach(const QVariant &vid, sessionState.identities) {
         Client::instance()->coreIdentityCreated(vid.value<Identity>());
     }
 
     // create buffers
     // FIXME: get rid of this crap -- why?
-    QVariantList bufferinfos = sessionState["BufferInfos"].toList();
     NetworkModel *networkModel = Client::networkModel();
     Q_ASSERT(networkModel);
-    foreach(QVariant vinfo, bufferinfos)
-    networkModel->bufferUpdated(vinfo.value<BufferInfo>());  // create BufferItems
-
-    QVariantList networkids = sessionState["NetworkIds"].toList();
+    foreach(const QVariant &vinfo, sessionState.bufferInfos)
+        networkModel->bufferUpdated(vinfo.value<BufferInfo>());  // create BufferItems
 
     // prepare sync progress thingys...
     // FIXME: Care about removal of networks
-    _numNetsToSync = networkids.count();
+    _numNetsToSync = sessionState.networkIds.count();
     updateProgress(0, _numNetsToSync);
 
     // create network objects
-    foreach(QVariant networkid, networkids) {
+    foreach(const QVariant &networkid, sessionState.networkIds) {
         NetworkId netid = networkid.value<NetworkId>();
         if (Client::network(netid))
             continue;
@@ -802,12 +592,3 @@ void CoreConnection::checkSyncState()
         emit synchronized();
     }
 }
-
-
-void CoreConnection::doCoreSetup(const QVariant &setupData)
-{
-    QVariantMap setup;
-    setup["MsgType"] = "CoreSetupData";
-    setup["SetupData"] = setupData;
-    qobject_cast<RemotePeer *>(_peer)->writeSocketData(setup);
-}
index 0bf402e..8b09f91 100644 (file)
@@ -21,8 +21,6 @@
 #ifndef CORECONNECTION_H_
 #define CORECONNECTION_H_
 
-// TODO: support system application proxy (new in Qt 4.6)
-
 #include "QPointer"
 #include "QTimer"
 
@@ -40,6 +38,7 @@
 #include "remotepeer.h"
 #include "types.h"
 
+class ClientAuthHandler;
 class CoreAccountModel;
 class InternalPeer;
 class Network;
@@ -59,7 +58,7 @@ public:
         Synchronized
     };
 
-    CoreConnection(CoreAccountModel *model, QObject *parent = 0);
+    CoreConnection(QObject *parent = 0);
 
     void init();
 
@@ -78,15 +77,13 @@ public:
     //! Check if we consider the last connect as reconnect
     bool wasReconnect() const { return _wasReconnect; }
 
-#ifdef HAVE_SSL
-    const QSslSocket *sslSocket() const;
-#endif
-
 public slots:
     bool connectToCore(AccountId = 0);
     void reconnectToCore();
     void disconnectFromCore();
 
+    void setupCore(const Protocol::SetupData &setupData);
+
 signals:
     void stateChanged(CoreConnection::ConnectionState);
     void encrypted(bool isEncrypted = true);
@@ -95,7 +92,6 @@ signals:
 
     void connectionError(const QString &errorMsg);
     void connectionErrorPopup(const QString &errorMsg);
-    void connectionWarnings(const QStringList &warnings);
     void connectionMsg(const QString &msg);
     void disconnected();
 
@@ -103,7 +99,7 @@ signals:
     void progressValueChanged(int value);
     void progressTextChanged(const QString &);
 
-    void startCoreSetup(const QVariantList &);
+    void startCoreSetup(const QVariantList &backendInfo);
     void coreSetupSuccess();
     void coreSetupFailed(const QString &error);
 
@@ -123,30 +119,22 @@ private slots:
     void disconnectFromCore(const QString &errorString, bool wantReconnect = true);
 
     void socketStateChanged(QAbstractSocket::SocketState);
-    void coreSocketError(QAbstractSocket::SocketError);
-    void coreHasData(const QVariant &item);
-    void coreSocketConnected();
+    void coreSocketError(QAbstractSocket::SocketError error, const QString &errorString);
     void coreSocketDisconnected();
 
-    void clientInitAck(const QVariantMap &msg);
-
     // for sync progress
     void networkInitDone();
     void checkSyncState();
 
-    void syncToCore(const QVariantMap &sessionState);
-    void internalSessionStateReceived(const QVariant &packedState);
-    void sessionStateReceived(const QVariantMap &state);
+    void loginToCore(const QString &user, const QString &password, bool remember); // for config wizard
+    void syncToCore(const Protocol::SessionState &sessionState);
+    void internalSessionStateReceived(const Protocol::SessionState &sessionState);
 
     void resetConnection(bool wantReconnect = false);
-    void connectionReady();
-
-    void loginToCore(const QString &user, const QString &password, bool remember); // for config wizard
-    void loginToCore(const QString &previousError = QString());
-    void loginSuccess();
-    void loginFailed(const QString &errorMessage);
 
-    void doCoreSetup(const QVariant &setupData);
+    void onConnectionReady();
+    void onLoginSuccessful(const CoreAccount &account);
+    void onHandshakeComplete(RemotePeer *peer, const Protocol::SessionState &sessionState);
 
     void updateProgress(int value, int maximum);
     void setProgressText(const QString &text);
@@ -157,11 +145,6 @@ private slots:
     void setState(QAbstractSocket::SocketState socketState);
     void setState(ConnectionState state);
 
-#ifdef HAVE_SSL
-    void sslSocketEncrypted();
-    void sslErrors();
-#endif
-
     void networkDetectionModeChanged(const QVariant &mode);
     void pingTimeoutIntervalChanged(const QVariant &interval);
     void reconnectIntervalChanged(const QVariant &interval);
@@ -172,11 +155,7 @@ private slots:
 #endif
 
 private:
-    CoreAccountModel *_model;
-    CoreAccount _account;
-    QVariantMap _coreMsgBuffer;
-
-    QPointer<QTcpSocket> _socket;
+    QPointer<ClientAuthHandler> _authHandler;
     QPointer<Peer> _peer;
     ConnectionState _state;
 
@@ -189,10 +168,10 @@ private:
     int _progressMinimum, _progressMaximum, _progressValue;
     QString _progressText;
 
-    QString _coreInfoString(const QVariantMap &);
     bool _resetting;
 
-    inline CoreAccountModel *accountModel() const;
+    CoreAccount _account;
+    CoreAccountModel *accountModel() const;
 
     friend class CoreConfigWizard;
 };
@@ -209,10 +188,5 @@ inline QString CoreConnection::progressText() const { return _progressText; }
 inline CoreConnection::ConnectionState CoreConnection::state() const { return _state; }
 inline bool CoreConnection::isConnected() const { return state() >= Connected; }
 inline CoreAccount CoreConnection::currentAccount() const { return _account; }
-inline CoreAccountModel *CoreConnection::accountModel() const { return _model; }
-
-#ifdef HAVE_SSL
-inline const QSslSocket *CoreConnection::sslSocket() const { return qobject_cast<QSslSocket *>(_socket); }
-#endif
 
 #endif
index 18e6fcf..04fbe3b 100644 (file)
@@ -4,6 +4,7 @@ setup_qt_variables(Core Network)
 
 set(SOURCES
     aliasmanager.cpp
+    authhandler.cpp
     backlogmanager.cpp
     basichandler.cpp
     bufferinfo.cpp
@@ -41,6 +42,7 @@ set(SOURCES
 
 set(MOC_HDRS
     aliasmanager.h
+    authhandler.h
     backlogmanager.h
     basichandler.h
     buffersyncer.h
@@ -97,7 +99,7 @@ if(CMAKE_HOST_UNIX)
     set(SOURCES ${SOURCES} logbacktrace_unix.cpp)
 endif(CMAKE_HOST_UNIX)
 
-include_directories(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR} ${QUASSEL_QT_INCLUDES})  # for version.inc and version.gen
+include_directories(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ${QUASSEL_QT_INCLUDES})  # for version.inc and version.gen
 
 if(NOT WITH_QT5)
   qt4_wrap_cpp(MOC ${MOC_HDRS})
diff --git a/src/common/authhandler.cpp b/src/common/authhandler.cpp
new file mode 100644 (file)
index 0000000..9877145
--- /dev/null
@@ -0,0 +1,78 @@
+/***************************************************************************
+ *   Copyright (C) 2005-2013 by the Quassel Project                        *
+ *   devel@quassel-irc.org                                                 *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) version 3.                                           *
+ *                                                                         *
+ *   This program is distributed in the hope that it will be useful,       *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this program; if not, write to the                         *
+ *   Free Software Foundation, Inc.,                                       *
+ *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
+ ***************************************************************************/
+
+#include "authhandler.h"
+
+AuthHandler::AuthHandler(QObject *parent)
+    : QObject(parent),
+    _state(UnconnectedState),
+    _socket(0)
+{
+
+}
+
+
+AuthHandler::State AuthHandler::state() const
+{
+    return _state;
+}
+
+
+void AuthHandler::setState(AuthHandler::State state)
+{
+    if (_state != state) {
+        _state = state;
+        emit stateChanged(state);
+    }
+}
+
+
+QTcpSocket *AuthHandler::socket() const
+{
+    return _socket;
+}
+
+
+void AuthHandler::setSocket(QTcpSocket *socket)
+{
+    _socket = socket;
+    connect(socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), SIGNAL(socketStateChanged(QAbstractSocket::SocketState)));
+    connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(socketError(QAbstractSocket::SocketError)));
+    connect(socket, SIGNAL(disconnected()), SIGNAL(disconnected()));
+}
+
+
+void AuthHandler::socketError(QAbstractSocket::SocketError error)
+{
+    emit socketError(error, _socket->errorString());
+}
+
+
+void AuthHandler::invalidMessage()
+{
+    qWarning() << Q_FUNC_INFO << "No handler for message!";
+}
+
+
+void AuthHandler::close()
+{
+    if (_socket && _socket->isOpen())
+        _socket->close();
+}
diff --git a/src/common/authhandler.h b/src/common/authhandler.h
new file mode 100644 (file)
index 0000000..6330fa9
--- /dev/null
@@ -0,0 +1,90 @@
+/***************************************************************************
+ *   Copyright (C) 2005-2013 by the Quassel Project                        *
+ *   devel@quassel-irc.org                                                 *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) version 3.                                           *
+ *                                                                         *
+ *   This program is distributed in the hope that it will be useful,       *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this program; if not, write to the                         *
+ *   Free Software Foundation, Inc.,                                       *
+ *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
+ ***************************************************************************/
+
+#ifndef AUTHHANDLER_H
+#define AUTHHANDLER_H
+
+#include <QTcpSocket>
+
+#include "protocol.h"
+
+class Peer;
+
+class AuthHandler : public QObject
+{
+    Q_OBJECT
+
+public:
+    enum State {
+        UnconnectedState,
+        HostLookupState,
+        ConnectingState,
+        ConnectedState,
+        RetryWithLegacyState,
+        AuthenticatingState,
+        AuthenticatedState,
+        ClosingState
+    };
+
+    AuthHandler(QObject *parent = 0);
+
+    State state() const;
+    QTcpSocket *socket() const;
+
+    virtual void handle(const Protocol::RegisterClient &) { invalidMessage(); }
+    virtual void handle(const Protocol::ClientDenied &) { invalidMessage(); }
+    virtual void handle(const Protocol::ClientRegistered &) { invalidMessage(); }
+    virtual void handle(const Protocol::SetupData &) { invalidMessage(); }
+    virtual void handle(const Protocol::SetupFailed &) { invalidMessage(); }
+    virtual void handle(const Protocol::SetupDone &) { invalidMessage(); }
+    virtual void handle(const Protocol::Login &) { invalidMessage(); }
+    virtual void handle(const Protocol::LoginFailed &) { invalidMessage(); }
+    virtual void handle(const Protocol::LoginSuccess &) { invalidMessage(); }
+    virtual void handle(const Protocol::SessionState &) { invalidMessage(); }
+
+    // fallback for unknown types, will trigger an error
+    template<class T>
+    void handle(const T &) { invalidMessage(); }
+
+public slots:
+    void close();
+
+signals:
+    void stateChanged(State state);
+    void disconnected();
+
+    void socketStateChanged(QAbstractSocket::SocketState state);
+    void socketError(QAbstractSocket::SocketError error, const QString &errorString);
+
+protected:
+    void setSocket(QTcpSocket *socket);
+    void setState(State state);
+
+private slots:
+    void socketError(QAbstractSocket::SocketError error);
+
+private:
+    void invalidMessage();
+
+    State _state;
+    QTcpSocket *_socket; // FIXME: should be a QSharedPointer? -> premature disconnect before the peer has taken over
+};
+
+#endif
index 20ab2e9..90bde5b 100644 (file)
@@ -37,7 +37,7 @@ public:
 
 
 InternalPeer::InternalPeer(QObject *parent)
-    : Peer(parent),
+    : Peer(0, parent),
     _proxy(0),
     _peer(0),
     _isOpen(true)
index ada5f09..7585954 100644 (file)
@@ -61,6 +61,18 @@ public:
     void dispatch(const Protocol::InitRequest &msg);
     void dispatch(const Protocol::InitData &msg);
 
+    /* These are not needed for InternalPeer */
+    void dispatch(const Protocol::RegisterClient &) {}
+    void dispatch(const Protocol::ClientDenied &) {}
+    void dispatch(const Protocol::ClientRegistered &) {}
+    void dispatch(const Protocol::SetupData &) {}
+    void dispatch(const Protocol::SetupFailed &) {}
+    void dispatch(const Protocol::SetupDone &) {}
+    void dispatch(const Protocol::Login &) {}
+    void dispatch(const Protocol::LoginFailed &) {}
+    void dispatch(const Protocol::LoginSuccess &) {}
+    void dispatch(const Protocol::SessionState &) {}
+
 public slots:
     void close(const QString &reason = QString());
 
index 62b5f79..671f634 100644 (file)
  *   Free Software Foundation, Inc.,                                       *
  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
  ***************************************************************************/
+
+#include "peer.h"
+
+Peer::Peer(AuthHandler *authHandler, QObject *parent)
+    : QObject(parent)
+    , _authHandler(authHandler)
+{
+
+}
+
+
+AuthHandler *Peer::authHandler() const
+{
+    return _authHandler;
+}
index b2327a3..aa8d0fe 100644 (file)
@@ -22,7 +22,9 @@
 #define PEER_H
 
 #include <QAbstractSocket>
+#include <QPointer>
 
+#include "authhandler.h"
 #include "protocol.h"
 #include "signalproxy.h"
 
@@ -31,13 +33,15 @@ class Peer : public QObject
     Q_OBJECT
 
 public:
-    Peer(QObject *parent = 0) : QObject(parent) {}
+    Peer(AuthHandler *authHandler, QObject *parent = 0);
 
     virtual QString description() const = 0;
 
     virtual SignalProxy *signalProxy() const = 0;
     virtual void setSignalProxy(SignalProxy *proxy) = 0;
 
+    AuthHandler *authHandler() const;
+
     virtual bool isOpen() const = 0;
     virtual bool isSecure() const = 0;
     virtual bool isLocal() const = 0;
@@ -45,27 +49,42 @@ public:
     virtual int lag() const = 0;
 
 public slots:
-    virtual void dispatch(const Protocol::SyncMessage &msg) = 0;
-    virtual void dispatch(const Protocol::RpcCall &msg) = 0;
-    virtual void dispatch(const Protocol::InitRequest &msg) = 0;
-    virtual void dispatch(const Protocol::InitData &msg) = 0;
+    /* Handshake messages */
+    virtual void dispatch(const Protocol::RegisterClient &) = 0;
+    virtual void dispatch(const Protocol::ClientDenied &) = 0;
+    virtual void dispatch(const Protocol::ClientRegistered &) = 0;
+    virtual void dispatch(const Protocol::SetupData &) = 0;
+    virtual void dispatch(const Protocol::SetupFailed &) = 0;
+    virtual void dispatch(const Protocol::SetupDone &) = 0;
+    virtual void dispatch(const Protocol::Login &) = 0;
+    virtual void dispatch(const Protocol::LoginFailed &) = 0;
+    virtual void dispatch(const Protocol::LoginSuccess &) = 0;
+    virtual void dispatch(const Protocol::SessionState &) = 0;
+
+    /* Sigproxy messages */
+    virtual void dispatch(const Protocol::SyncMessage &) = 0;
+    virtual void dispatch(const Protocol::RpcCall &) = 0;
+    virtual void dispatch(const Protocol::InitRequest &) = 0;
+    virtual void dispatch(const Protocol::InitData &) = 0;
 
     virtual void close(const QString &reason = QString()) = 0;
 
 signals:
     void disconnected();
-    void error(QAbstractSocket::SocketError);
     void secureStateChanged(bool secure = true);
     void lagUpdated(int msecs);
 
 protected:
-    template<class T>
+    template<typename T>
     void handle(const T &protoMessage);
+
+private:
+    QPointer<AuthHandler> _authHandler;
 };
 
 
 // Template method needed in the header
-template<class T> inline
+template<typename T> inline
 void Peer::handle(const T &protoMessage)
 {
     switch(protoMessage.handler()) {
@@ -77,6 +96,14 @@ void Peer::handle(const T &protoMessage)
             signalProxy()->handle(this, protoMessage);
             break;
 
+        case Protocol::AuthHandler:
+            if (!authHandler()) {
+                qWarning() << Q_FUNC_INFO << "Cannot handle auth messages without an active AuthHandler!";
+                return;
+            }
+            authHandler()->handle(protoMessage);
+            break;
+
         default:
             qWarning() << Q_FUNC_INFO << "Unknown handler for protocol message!";
             return;
index c36baab..08f5b15 100644 (file)
  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
  ***************************************************************************/
 
+#include <QHostAddress>
 #include <QTcpSocket>
 
 #include "legacypeer.h"
+#include "quassel.h"
+
+/* version.inc is no longer used for this */
+const int protocolVersion = 10;
+const int coreNeedsProtocol = protocolVersion;
+const int clientNeedsProtocol = protocolVersion;
 
 using namespace Protocol;
 
-LegacyPeer::LegacyPeer(QTcpSocket *socket, QObject *parent)
-    : RemotePeer(socket, parent),
+LegacyPeer::LegacyPeer(::AuthHandler *authHandler, QTcpSocket *socket, QObject *parent)
+    : RemotePeer(authHandler, socket, parent),
     _blockSize(0),
     _useCompression(false)
 {
@@ -40,9 +47,13 @@ void LegacyPeer::setSignalProxy(::SignalProxy *proxy)
 {
     RemotePeer::setSignalProxy(proxy);
 
+    // FIXME only in compat mode
+    socket()->flush();
     if (proxy) {
         // enable compression now if requested - the initial handshake is uncompressed in the legacy protocol!
         _useCompression = socket()->property("UseCompression").toBool();
+        if (_useCompression)
+            qDebug() << "Using compression for peer:" << qPrintable(socket()->peerAddress().toString());
     }
 
 }
@@ -54,7 +65,7 @@ void LegacyPeer::socketDataAvailable()
     while (readSocketData(item)) {
         // if no sigproxy is set, we're in handshake mode and let the data be handled elsewhere
         if (!signalProxy())
-            emit dataReceived(item);
+            handleHandshakeMessage(item);
         else
             handlePackedFunc(item);
     }
@@ -149,6 +160,237 @@ void LegacyPeer::writeSocketData(const QVariant &item)
 }
 
 
+/*** Handshake messages ***/
+
+/* These messages are transmitted during handshake phase, which in case of the legacy protocol means they have
+ * a structure different from those being used after the handshake.
+ * Also, the legacy handshake does not fully match the redesigned one, so we'll have to do various mappings here.
+ */
+
+void LegacyPeer::handleHandshakeMessage(const QVariant &msg)
+{
+    QVariantMap m = msg.toMap();
+
+    QString msgType = m["MsgType"].toString();
+    if (msgType.isEmpty()) {
+        emit protocolError(tr("Invalid handshake message!"));
+        return;
+    }
+
+    if (msgType == "ClientInit") {
+        // FIXME only in compat mode
+        int ver = m["ProtocolVersion"].toInt();
+        if (ver < coreNeedsProtocol) {
+            emit protocolVersionMismatch(ver, coreNeedsProtocol);
+            return;
+        }
+
+#ifndef QT_NO_COMPRESS
+        // FIXME only in compat mode
+        if (m["UseCompression"].toBool()) {
+            socket()->setProperty("UseCompression", true);
+        }
+#endif
+        handle(RegisterClient(m["ClientVersion"].toString(), m["UseSsl"].toBool()));
+    }
+
+    else if (msgType == "ClientInitReject") {
+        handle(ClientDenied(m["Error"].toString()));
+    }
+
+    else if (msgType == "ClientInitAck") {
+        // FIXME only in compat mode
+        int ver = m["ProtocolVersion"].toInt();
+        if (ver < clientNeedsProtocol) {
+            emit protocolVersionMismatch(ver, clientNeedsProtocol);
+            return;
+        }
+#ifndef QT_NO_COMPRESS
+        if (m["SupportsCompression"].toBool())
+            socket()->setProperty("UseCompression", true);
+#endif
+
+        handle(ClientRegistered(m["CoreFeatures"].toUInt(), m["Configured"].toBool(), m["StorageBackends"].toList(), m["SupportSsl"].toBool(), QDateTime()));
+    }
+
+    else if (msgType == "CoreSetupData") {
+        QVariantMap map = m["SetupData"].toMap();
+        handle(SetupData(map["AdminUser"].toString(), map["AdminPasswd"].toString(), map["Backend"].toString(), map["ConnectionProperties"].toMap()));
+    }
+
+    else if (msgType == "CoreSetupReject") {
+        handle(SetupFailed(m["Error"].toString()));
+    }
+
+    else if (msgType == "CoreSetupAck") {
+        handle(SetupDone());
+    }
+
+    else if (msgType == "ClientLogin") {
+        handle(Login(m["User"].toString(), m["Password"].toString()));
+    }
+
+    else if (msgType == "ClientLoginReject") {
+        handle(LoginFailed(m["Error"].toString()));
+    }
+
+    else if (msgType == "ClientLoginAck") {
+        handle(LoginSuccess());
+    }
+
+    else if (msgType == "SessionInit") {
+        QVariantMap map = m["SessionState"].toMap();
+        handle(SessionState(map["Identities"].toList(), map["BufferInfos"].toList(), map["NetworkIds"].toList()));
+    }
+
+    else {
+        emit protocolError(tr("Unknown protocol message of type %1").arg(msgType));
+    }
+}
+
+
+void LegacyPeer::dispatch(const RegisterClient &msg) {
+    QVariantMap m;
+    m["MsgType"] = "ClientInit";
+    m["ClientVersion"] = msg.clientVersion;
+    m["ClientDate"] = Quassel::buildInfo().buildDate;
+
+    // FIXME only in compat mode
+    m["ProtocolVersion"] = protocolVersion;
+    m["UseSsl"] = msg.sslSupported;
+#ifndef QT_NO_COMPRESS
+    m["UseCompression"] = true;
+#else
+    m["UseCompression"] = false;
+#endif
+
+    writeSocketData(m);
+}
+
+
+void LegacyPeer::dispatch(const ClientDenied &msg) {
+    QVariantMap m;
+    m["MsgType"] = "ClientInitReject";
+    m["Error"] = msg.errorString;
+
+    writeSocketData(m);
+}
+
+
+void LegacyPeer::dispatch(const ClientRegistered &msg) {
+    QVariantMap m;
+    m["MsgType"] = "ClientInitAck";
+    m["CoreFeatures"] = msg.coreFeatures;
+    m["StorageBackends"] = msg.backendInfo;
+
+    // FIXME only in compat mode
+    m["ProtocolVersion"] = protocolVersion;
+    m["SupportSsl"] = msg.sslSupported;
+    m["SupportsCompression"] = socket()->property("UseCompression"); // this property gets already set in the ClientInit handler
+
+    // This is only used for old v10 clients (pre-0.5)
+    int uptime = msg.coreStartTime.secsTo(QDateTime::currentDateTime().toUTC());
+    int updays = uptime / 86400; uptime %= 86400;
+    int uphours = uptime / 3600; uptime %= 3600;
+    int upmins = uptime / 60;
+    m["CoreInfo"] = tr("<b>Quassel Core Version %1</b><br>"
+                       "Built: %2<br>"
+                       "Up %3d%4h%5m (since %6)").arg(Quassel::buildInfo().fancyVersionString)
+                       .arg(Quassel::buildInfo().buildDate)
+                       .arg(updays).arg(uphours, 2, 10, QChar('0')).arg(upmins, 2, 10, QChar('0')).arg(msg.coreStartTime.toString(Qt::TextDate));
+
+    m["LoginEnabled"] = m["Configured"] = msg.coreConfigured;
+
+    writeSocketData(m);
+    socket()->flush(); // ensure that the write cache is flushed before we switch to ssl
+}
+
+
+void LegacyPeer::dispatch(const SetupData &msg)
+{
+    QVariantMap map;
+    map["AdminUser"] = msg.adminUser;
+    map["AdminPasswd"] = msg.adminPassword;
+    map["Backend"] = msg.backend;
+    map["ConnectionProperties"] = msg.setupData;
+
+    QVariantMap m;
+    m["MsgType"] = "CoreSetupData";
+    m["SetupData"] = map;
+    writeSocketData(m);
+}
+
+
+void LegacyPeer::dispatch(const SetupFailed &msg)
+{
+    QVariantMap m;
+    m["MsgType"] = "CoreSetupReject";
+    m["Error"] = msg.errorString;
+
+    writeSocketData(m);
+}
+
+
+void LegacyPeer::dispatch(const SetupDone &msg)
+{
+    Q_UNUSED(msg)
+
+    QVariantMap m;
+    m["MsgType"] = "CoreSetupAck";
+
+    writeSocketData(m);
+}
+
+
+void LegacyPeer::dispatch(const Login &msg)
+{
+    QVariantMap m;
+    m["MsgType"] = "ClientLogin";
+    m["User"] = msg.user;
+    m["Password"] = msg.password;
+
+    writeSocketData(m);
+}
+
+
+void LegacyPeer::dispatch(const LoginFailed &msg)
+{
+    QVariantMap m;
+    m["MsgType"] = "ClientLoginReject";
+    m["Error"] = msg.errorString;
+
+    writeSocketData(m);
+}
+
+
+void LegacyPeer::dispatch(const LoginSuccess &msg)
+{
+    Q_UNUSED(msg)
+
+    QVariantMap m;
+    m["MsgType"] = "ClientLoginAck";
+
+    writeSocketData(m);
+}
+
+
+void LegacyPeer::dispatch(const SessionState &msg)
+{
+    QVariantMap m;
+    m["MsgType"] = "SessionInit";
+
+    QVariantMap map;
+    map["BufferInfos"] = msg.bufferInfos;
+    map["NetworkIds"] = msg.networkIds;
+    map["Identities"] = msg.identities;
+    m["SessionState"] = map;
+
+    writeSocketData(m);
+}
+
+
+/*** Standard messages ***/
+
 void LegacyPeer::handlePackedFunc(const QVariant &packedFunc)
 {
     QVariantList params(packedFunc.toList());
@@ -158,6 +400,7 @@ void LegacyPeer::handlePackedFunc(const QVariant &packedFunc)
         return;
     }
 
+    // TODO: make sure that this is a valid request type
     RequestType requestType = (RequestType)params.takeFirst().value<int>();
     switch (requestType) {
         case Sync: {
index a3aa178..9234b33 100644 (file)
@@ -41,11 +41,22 @@ public:
         HeartBeatReply
     };
 
-    LegacyPeer(QTcpSocket *socket, QObject *parent = 0);
+    LegacyPeer(AuthHandler *authHandler, QTcpSocket *socket, QObject *parent = 0);
     ~LegacyPeer() {}
 
     void setSignalProxy(SignalProxy *proxy);
 
+    void dispatch(const Protocol::RegisterClient &msg);
+    void dispatch(const Protocol::ClientDenied &msg);
+    void dispatch(const Protocol::ClientRegistered &msg);
+    void dispatch(const Protocol::SetupData &msg);
+    void dispatch(const Protocol::SetupFailed &msg);
+    void dispatch(const Protocol::SetupDone &msg);
+    void dispatch(const Protocol::Login &msg);
+    void dispatch(const Protocol::LoginFailed &msg);
+    void dispatch(const Protocol::LoginSuccess &msg);
+    void dispatch(const Protocol::SessionState &msg);
+
     void dispatch(const Protocol::SyncMessage &msg);
     void dispatch(const Protocol::RpcCall &msg);
     void dispatch(const Protocol::InitRequest &msg);
@@ -57,11 +68,18 @@ public:
     // FIXME: this is only used for the auth phase and should be replaced by something more generic
     void writeSocketData(const QVariant &item);
 
+signals:
+    void protocolError(const QString &errorString);
+
+    // only used in compat mode
+    void protocolVersionMismatch(int actual, int expected);
+
 private slots:
     void socketDataAvailable();
 
 private:
     bool readSocketData(QVariant &item);
+    void handleHandshakeMessage(const QVariant &msg);
     void handlePackedFunc(const QVariant &packedFunc);
     void dispatchPackedFunc(const QVariantList &packedFunc);
 
index d3752f1..fcfc7ee 100644 (file)
@@ -31,8 +31,8 @@
 
 using namespace Protocol;
 
-RemotePeer::RemotePeer(QTcpSocket *socket, QObject *parent)
-    : Peer(parent),
+RemotePeer::RemotePeer(::AuthHandler *authHandler, QTcpSocket *socket, QObject *parent)
+    : Peer(authHandler, parent),
     _socket(socket),
     _signalProxy(0),
     _heartBeatTimer(new QTimer(this)),
@@ -41,7 +41,8 @@ RemotePeer::RemotePeer(QTcpSocket *socket, QObject *parent)
 {
     socket->setParent(this);
     connect(socket, SIGNAL(disconnected()), SIGNAL(disconnected()));
-    connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), SIGNAL(error(QAbstractSocket::SocketError)));
+    connect(socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), SIGNAL(socketStateChanged(QAbstractSocket::SocketState)));
+    connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(onSocketError(QAbstractSocket::SocketError)));
 
 #ifdef HAVE_SSL
     QSslSocket *sslSocket = qobject_cast<QSslSocket *>(socket);
@@ -53,6 +54,12 @@ RemotePeer::RemotePeer(QTcpSocket *socket, QObject *parent)
 }
 
 
+void RemotePeer::onSocketError(QAbstractSocket::SocketError error)
+{
+    emit socketError(error, socket()->errorString());
+}
+
+
 QString RemotePeer::description() const
 {
     if (socket())
@@ -169,9 +176,9 @@ void RemotePeer::handle(const HeartBeatReply &heartBeatReply)
 {
     _heartBeatCount = 0;
 #if QT_VERSION >= 0x040900
-    emit lagUpdated(heartBeatReply.timestamp().msecsTo(QDateTime::currentDateTime().toUTC()) / 2);
+    emit lagUpdated(heartBeatReply.timestamp.msecsTo(QDateTime::currentDateTime().toUTC()) / 2);
 #else
-    emit lagUpdated(heartBeatReply.timestamp().time().msecsTo(QDateTime::currentDateTime().toUTC().time()) / 2);
+    emit lagUpdated(heartBeatReply.timestamp.time().msecsTo(QDateTime::currentDateTime().toUTC().time()) / 2);
 #endif
 }
 
index 6c3e35f..e577a86 100644 (file)
 class QTcpSocket;
 class QTimer;
 
+class AuthHandler;
+
 class RemotePeer : public Peer
 {
     Q_OBJECT
 
 public:
-    RemotePeer(QTcpSocket *socket, QObject *parent = 0);
+    // import the virtuals from the baseclass
+    using Peer::handle;
+    using Peer::dispatch;
+
+    RemotePeer(AuthHandler *authHandler, QTcpSocket *socket, QObject *parent = 0);
     virtual ~RemotePeer() {};
 
     void setSignalProxy(SignalProxy *proxy);
@@ -53,24 +59,17 @@ public:
 
     QTcpSocket *socket() const;
 
-    // this is only used for the auth phase and should be replaced by something more generic
-    virtual void writeSocketData(const QVariant &item) = 0;
-
 public slots:
     void close(const QString &reason = QString());
 
 signals:
-    // this is only used for the auth phase and should be replaced by something more generic
-    void dataReceived(const QVariant &item);
-
     void transferProgress(int current, int max);
+    void socketStateChanged(QAbstractSocket::SocketState socketState);
+    void socketError(QAbstractSocket::SocketError error, const QString &errorString);
 
 protected:
     SignalProxy *signalProxy() const;
 
-    using Peer::handle;
-    using Peer::dispatch;
-
     // These protocol messages get handled internally and won't reach SignalProxy
     void handle(const Protocol::HeartBeat &heartBeat);
     void handle(const Protocol::HeartBeatReply &heartBeatReply);
@@ -80,6 +79,7 @@ protected:
 private slots:
     void sendHeartBeat();
     void changeHeartBeatInterval(int secs);
+    void onSocketError(QAbstractSocket::SocketError error);
 
 private:
     QTcpSocket *_socket;
index 9f31f6f..a8fe30d 100644 (file)
@@ -125,7 +125,7 @@ private:
     void handle(Peer *peer, const Protocol::InitData &initData);
 
     template<class T>
-    void handle(Peer *peer, T) { Q_UNUSED(peer); Q_ASSERT(0); }
+    void handle(Peer *, T) { Q_ASSERT(0); }
 
     bool invokeSlot(QObject *receiver, int methodId, const QVariantList &params, QVariant &returnValue);
     bool invokeSlot(QObject *receiver, int methodId, const QVariantList &params = QVariantList());
index 6df50bf..42dac8f 100644 (file)
@@ -7,6 +7,7 @@ set(SOURCES
     core.cpp
     corealiasmanager.cpp
     coreapplication.cpp
+    coreauthhandler.cpp
     corebacklogmanager.cpp
     corebasichandler.cpp
     corebuffersyncer.cpp
@@ -40,6 +41,7 @@ set(MOC_HDRS
     core.h
     corealiasmanager.h
     coreapplication.h
+    coreauthhandler.h
     corebacklogmanager.h
     corebasichandler.h
     corebuffersyncer.h
index 4413211..b3ec48b 100644 (file)
 #include <QCoreApplication>
 
 #include "core.h"
+#include "coreauthhandler.h"
 #include "coresession.h"
 #include "coresettings.h"
+#include "logger.h"
 #include "internalpeer.h"
+#include "network.h"
 #include "postgresqlstorage.h"
 #include "quassel.h"
 #include "sqlitestorage.h"
-#include "network.h"
-#include "logger.h"
-
 #include "util.h"
 
 #include "protocols/legacy/legacypeer.h"
@@ -84,7 +84,8 @@ void Core::destroy()
 
 
 Core::Core()
-    : _storage(0)
+    : QObject(),
+      _storage(0)
 {
 #ifdef HAVE_UMASK
     umask(S_IRWXG | S_IRWXO);
@@ -178,7 +179,9 @@ Core::Core()
 void Core::init()
 {
     CoreSettings cs;
-    _configured = initStorage(cs.storageSettings().toMap());
+    // legacy
+    QVariantMap dbsettings = cs.storageSettings().toMap();
+    _configured = initStorage(dbsettings.value("Backend").toString(), dbsettings.value("ConnectionProperties").toMap());
 
     if (Quassel::isOptionSet("select-backend")) {
         selectBackend(Quassel::optionValue("select-backend"));
@@ -217,8 +220,9 @@ void Core::init()
 
 Core::~Core()
 {
-    foreach(RemotePeer *peer, clientInfo.keys()) {
-        peer->close(); // disconnect non authed clients
+    // FIXME do we need more cleanup for handlers?
+    foreach(CoreAuthHandler *handler, _connectingClients) {
+        handler->deleteLater(); // disconnect non authed clients
     }
     qDeleteAll(sessions);
     qDeleteAll(_storageBackends);
@@ -270,42 +274,50 @@ void Core::restoreState()
 
 
 /*** Core Setup ***/
-QString Core::setupCoreForInternalUsage()
+
+QString Core::setup(const QString &adminUser, const QString &adminPassword, const QString &backend, const QVariantMap &setupData)
 {
-    Q_ASSERT(!_storageBackends.isEmpty());
-    QVariantMap setupData;
-    qsrand(QDateTime::currentDateTime().toTime_t());
-    int pass = 0;
-    for (int i = 0; i < 10; i++) {
-        pass *= 10;
-        pass += qrand() % 10;
-    }
-    setupData["AdminUser"] = "AdminUser";
-    setupData["AdminPasswd"] = QString::number(pass);
-    setupData["Backend"] = QString("SQLite"); // mono client currently needs sqlite
-    return setupCore(setupData);
+    return instance()->setupCore(adminUser, adminPassword, backend, setupData);
 }
 
 
-QString Core::setupCore(QVariantMap setupData)
+QString Core::setupCore(const QString &adminUser, const QString &adminPassword, const QString &backend, const QVariantMap &setupData)
 {
-    QString user = setupData.take("AdminUser").toString();
-    QString password = setupData.take("AdminPasswd").toString();
-    if (user.isEmpty() || password.isEmpty()) {
+    if (_configured)
+        return tr("Core is already configured! Not configuring again...");
+
+    if (adminUser.isEmpty() || adminPassword.isEmpty()) {
         return tr("Admin user or password not set.");
     }
-    if (_configured || !(_configured = initStorage(setupData, true))) {
+    if (!(_configured = initStorage(backend, setupData, true))) {
         return tr("Could not setup storage!");
     }
-    CoreSettings s;
-    s.setStorageSettings(setupData);
+
+    saveBackendSettings(backend, setupData);
+
     quInfo() << qPrintable(tr("Creating admin user..."));
-    _storage->addUser(user, password);
+    _storage->addUser(adminUser, adminPassword);
     startListening(); // TODO check when we need this
     return QString();
 }
 
 
+QString Core::setupCoreForInternalUsage()
+{
+    Q_ASSERT(!_storageBackends.isEmpty());
+
+    qsrand(QDateTime::currentDateTime().toTime_t());
+    int pass = 0;
+    for (int i = 0; i < 10; i++) {
+        pass *= 10;
+        pass += qrand() % 10;
+    }
+
+    // mono client currently needs sqlite
+    return setupCore("AdminUser", QString::number(pass), "SQLite", QVariantMap());
+}
+
+
 /*** Storage Handling ***/
 void Core::registerStorageBackends()
 {
@@ -346,7 +358,7 @@ void Core::unregisterStorageBackend(Storage *backend)
 
 // old db settings:
 // "Type" => "sqlite"
-bool Core::initStorage(const QString &backend, QVariantMap settings, bool setup)
+bool Core::initStorage(const QString &backend, const QVariantMap &settings, bool setup)
 {
     _storage = 0;
 
@@ -388,12 +400,6 @@ bool Core::initStorage(const QString &backend, QVariantMap settings, bool setup)
 }
 
 
-bool Core::initStorage(QVariantMap dbSettings, bool setup)
-{
-    return initStorage(dbSettings["Backend"].toString(), dbSettings["ConnectionProperties"].toMap(), setup);
-}
-
-
 void Core::syncStorage()
 {
     if (_storage)
@@ -415,6 +421,17 @@ bool Core::createNetwork(UserId user, NetworkInfo &info)
 
 /*** Network Management ***/
 
+bool Core::sslSupported()
+{
+#ifdef HAVE_SSL
+    SslServer *sslServer = qobject_cast<SslServer *>(&instance()->_server);
+    return sslServer && sslServer->isCertValid();
+#else
+    return false;
+#endif
+}
+
+
 bool Core::startListening()
 {
     // in mono mode we only start a local port if a port is specified in the cli call
@@ -517,13 +534,14 @@ void Core::incomingConnection()
     Q_ASSERT(server);
     while (server->hasPendingConnections()) {
         QTcpSocket *socket = server->nextPendingConnection();
-        RemotePeer *peer = new LegacyPeer(socket, this);
 
-        connect(peer, SIGNAL(disconnected()), SLOT(clientDisconnected()));
-        connect(peer, SIGNAL(dataReceived(QVariant)), SLOT(processClientMessage(QVariant)));
-        connect(peer, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(socketError(QAbstractSocket::SocketError)));
+        CoreAuthHandler *handler = new CoreAuthHandler(socket, this);
+        _connectingClients.insert(handler);
+
+        connect(handler, SIGNAL(disconnected()), SLOT(clientDisconnected()));
+        connect(handler, SIGNAL(socketError(QAbstractSocket::SocketError,QString)), SLOT(socketError(QAbstractSocket::SocketError,QString)));
+        connect(handler, SIGNAL(handshakeComplete(RemotePeer*,UserId)), SLOT(setupClientSession(RemotePeer*,UserId)));
 
-        clientInfo.insert(peer, QVariantMap());
         quInfo() << qPrintable(tr("Client connected from"))  << qPrintable(socket->peerAddress().toString());
 
         if (!_configured) {
@@ -533,168 +551,15 @@ void Core::incomingConnection()
 }
 
 
-void Core::processClientMessage(const QVariant &data)
-{
-    RemotePeer *peer = qobject_cast<RemotePeer *>(sender());
-    if (!peer) {
-        qWarning() << Q_FUNC_INFO << "Message not sent by RemoteConnection!";
-        return;
-    }
-
-    QVariantMap msg = data.toMap();
-    if (!msg.contains("MsgType")) {
-        // Client is way too old, does not even use the current init format
-        qWarning() << qPrintable(tr("Antique client trying to connect... refusing."));
-        peer->close();
-        return;
-    }
-
-    // OK, so we have at least an init message format we can understand
-    if (msg["MsgType"] == "ClientInit") {
-        QVariantMap reply;
-
-        // Just version information -- check it!
-        uint ver = msg["ProtocolVersion"].toUInt();
-        if (ver < Quassel::buildInfo().coreNeedsProtocol) {
-            reply["MsgType"] = "ClientInitReject";
-            reply["Error"] = tr("<b>Your Quassel Client is too old!</b><br>"
-                                "This core needs at least client/core protocol version %1.<br>"
-                                "Please consider upgrading your client.").arg(Quassel::buildInfo().coreNeedsProtocol);
-            peer->writeSocketData(reply);
-            qWarning() << qPrintable(tr("Client")) << peer->description() << qPrintable(tr("too old, rejecting."));
-            peer->close();
-            return;
-        }
-
-        reply["ProtocolVersion"] = Quassel::buildInfo().protocolVersion;
-        reply["CoreVersion"] = Quassel::buildInfo().fancyVersionString;
-        reply["CoreDate"] = Quassel::buildInfo().buildDate;
-        reply["CoreStartTime"] = startTime(); // v10 clients don't necessarily parse this, see below
-
-        // FIXME: newer clients no longer use the hardcoded CoreInfo (for now), since it gets the
-        //        time zone wrong. With the next protocol bump (10 -> 11), we should remove this
-        //        or make it properly configurable.
-
-        int uptime = startTime().secsTo(QDateTime::currentDateTime().toUTC());
-        int updays = uptime / 86400; uptime %= 86400;
-        int uphours = uptime / 3600; uptime %= 3600;
-        int upmins = uptime / 60;
-        reply["CoreInfo"] = tr("<b>Quassel Core Version %1</b><br>"
-                               "Built: %2<br>"
-                               "Up %3d%4h%5m (since %6)").arg(Quassel::buildInfo().fancyVersionString)
-                            .arg(Quassel::buildInfo().buildDate)
-                            .arg(updays).arg(uphours, 2, 10, QChar('0')).arg(upmins, 2, 10, QChar('0')).arg(startTime().toString(Qt::TextDate));
-
-        reply["CoreFeatures"] = (int)Quassel::features();
-
-#ifdef HAVE_SSL
-        SslServer *sslServer = qobject_cast<SslServer *>(&_server);
-        QSslSocket *sslSocket = qobject_cast<QSslSocket *>(peer->socket());
-        bool supportSsl = sslServer && sslSocket && sslServer->isCertValid();
-#else
-        bool supportSsl = false;
-#endif
-
-#ifndef QT_NO_COMPRESS
-        bool supportsCompression = true;
-#else
-        bool supportsCompression = false;
-#endif
-
-        reply["SupportSsl"] = supportSsl;
-        reply["SupportsCompression"] = supportsCompression;
-        // switch to ssl/compression after client has been informed about our capabilities (see below)
-
-        reply["LoginEnabled"] = true;
-
-        // check if we are configured, start wizard otherwise
-        if (!_configured) {
-            reply["Configured"] = false;
-            QList<QVariant> backends;
-            foreach(Storage *backend, _storageBackends.values()) {
-                QVariantMap v;
-                v["DisplayName"] = backend->displayName();
-                v["Description"] = backend->description();
-                v["SetupKeys"] = backend->setupKeys();
-                v["SetupDefaults"] = backend->setupDefaults();
-                backends.append(v);
-            }
-            reply["StorageBackends"] = backends;
-            reply["LoginEnabled"] = false;
-        }
-        else {
-            reply["Configured"] = true;
-        }
-        clientInfo[peer] = msg; // store for future reference
-        reply["MsgType"] = "ClientInitAck";
-        peer->writeSocketData(reply);
-        peer->socket()->flush(); // ensure that the write cache is flushed before we switch to ssl
-
-#ifdef HAVE_SSL
-        // after we told the client that we are ssl capable we switch to ssl mode
-        if (supportSsl && msg["UseSsl"].toBool()) {
-            qDebug() << qPrintable(tr("Starting TLS for Client:"))  << peer->description();
-            connect(sslSocket, SIGNAL(sslErrors(const QList<QSslError> &)), SLOT(sslErrors(const QList<QSslError> &)));
-            sslSocket->startServerEncryption();
-        }
-#endif
-
-#ifndef QT_NO_COMPRESS
-        if (supportsCompression && msg["UseCompression"].toBool()) {
-            peer->socket()->setProperty("UseCompression", true);
-            qDebug() << "Using compression for Client:" << qPrintable(peer->socket()->peerAddress().toString());
-        }
-#endif
-    }
-    else {
-        // for the rest, we need an initialized connection
-        if (!clientInfo.contains(peer)) {
-            QVariantMap reply;
-            reply["MsgType"] = "ClientLoginReject";
-            reply["Error"] = tr("<b>Client not initialized!</b><br>You need to send an init message before trying to login.");
-            peer->writeSocketData(reply);
-            qWarning() << qPrintable(tr("Client")) << qPrintable(peer->socket()->peerAddress().toString()) << qPrintable(tr("did not send an init message before trying to login, rejecting."));
-            peer->close(); return;
-        }
-        if (msg["MsgType"] == "CoreSetupData") {
-            QVariantMap reply;
-            QString result = setupCore(msg["SetupData"].toMap());
-            if (!result.isEmpty()) {
-                reply["MsgType"] = "CoreSetupReject";
-                reply["Error"] = result;
-            }
-            else {
-                reply["MsgType"] = "CoreSetupAck";
-            }
-            peer->writeSocketData(reply);
-        }
-        else if (msg["MsgType"] == "ClientLogin") {
-            QVariantMap reply;
-            UserId uid = _storage->validateUser(msg["User"].toString(), msg["Password"].toString());
-            if (uid == 0) {
-                reply["MsgType"] = "ClientLoginReject";
-                reply["Error"] = tr("<b>Invalid username or password!</b><br>The username/password combination you supplied could not be found in the database.");
-                peer->writeSocketData(reply);
-                return;
-            }
-            reply["MsgType"] = "ClientLoginAck";
-            peer->writeSocketData(reply);
-            quInfo() << qPrintable(tr("Client")) << qPrintable(peer->socket()->peerAddress().toString()) << qPrintable(tr("initialized and authenticated successfully as \"%1\" (UserId: %2).").arg(msg["User"].toString()).arg(uid.toInt()));
-            setupClientSession(peer, uid);
-        }
-    }
-}
-
-
 // Potentially called during the initialization phase (before handing the connection off to the session)
 void Core::clientDisconnected()
 {
-    RemotePeer *peer = qobject_cast<RemotePeer *>(sender());
-    Q_ASSERT(peer);
+    CoreAuthHandler *handler = qobject_cast<CoreAuthHandler *>(sender());
+    Q_ASSERT(handler);
 
-    quInfo() << qPrintable(tr("Non-authed client disconnected.")) << qPrintable(peer->socket()->peerAddress().toString());
-    clientInfo.remove(peer);
-    peer->deleteLater();
+    quInfo() << qPrintable(tr("Non-authed client disconnected:")) << qPrintable(handler->socket()->peerAddress().toString());
+    _connectingClients.remove(handler);
+    handler->deleteLater();
 
     // make server listen again if still not configured
     if (!_configured) {
@@ -708,10 +573,13 @@ void Core::clientDisconnected()
 
 void Core::setupClientSession(RemotePeer *peer, UserId uid)
 {
+    CoreAuthHandler *handler = qobject_cast<CoreAuthHandler *>(sender());
+    Q_ASSERT(handler);
+
     // From now on everything is handled by the client session
-    disconnect(peer, 0, this, 0);
-    peer->socket()->flush();
-    clientInfo.remove(peer);
+    disconnect(handler, 0, this, 0);
+    _connectingClients.remove(handler);
+    handler->deleteLater();
 
     // Find or create session for validated user
     SessionThread *session;
@@ -721,8 +589,9 @@ void Core::setupClientSession(RemotePeer *peer, UserId uid)
     else {
         session = createSession(uid);
         if (!session) {
-            qWarning() << qPrintable(tr("Could not initialize session for client:")) << qPrintable(peer->socket()->peerAddress().toString());
+            qWarning() << qPrintable(tr("Could not initialize session for client:")) << qPrintable(peer->description());
             peer->close();
+            peer->deleteLater();
             return;
         }
     }
@@ -747,8 +616,9 @@ void Core::addClientHelper(RemotePeer *peer, UserId uid)
 {
     // Find or create session for validated user
     if (!sessions.contains(uid)) {
-        qWarning() << qPrintable(tr("Could not find a session for client:")) << qPrintable(peer->socket()->peerAddress().toString());
+        qWarning() << qPrintable(tr("Could not find a session for client:")) << qPrintable(peer->description());
         peer->close();
+        peer->deleteLater();
         return;
     }
 
@@ -801,23 +671,24 @@ SessionThread *Core::createSession(UserId uid, bool restore)
 }
 
 
-#ifdef HAVE_SSL
-void Core::sslErrors(const QList<QSslError> &errors)
+void Core::socketError(QAbstractSocket::SocketError err, const QString &errorString)
 {
-    Q_UNUSED(errors);
-    QSslSocket *socket = qobject_cast<QSslSocket *>(sender());
-    if (socket)
-        socket->ignoreSslErrors();
+    qWarning() << QString("Socket error %1: %2").arg(err).arg(errorString);
 }
 
 
-#endif
-
-void Core::socketError(QAbstractSocket::SocketError err)
+QVariantList Core::backendInfo()
 {
-    RemotePeer *peer = qobject_cast<RemotePeer *>(sender());
-    if (peer && err != QAbstractSocket::RemoteHostClosedError)
-        qWarning() << "Core::socketError()" << peer->socket() << err << peer->socket()->errorString();
+    QVariantList backends;
+    foreach(const Storage *backend, instance()->_storageBackends.values()) {
+        QVariantMap v;
+        v["DisplayName"] = backend->displayName();
+        v["Description"] = backend->description();
+        v["SetupKeys"] = backend->setupKeys();
+        v["SetupDefaults"] = backend->setupDefaults();
+        backends.append(v);
+    }
+    return backends;
 }
 
 
index ebe8061..de1ad6e 100644 (file)
@@ -41,8 +41,8 @@
 #include "storage.h"
 #include "types.h"
 
+class CoreAuthHandler;
 class CoreSession;
-class RemotePeer;
 struct NetworkInfo;
 class SessionThread;
 class SignalProxy;
@@ -64,6 +64,16 @@ public:
     /*** Storage access ***/
     // These methods are threadsafe.
 
+    //! Validate user
+    /**
+     * \param userName The user's login name
+     * \param password The user's uncrypted password
+     * \return The user's ID if valid; 0 otherwise
+     */
+    static inline UserId validateUser(const QString &userName, const QString &password) {
+        return instance()->_storage->validateUser(userName, password);
+    }
+
     //! Store a user setting persistently
     /**
      * \param userId       The users Id
@@ -472,7 +482,12 @@ public:
     }
 
 
-    const QDateTime &startTime() const { return _startTime; }
+    static inline QDateTime startTime() { return instance()->_startTime; }
+    static inline bool isConfigured() { return instance()->_configured; }
+    static bool sslSupported();
+    static QVariantList backendInfo();
+
+    static QString setup(const QString &adminUser, const QString &adminPassword, const QString &backend, const QVariantMap &setupData);
 
     static inline QTimer &syncTimer() { return instance()->_storageSyncTimer; }
 
@@ -486,13 +501,14 @@ public slots:
      */
     void syncStorage();
     void setupInternalClientSession(InternalPeer *clientConnection);
+    QString setupCore(const QString &adminUser, const QString &adminPassword, const QString &backend, const QVariantMap &setupData);
 
 signals:
     //! Sent when a BufferInfo is updated in storage.
     void bufferInfoUpdated(UserId user, const BufferInfo &info);
 
-    //! Relay From CoreSession::sessionState(const QVariant &). Used for internal connection only
-    void sessionState(const QVariant &);
+    //! Relay from CoreSession::sessionState(). Used for internal connection only
+    void sessionState(const Protocol::SessionState &sessionState);
 
 protected:
     virtual void customEvent(QEvent *event);
@@ -503,15 +519,10 @@ private slots:
     void incomingConnection();
     void clientDisconnected();
 
-    bool initStorage(const QString &backend, QVariantMap settings, bool setup = false);
-    bool initStorage(QVariantMap dbSettings, bool setup = false);
+    bool initStorage(const QString &backend, const QVariantMap &settings, bool setup = false);
 
-#ifdef HAVE_SSL
-    void sslErrors(const QList<QSslError> &errors);
-#endif
-    void socketError(QAbstractSocket::SocketError);
-
-    void processClientMessage(const QVariant &data);
+    void socketError(QAbstractSocket::SocketError err, const QString &errorString);
+    void setupClientSession(RemotePeer *, UserId);
 
 private:
     Core();
@@ -520,11 +531,9 @@ private:
     static Core *instanceptr;
 
     SessionThread *createSession(UserId userId, bool restoreState = false);
-    void setupClientSession(RemotePeer *peer, UserId uid);
     void addClientHelper(RemotePeer *peer, UserId uid);
     //void processCoreSetup(QTcpSocket *socket, QVariantMap &msg);
     QString setupCoreForInternalUsage();
-    QString setupCore(QVariantMap setupData);
 
     void registerStorageBackends();
     bool registerStorageBackend(Storage *);
@@ -536,6 +545,8 @@ private:
     void saveBackendSettings(const QString &backend, const QVariantMap &settings);
     QVariantMap promptForSettings(const Storage *storage);
 
+private:
+    QSet<CoreAuthHandler *> _connectingClients;
     QHash<UserId, SessionThread *> sessions;
     Storage *_storage;
     QTimer _storageSyncTimer;
@@ -548,8 +559,6 @@ private:
 
     OidentdConfigGenerator *_oidentdConfigGenerator;
 
-    QHash<RemotePeer *, QVariantMap> clientInfo;
-
     QHash<QString, Storage *> _storageBackends;
 
     QDateTime _startTime;
diff --git a/src/core/coreauthhandler.cpp b/src/core/coreauthhandler.cpp
new file mode 100644 (file)
index 0000000..5942676
--- /dev/null
@@ -0,0 +1,152 @@
+/***************************************************************************
+ *   Copyright (C) 2005-2013 by the Quassel Project                        *
+ *   devel@quassel-irc.org                                                 *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) version 3.                                           *
+ *                                                                         *
+ *   This program is distributed in the hope that it will be useful,       *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this program; if not, write to the                         *
+ *   Free Software Foundation, Inc.,                                       *
+ *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
+ ***************************************************************************/
+
+#include "coreauthhandler.h"
+
+#ifdef HAVE_SSL
+#  include <QSslSocket>
+#endif
+
+#include "core.h"
+#include "logger.h"
+
+#include "protocols/legacy/legacypeer.h"
+
+using namespace Protocol;
+
+CoreAuthHandler::CoreAuthHandler(QTcpSocket *socket, QObject *parent)
+    : AuthHandler(parent)
+    , _peer(0)
+    , _clientRegistered(false)
+{
+    setSocket(socket);
+
+    // TODO: protocol detection
+
+    // FIXME: make sure _peer gets deleted
+    // TODO: socket ownership goes to the peer! (-> use shared ptr later...)
+    _peer = new LegacyPeer(this, socket, this);
+    // only in compat mode
+    connect(_peer, SIGNAL(protocolVersionMismatch(int,int)), SLOT(onProtocolVersionMismatch(int,int)));
+}
+
+
+// only in compat mode
+void CoreAuthHandler::onProtocolVersionMismatch(int actual, int expected)
+{
+    qWarning() << qPrintable(tr("Client")) << _peer->description() << qPrintable(tr("too old, rejecting."));
+    QString errorString = tr("<b>Your Quassel Client is too old!</b><br>"
+                             "This core needs at least client/core protocol version %1 (got: %2).<br>"
+                             "Please consider upgrading your client.").arg(expected, actual);
+    _peer->dispatch(ClientDenied(errorString));
+    _peer->close();
+}
+
+
+void CoreAuthHandler::startSsl()
+{
+#ifdef HAVE_SSL
+    QSslSocket *sslSocket = qobject_cast<QSslSocket *>(socket());
+    Q_ASSERT(sslSocket);
+
+    qDebug() << qPrintable(tr("Starting encryption for Client:"))  << _peer->description();
+    connect(sslSocket, SIGNAL(sslErrors(const QList<QSslError> &)), SLOT(onSslErrors()));
+    sslSocket->startServerEncryption();
+#endif
+}
+
+
+#ifdef HAVE_SSL
+void CoreAuthHandler::onSslErrors()
+{
+    QSslSocket *sslSocket = qobject_cast<QSslSocket *>(socket());
+    Q_ASSERT(sslSocket);
+    sslSocket->ignoreSslErrors();
+}
+#endif
+
+
+bool CoreAuthHandler::checkClientRegistered()
+{
+    if (!_clientRegistered) {
+        qWarning() << qPrintable(tr("Client")) << qPrintable(socket()->peerAddress().toString()) << qPrintable(tr("did not send an init message before trying to login, rejecting."));
+        _peer->dispatch(ClientDenied(tr("<b>Client not initialized!</b><br>You need to send an init message before trying to login.")));
+        _peer->close();
+        return false;
+    }
+    return true;
+}
+
+
+void CoreAuthHandler::handle(const RegisterClient &msg)
+{
+    // TODO: only in compat mode
+    bool useSsl = false;
+#ifdef HAVE_SSL
+    if (Core::sslSupported() && msg.sslSupported)
+        useSsl = true;
+#endif
+    QVariantList backends;
+    bool configured = Core::isConfigured();
+    if (!configured)
+        backends = Core::backendInfo();
+
+    _peer->dispatch(ClientRegistered(Quassel::features(), configured, backends, useSsl, Core::instance()->startTime()));
+    // TODO: only in compat mode
+    if (useSsl)
+        startSsl();
+
+    _clientRegistered = true;
+}
+
+
+void CoreAuthHandler::handle(const SetupData &msg)
+{
+    if (!checkClientRegistered())
+        return;
+
+    QString result = Core::setup(msg.adminUser, msg.adminPassword, msg.backend, msg.setupData);
+    if (!result.isEmpty())
+        _peer->dispatch(SetupFailed(result));
+    else
+        _peer->dispatch(SetupDone());
+}
+
+
+void CoreAuthHandler::handle(const Login &msg)
+{
+    if (!checkClientRegistered())
+        return;
+
+    UserId uid = Core::validateUser(msg.user, msg.password);
+    if (uid == 0) {
+        _peer->dispatch(LoginFailed(tr("<b>Invalid username or password!</b><br>The username/password combination you supplied could not be found in the database.")));
+        return;
+    }
+    _peer->dispatch(LoginSuccess());
+
+    quInfo() << qPrintable(tr("Client %1 initialized and authenticated successfully as \"%2\" (UserId: %3).").arg(socket()->peerAddress().toString(), msg.user, QString::number(uid.toInt())));
+
+    disconnect(socket(), 0, this, 0);
+    disconnect(_peer, 0, this, 0);
+    _peer->setParent(0); // Core needs to take care of this one now!
+
+    emit handshakeComplete(_peer, uid);
+}
diff --git a/src/core/coreauthhandler.h b/src/core/coreauthhandler.h
new file mode 100644 (file)
index 0000000..85580c9
--- /dev/null
@@ -0,0 +1,62 @@
+/***************************************************************************
+ *   Copyright (C) 2005-2013 by the Quassel Project                        *
+ *   devel@quassel-irc.org                                                 *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) version 3.                                           *
+ *                                                                         *
+ *   This program is distributed in the hope that it will be useful,       *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this program; if not, write to the                         *
+ *   Free Software Foundation, Inc.,                                       *
+ *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
+ ***************************************************************************/
+
+#ifndef COREAUTHHANDLER_H
+#define COREAUTHHANDLER_H
+
+#include "authhandler.h"
+#include "remotepeer.h"
+#include "types.h"
+
+class CoreAuthHandler : public AuthHandler
+{
+    Q_OBJECT
+
+public:
+    CoreAuthHandler(QTcpSocket *socket, QObject *parent = 0);
+
+signals:
+    void handshakeComplete(RemotePeer *peer, UserId uid);
+
+private:
+    using AuthHandler::handle;
+
+    void handle(const Protocol::RegisterClient &msg);
+    void handle(const Protocol::SetupData &msg);
+    void handle(const Protocol::Login &msg);
+
+    bool checkClientRegistered();
+
+private slots:
+    void startSsl();
+#ifdef HAVE_SSL
+    void onSslErrors();
+#endif
+
+    // only in compat mode
+    void onProtocolVersionMismatch(int actual, int expected);
+
+private:
+    RemotePeer *_peer;
+
+    bool _clientRegistered;
+};
+
+#endif
index d7a330a..ce5026a 100644 (file)
@@ -208,10 +208,7 @@ void CoreSession::restoreSessionState()
 
 void CoreSession::addClient(RemotePeer *peer)
 {
-    QVariantMap reply;
-    reply["MsgType"] = "SessionInit";
-    reply["SessionState"] = sessionState();
-    peer->writeSocketData(reply);
+    peer->dispatch(sessionState());
     signalProxy()->addPeer(peer);
 }
 
@@ -375,34 +372,20 @@ void CoreSession::processMessages()
 }
 
 
-QVariant CoreSession::sessionState()
+Protocol::SessionState CoreSession::sessionState() const
 {
-    QVariantMap v;
+    QVariantList bufferInfos;
+    QVariantList networkIds;
+    QVariantList identities;
 
-    v["CoreFeatures"] = (int)Quassel::features();
+    foreach(const BufferInfo &id, buffers())
+        bufferInfos << QVariant::fromValue(id);
+    foreach(const NetworkId &id, _networks.keys())
+        networkIds << QVariant::fromValue(id);
+    foreach(const Identity *i, _identities.values())
+        identities << QVariant::fromValue(*i);
 
-    QVariantList bufs;
-    foreach(BufferInfo id, buffers()) bufs << qVariantFromValue(id);
-    v["BufferInfos"] = bufs;
-    QVariantList networkids;
-    foreach(NetworkId id, _networks.keys()) networkids << qVariantFromValue(id);
-    v["NetworkIds"] = networkids;
-
-    quint32 ircusercount = 0;
-    quint32 ircchannelcount = 0;
-    foreach(Network *net, _networks.values()) {
-        ircusercount += net->ircUserCount();
-        ircchannelcount += net->ircChannelCount();
-    }
-    v["IrcUserCount"] = ircusercount;
-    v["IrcChannelCount"] = ircchannelcount;
-
-    QList<QVariant> idlist;
-    foreach(Identity *i, _identities.values()) idlist << qVariantFromValue(*i);
-    v["Identities"] = idlist;
-
-    //v["Payload"] = QByteArray(100000000, 'a');  // for testing purposes
-    return v;
+    return Protocol::SessionState(identities, bufferInfos, networkIds);
 }
 
 
index 019d5b3..59d6d51 100644 (file)
@@ -27,6 +27,7 @@
 #include "corecoreinfo.h"
 #include "corealiasmanager.h"
 #include "coreignorelistmanager.h"
+#include "protocol.h"
 #include "message.h"
 #include "storage.h"
 
@@ -68,7 +69,7 @@ public:
     inline CoreNetworkConfig *networkConfig() const { return _networkConfig; }
     NetworkConnection *networkConnection(NetworkId) const;
 
-    QVariant sessionState();
+    Protocol::SessionState sessionState() const;
 
     inline SignalProxy *signalProxy() const { return _signalProxy; }
 
@@ -130,7 +131,7 @@ public slots:
 
 signals:
     void initialized();
-    void sessionState(const QVariant &);
+    void sessionState(const Protocol::SessionState &sessionState);
 
     //void msgFromGui(uint netid, QString buf, QString message);
     void displayMsg(Message message);
index e886db5..a450ef6 100644 (file)
@@ -123,7 +123,7 @@ void SessionThread::run()
     _session = new CoreSession(user(), _restoreState);
     connect(this, SIGNAL(addRemoteClient(RemotePeer*)), _session, SLOT(addClient(RemotePeer*)));
     connect(this, SIGNAL(addInternalClient(InternalPeer*)), _session, SLOT(addClient(InternalPeer*)));
-    connect(_session, SIGNAL(sessionState(QVariant)), Core::instance(), SIGNAL(sessionState(QVariant)));
+    connect(_session, SIGNAL(sessionState(Protocol::SessionState)), Core::instance(), SIGNAL(sessionState(Protocol::SessionState)));
     emit initialized();
     exec();
     delete _session;
index ee19c20..a9fe750 100644 (file)
@@ -122,6 +122,7 @@ bool SslServer::setCertificate(const QString &path)
     }
     if (!_cert.isValid()) {
         quWarning() << "SslServer: Invalid certificate (most likely expired)";
+        // We allow the core to offer SSL anyway, so no "return false" here. Client will warn about the cert being invalid.
     }
     if (_key.isNull()) {
         quWarning() << "SslServer:" << qPrintable(path) << "contains no key data";
index 9fbc074..ca21a2a 100644 (file)
@@ -89,12 +89,7 @@ void CoreConfigWizard::prepareCoreSetup(const QString &backend, const QVariantMa
     foreach(int idx, visitedPages())
     page(idx)->setEnabled(false);
 
-    QVariantMap foo;
-    foo["AdminUser"] = field("adminUser.user").toString();
-    foo["AdminPasswd"] = field("adminUser.password").toString();
-    foo["Backend"] = backend;
-    foo["ConnectionProperties"] = properties;
-    coreConnection()->doCoreSetup(foo);
+    coreConnection()->setupCore(Protocol::SetupData(field("adminUser.user").toString(), field("adminUser.password").toString(), backend, properties));
 }
 
 
index 69ca448..191a32a 100644 (file)
@@ -71,5 +71,5 @@ void MonolithicApplication::startInternalCore()
     Core *core = Core::instance();
     CoreConnection *connection = Client::coreConnection();
     connect(connection, SIGNAL(connectToInternalCore(InternalPeer*)), core, SLOT(setupInternalClientSession(InternalPeer*)));
-    connect(core, SIGNAL(sessionState(QVariant)), connection, SLOT(internalSessionStateReceived(QVariant)));
+    connect(core, SIGNAL(sessionState(Protocol::SessionState)), connection, SLOT(internalSessionStateReceived(Protocol::SessionState)));
 }