Implement protocol detection
authorManuel Nickschas <sputnick@quassel-irc.org>
Thu, 23 Jan 2014 19:36:43 +0000 (20:36 +0100)
committerManuel Nickschas <sputnick@quassel-irc.org>
Thu, 23 Jan 2014 19:36:43 +0000 (20:36 +0100)
This introduces a new initial handshake for negotiating the supported
protocols and connection features on both sides. It is completely
backwards compatible with older releases, in which case we go into
fallback mode.

Arguably, we could've used a nice, verbose, text-based handshake, but
that would've introduced questions around string formats, parsing and so
on. Also I felt like doing some bitbanging, so now the probing only
exchanges a few bytes which are described in the wiki [1]. If we ever
plan to use a more verbose or different format, changing the magic that
starts the whole shebang will be sufficient to indicate a new format.

Immediately after probing, if both core and client support the new format
(and a protocol other than the legacy one),we'll enable SSL and compression [2]
as appropriate, instead of doing it somewhat later in the middle of the legacy
handshake.

To retain compatibility, the magic number sent by the client is designed such
that older cores will immediately close the connection; we'll then reconnect
in compatibility mode. The other way round, if an older client connects to
a new core, we'll figure out that there's no magic being sent and switch to
legacy mode as well.

The unchanged legacy protocol is also the last resort even if both ends speak
the new handshake as long as we don't have an alternative to offer. So for now,
we'll probe for protocol support, get back the legacy protocol as only choice,
and use that as before. This also disables early SSL and compression mentioned above.

This means that 3rd party clients could already implement the handshake in
preparation for the future without changing anything else. Note that they should
also implement the detection of older cores in order to stay compatible with them -
simply detect a disconnect after the first few bytes sent and reconnect again
in compat mode.

[1] http://bugs.quassel-irc.org/projects/quassel-irc/wiki/Doc_quassel_protocols
[2] Not implemented yet.

src/client/clientauthhandler.cpp
src/client/clientauthhandler.h
src/common/protocol.h
src/core/coreauthhandler.cpp
src/core/coreauthhandler.h

index 6661119..415a7dd 100644 (file)
@@ -22,6 +22,8 @@
 
 // TODO: support system application proxy (new in Qt 4.6)
 
+#include <QtEndian>
+
 #ifdef HAVE_SSL
     #include <QSslSocket>
 #else
@@ -30,6 +32,7 @@
 
 #include "client.h"
 #include "clientsettings.h"
+#include "peerfactory.h"
 
 #include "protocols/legacy/legacypeer.h"
 
@@ -38,7 +41,10 @@ using namespace Protocol;
 ClientAuthHandler::ClientAuthHandler(CoreAccount account, QObject *parent)
     : AuthHandler(parent),
     _peer(0),
-    _account(account)
+    _account(account),
+    _probing(false),
+    _legacy(false),
+    _connectionFeatures(0)
 {
 
 }
@@ -76,9 +82,8 @@ void ClientAuthHandler::connectToCore()
 #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(readyRead()), SLOT(onReadyRead()));
     connect(socket, SIGNAL(connected()), SLOT(onSocketConnected()));
 
     emit statusMessage(tr("Connecting to %1...").arg(_account.accountName()));
@@ -86,29 +91,33 @@ void ClientAuthHandler::connectToCore()
 }
 
 
-// TODO: handle protocol detection
 void ClientAuthHandler::onSocketStateChanged(QAbstractSocket::SocketState socketState)
 {
     QString text;
 
     switch(socketState) {
-        case QAbstractSocket::UnconnectedState:
-            text = tr("Disconnected");
-            // Ensure the disconnected() signal is sent even if we haven't reached the Connected state yet.
-            // The baseclass implementation will make sure to only send the signal once.
-            onSocketDisconnected();
-            break;
         case QAbstractSocket::HostLookupState:
-            text = tr("Looking up %1...").arg(_account.hostName());
+            if (!_legacy)
+                text = tr("Looking up %1...").arg(_account.hostName());
             break;
         case QAbstractSocket::ConnectingState:
-            text = tr("Connecting to %1...").arg(_account.hostName());
+            if (!_legacy)
+                text = tr("Connecting to %1...").arg(_account.hostName());
             break;
         case QAbstractSocket::ConnectedState:
             text = tr("Connected to %1").arg(_account.hostName());
             break;
         case QAbstractSocket::ClosingState:
-            text = tr("Disconnecting from %1...").arg(_account.hostName());
+            if (!_probing)
+                text = tr("Disconnecting from %1...").arg(_account.hostName());
+            break;
+        case QAbstractSocket::UnconnectedState:
+            if (!_probing) {
+                text = tr("Disconnected");
+                // Ensure the disconnected() signal is sent even if we haven't reached the Connected state yet.
+                // The baseclass implementation will make sure to only send the signal once.
+                onSocketDisconnected();
+            }
             break;
         default:
             break;
@@ -119,18 +128,35 @@ void ClientAuthHandler::onSocketStateChanged(QAbstractSocket::SocketState socket
     }
 }
 
-// TODO: handle protocol detection
-/*
 void ClientAuthHandler::onSocketError(QAbstractSocket::SocketError error)
 {
-    emit socketError(error, socket()->errorString());
+    if (_probing && error == QAbstractSocket::RemoteHostClosedError) {
+        _legacy = true;
+        return;
+    }
+
+    _probing = false; // all other errors are unrelated to probing and should be handled
+    AuthHandler::onSocketError(error);
 }
-*/
 
-void ClientAuthHandler::onSocketConnected()
+
+void ClientAuthHandler::onSocketDisconnected()
 {
-    // TODO: protocol detection
+    if (_probing && _legacy) {
+        // Remote host has closed the connection while probing
+        _probing = false;
+        disconnect(socket(), SIGNAL(readyRead()), this, SLOT(onReadyRead()));
+        emit statusMessage(tr("Reconnecting in compatibility mode..."));
+        socket()->connectToHost(_account.hostName(), _account.port());
+        return;
+    }
+
+    AuthHandler::onSocketDisconnected();
+}
 
+
+void ClientAuthHandler::onSocketConnected()
+{
     if (_peer) {
         qWarning() << Q_FUNC_INFO << "Peer already exists!";
         return;
@@ -138,21 +164,75 @@ void ClientAuthHandler::onSocketConnected()
 
     socket()->setSocketOption(QAbstractSocket::KeepAliveOption, true);
 
-    _peer = new LegacyPeer(this, socket(), this);
+    if (!_legacy) {
+        // First connection attempt, try probing for a capable core
+        _probing = true;
 
-    connect(_peer, SIGNAL(transferProgress(int,int)), SIGNAL(transferProgress(int,int)));
-
-    // compat only
-    connect(_peer, SIGNAL(protocolVersionMismatch(int,int)), SLOT(onProtocolVersionMismatch(int,int)));
+        QDataStream stream(socket()); // stream handles the endianness for us
 
-    emit statusMessage(tr("Synchronizing to core..."));
-
-    bool useSsl = false;
+        quint32 magic = Protocol::magic;
 #ifdef HAVE_SSL
-    useSsl = _account.useSsl();
+        if (_account.useSsl())
+            magic |= Protocol::Encryption;
 #endif
+        //magic |= Protocol::Compression; // not implemented yet
 
-    _peer->dispatch(RegisterClient(Quassel::buildInfo().fancyVersionString, useSsl));
+        stream << magic;
+
+        // here goes the list of protocols we support, in order of preference
+        stream << ((quint32)Protocol::LegacyProtocol | 0x80000000); // end list
+
+        socket()->flush(); // make sure the probing data is sent immediately
+        return;
+    }
+
+    // If we arrive here, it's the second connection attempt, meaning probing was not successful -> enable legacy support
+
+    qDebug() << "Legacy core detected, switching to compatibility mode";
+
+    RemotePeer *peer = new LegacyPeer(this, socket(), this);
+    // Only needed for the legacy peer, as all others check the protocol version before instantiation
+    connect(peer, SIGNAL(protocolVersionMismatch(int,int)), SLOT(onProtocolVersionMismatch(int,int)));
+
+    setPeer(peer);
+}
+
+
+void ClientAuthHandler::onReadyRead()
+{
+    if (socket()->bytesAvailable() < 4)
+        return;
+
+    if (!_probing)
+        return; // make sure to not read more data than needed
+
+    _probing = false;
+    disconnect(socket(), SIGNAL(readyRead()), this, SLOT(onReadyRead()));
+
+    quint32 reply;
+    socket()->read((char *)&reply, 4);
+    reply = qFromBigEndian<quint32>(reply);
+
+    Protocol::Type type = static_cast<Protocol::Type>(reply & 0xff);
+    quint16 protoFeatures = static_cast<quint16>(reply>>8 & 0xffff);
+    _connectionFeatures = static_cast<quint8>(reply>>24);
+
+    RemotePeer *peer = PeerFactory::createPeer(PeerFactory::ProtoDescriptor(type, protoFeatures), this, socket(), this);
+    if (!peer) {
+        qWarning() << "No valid protocol supported for this core!";
+        emit errorPopup(tr("<b>Incompatible Quassel Core!</b><br>"
+                           "None of the protocols this client speaks are supported by the core you are trying to connect to."));
+
+        requestDisconnect(tr("Core speaks none of the protocols we support"));
+        return;
+    }
+
+    if (peer->protocol() == Protocol::LegacyProtocol) {
+        connect(peer, SIGNAL(protocolVersionMismatch(int,int)), SLOT(onProtocolVersionMismatch(int,int)));
+        _legacy = true;
+    }
+
+    setPeer(peer);
 }
 
 
@@ -164,104 +244,55 @@ void ClientAuthHandler::onProtocolVersionMismatch(int actual, int expected)
 }
 
 
-void ClientAuthHandler::handle(const ClientDenied &msg)
+void ClientAuthHandler::setPeer(RemotePeer *peer)
 {
-    emit errorPopup(msg.errorString);
-    requestDisconnect(tr("The core refused connection from this client"));
+    _peer = peer;
+    connect(_peer, SIGNAL(transferProgress(int,int)), SIGNAL(transferProgress(int,int)));
+
+    // The legacy protocol enables SSL later, after registration
+    if (!_account.useSsl() || _legacy)
+        startRegistration();
+    // otherwise, do it now
+    else
+        checkAndEnableSsl(_connectionFeatures & Protocol::Encryption);
 }
 
 
-void ClientAuthHandler::handle(const ClientRegistered &msg)
+void ClientAuthHandler::startRegistration()
 {
-    _coreConfigured = msg.coreConfigured;
-    _backendInfo = msg.backendInfo;
-
-    Client::setCoreFeatures(static_cast<Quassel::Features>(msg.coreFeatures));
+    emit statusMessage(tr("Synchronizing to core..."));
 
+    // useSsl will be ignored by non-legacy peers
+    bool useSsl = false;
 #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;
-    }
+    useSsl = _account.useSsl();
 #endif
-    // if we use SSL we wait for the next step until every SSL warning has been cleared
-    onConnectionReady();
-}
 
+    _peer->dispatch(RegisterClient(Quassel::buildInfo().fancyVersionString, useSsl));
+}
 
-#ifdef HAVE_SSL
 
-void ClientAuthHandler::onSslSocketEncrypted()
+void ClientAuthHandler::handle(const ClientDenied &msg)
 {
-    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();
+    emit errorPopup(msg.errorString);
+    requestDisconnect(tr("The core refused connection from this client"));
 }
 
 
-void ClientAuthHandler::onSslErrors()
+void ClientAuthHandler::handle(const ClientRegistered &msg)
 {
-    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;
-        }
+    _coreConfigured = msg.coreConfigured;
+    _backendInfo = msg.backendInfo;
 
-        if (permanently)
-            s.setAccountValue("SslCert", socket->peerCertificate().digest());
-        else
-            s.setAccountValue("SslCert", QString());
-    }
+    Client::setCoreFeatures(static_cast<Quassel::Features>(msg.coreFeatures));
 
-    socket->ignoreSslErrors();
+    // The legacy protocol enables SSL at this point
+    if(_legacy && _account.useSsl())
+        checkAndEnableSsl(msg.sslSupported);
+    else
+        onConnectionReady();
 }
 
-#endif /* HAVE_SSL */
-
 
 void ClientAuthHandler::onConnectionReady()
 {
@@ -344,3 +375,95 @@ void ClientAuthHandler::handle(const SessionState &msg)
     _peer->setParent(0);
     emit handshakeComplete(_peer, msg);
 }
+
+
+/*** SSL Stuff ***/
+
+void ClientAuthHandler::checkAndEnableSsl(bool coreSupportsSsl)
+{
+#ifndef HAVE_SSL
+    Q_UNUSED(coreSupportsSsl);
+#else
+    CoreAccountSettings s;
+    if (coreSupportsSsl && _account.useSsl()) {
+        // 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());
+        }
+        if (_legacy)
+            onConnectionReady();
+        else
+            startRegistration();
+    }
+#endif
+}
+
+#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);
+
+    if (_legacy)
+        onConnectionReady();
+    else
+        startRegistration();
+}
+
+
+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 */
index d80051a..6683d1d 100644 (file)
@@ -78,10 +78,17 @@ private:
     void handle(const Protocol::LoginSuccess &msg);
     void handle(const Protocol::SessionState &msg);
 
+    void setPeer(RemotePeer *peer);
+    void checkAndEnableSsl(bool coreSupportsSsl);
+    void startRegistration();
+
 private slots:
     void onSocketConnected();
     void onSocketStateChanged(QAbstractSocket::SocketState state);
-    //void onSocketError(QAbstractSocket::SocketError);
+    void onSocketError(QAbstractSocket::SocketError);
+    void onSocketDisconnected();
+    void onReadyRead();
+
 #ifdef HAVE_SSL
     void onSslSocketEncrypted();
     void onSslErrors();
@@ -96,7 +103,9 @@ private:
     bool _coreConfigured;
     QVariantList _backendInfo;
     CoreAccount _account;
-
+    bool _probing;
+    bool _legacy;
+    quint8 _connectionFeatures;
 };
 
 #endif
index d1154db..d7c1b1d 100644 (file)
 
 namespace Protocol {
 
+const quint32 magic = 0x42b33f00;
+
 enum Type {
     LegacyProtocol = 1
 };
 
+
+enum Feature {
+    Encryption = 0x01,
+    Compression = 0x02
+};
+
+
 enum Handler {
     SignalProxy,
     AuthHandler
 };
 
-/*** Handshake, handled by AuthHandler ***/
 
+/*** Handshake, handled by AuthHandler ***/
 
 struct HandshakeMessage {
     inline Handler handler() const { return AuthHandler; }
index c27afde..e277fcb 100644 (file)
 using namespace Protocol;
 
 CoreAuthHandler::CoreAuthHandler(QTcpSocket *socket, QObject *parent)
-    : AuthHandler(parent)
-    , _peer(0)
-    , _clientRegistered(false)
+    : AuthHandler(parent),
+    _peer(0),
+    _magicReceived(false),
+    _legacy(false),
+    _clientRegistered(false),
+    _connectionFeatures(0)
 {
     setSocket(socket);
+    connect(socket, SIGNAL(readyRead()), SLOT(onReadyRead()));
 
-    // TODO: protocol detection
+    // TODO: Timeout for the handshake phase
 
-    // 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)));
 }
 
 
+void CoreAuthHandler::onReadyRead()
+{
+    if (socket()->bytesAvailable() < 4)
+        return;
+
+    // once we have selected a peer, we certainly don't want to read more data!
+    if (_peer)
+        return;
+
+    if (!_magicReceived) {
+        quint32 magic;
+        socket()->peek((char*)&magic, 4);
+        magic = qFromBigEndian<quint32>(magic);
+
+        if ((magic & 0xffffff00) != Protocol::magic) {
+            // no magic, assume legacy protocol
+            qDebug() << "Legacy client detected, switching to compatibility mode";
+            _legacy = true;
+            RemotePeer *peer = new LegacyPeer(this, socket(), this);
+            connect(peer, SIGNAL(protocolVersionMismatch(int,int)), SLOT(onProtocolVersionMismatch(int,int)));
+            setPeer(peer);
+            return;
+        }
+
+        _magicReceived = true;
+        quint8 features = magic & 0xff;
+        // figure out which connection features we'll use based on the client's support
+        if (Core::sslSupported() && (features & Protocol::Encryption))
+            _connectionFeatures |= Protocol::Encryption;
+        if (features & Protocol::Compression)
+            _connectionFeatures |= Protocol::Compression;
+
+        socket()->read((char*)&magic, 4); // read the 4 bytes we've just peeked at
+    }
+
+    // read the list of protocols supported by the client
+    while (socket()->bytesAvailable() >= 4) {
+        quint32 data;
+        socket()->read((char*)&data, 4);
+        data = qFromBigEndian<quint32>(data);
+
+        Protocol::Type type = static_cast<Protocol::Type>(data & 0xff);
+        quint16 protoFeatures = static_cast<quint16>(data>>8 & 0xffff);
+        _supportedProtos.append(PeerFactory::ProtoDescriptor(type, protoFeatures));
+
+        if (data >= 0x80000000) { // last protocol
+            RemotePeer *peer = PeerFactory::createPeer(_supportedProtos, this, socket(), this);
+            if (peer->protocol() == Protocol::LegacyProtocol) {
+                _legacy = true;
+                connect(peer, SIGNAL(protocolVersionMismatch(int,int)), SLOT(onProtocolVersionMismatch(int,int)));
+            }
+            setPeer(peer);
+
+            // inform the client
+            quint32 reply = peer->protocol() | peer->enabledFeatures()<<8 | _connectionFeatures<<24;
+            reply = qToBigEndian<quint32>(reply);
+            socket()->write((char*)&reply, 4);
+            socket()->flush();
+
+            if (!_legacy && (_connectionFeatures & Protocol::Encryption))
+                startSsl(); // legacy peer enables it later
+            return;
+        }
+    }
+}
+
+
+void CoreAuthHandler::setPeer(RemotePeer *peer)
+{
+    _peer = peer;
+    disconnect(socket(), SIGNAL(readyRead()), this, SLOT(onReadyRead()));
+}
+
 // only in compat mode
 void CoreAuthHandler::onProtocolVersionMismatch(int actual, int expected)
 {
@@ -60,35 +132,11 @@ void CoreAuthHandler::onProtocolVersionMismatch(int actual, int expected)
 }
 
 
-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->flush(); // ensure that the write cache is flushed before we switch to ssl (bug 682)
-    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.")));
+        qWarning() << qPrintable(tr("Client")) << qPrintable(socket()->peerAddress().toString()) << qPrintable(tr("did not send a registration message before trying to login, rejecting."));
+        _peer->dispatch(ClientDenied(tr("<b>Client not initialized!</b><br>You need to send a registration message before trying to login.")));
         _peer->close();
         return false;
     }
@@ -98,25 +146,27 @@ bool CoreAuthHandler::checkClientRegistered()
 
 void CoreAuthHandler::handle(const RegisterClient &msg)
 {
-    // TODO: only in compat mode
-    bool useSsl = false;
-#ifdef HAVE_SSL
-    if (Quassel::isOptionSet("require-ssl") && !msg.sslSupported) {
+    bool useSsl;
+    if (_legacy)
+        useSsl = Core::sslSupported() && msg.sslSupported;
+    else
+        useSsl = _connectionFeatures & Protocol::Encryption;
+
+    if (Quassel::isOptionSet("require-ssl") && !useSsl) {
         _peer->dispatch(ClientDenied(tr("<b>SSL is required!</b><br>You need to use SSL in order to connect to this core.")));
         _peer->close();
         return;
     }
-    if (Core::sslSupported() && msg.sslSupported)
-        useSsl = true;
-#endif
+
     QVariantList backends;
     bool configured = Core::isConfigured();
     if (!configured)
         backends = Core::backendInfo();
 
+    // useSsl and startTime are only used for the legacy protocol
     _peer->dispatch(ClientRegistered(Quassel::features(), configured, backends, useSsl, Core::instance()->startTime()));
-    // TODO: only in compat mode
-    if (useSsl)
+
+    if (_legacy && useSsl)
         startSsl();
 
     _clientRegistered = true;
@@ -157,3 +207,30 @@ void CoreAuthHandler::handle(const Login &msg)
     socket()->flush(); // Make sure all data is sent before handing over the peer (and socket) to the session thread (bug 682)
     emit handshakeComplete(_peer, uid);
 }
+
+
+/*** SSL Stuff ***/
+
+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->flush(); // ensure that the write cache is flushed before we switch to ssl (bug 682)
+    sslSocket->startServerEncryption();
+    #endif /* HAVE_SSL */
+}
+
+
+#ifdef HAVE_SSL
+void CoreAuthHandler::onSslErrors()
+{
+    QSslSocket *sslSocket = qobject_cast<QSslSocket *>(socket());
+    Q_ASSERT(sslSocket);
+    sslSocket->ignoreSslErrors();
+}
+#endif
+
index 3f7ddf3..bb0b28b 100644 (file)
@@ -22,6 +22,7 @@
 #define COREAUTHHANDLER_H
 
 #include "authhandler.h"
+#include "peerfactory.h"
 #include "remotepeer.h"
 #include "types.h"
 
@@ -42,21 +43,29 @@ private:
     void handle(const Protocol::SetupData &msg);
     void handle(const Protocol::Login &msg);
 
+    void setPeer(RemotePeer *peer);
+    void startSsl();
+
     bool checkClientRegistered();
 
 private slots:
-    void startSsl();
+    void onReadyRead();
+
 #ifdef HAVE_SSL
     void onSslErrors();
 #endif
 
-    // only in compat mode
+    // only in legacy mode
     void onProtocolVersionMismatch(int actual, int expected);
 
 private:
     RemotePeer *_peer;
 
+    bool _magicReceived;
+    bool _legacy;
     bool _clientRegistered;
+    quint8 _connectionFeatures;
+    QVector<PeerFactory::ProtoDescriptor> _supportedProtos;
 };
 
 #endif