diff options
Diffstat (limited to 'src/network/ssl/qsslserver.cpp')
-rw-r--r-- | src/network/ssl/qsslserver.cpp | 151 |
1 files changed, 138 insertions, 13 deletions
diff --git a/src/network/ssl/qsslserver.cpp b/src/network/ssl/qsslserver.cpp index 727d647784..40a6a6f526 100644 --- a/src/network/ssl/qsslserver.cpp +++ b/src/network/ssl/qsslserver.cpp @@ -16,7 +16,7 @@ Transport Layer Security (TLS). To configure the secure handshake settings, use the applicable setter - functions on a QSslConfiguration object, and then use it as a argument + functions on a QSslConfiguration object, and then use it as an argument to the setSslConfiguration() function. All following incoming connections handled will use these settings. @@ -32,7 +32,7 @@ nextPendingConnection() function to fetch the next incoming connection and take it out of the pending connection queue. The QSslSocket is a child of the QSslServer and will be deleted when the QSslServer is deleted. It is - still a good a good idea to destroy the object explicitly when you are done + still a good idea to destroy the object explicitly when you are done with it, to avoid wasting memory. \sa QTcpServer, QSslConfiguration, QSslSocket @@ -171,6 +171,13 @@ QSslConfiguration::setHandshakeMustInterruptOnError() */ +/*! + \fn void QSslServer::startedEncryptionHandshake(QSslSocket *socket) + + This signal is emitted when the client, connected to \a socket, + initiates the TLS handshake. +*/ + #include "qsslserver.h" #include "qsslserver_p.h" @@ -228,6 +235,42 @@ QSslConfiguration QSslServer::sslConfiguration() const } /*! + Sets the \a timeout to use for all incoming handshakes, in milliseconds. + + This is relevant in the scenario where a client, whether malicious or + accidental, connects to the server but makes no attempt at communicating or + initiating a handshake. QSslServer will then automatically end the + connection after \a timeout milliseconds have elapsed. + + By default the timeout is 5000 milliseconds (5 seconds). + + \note The underlying TLS framework may have their own timeout logic now or + in the future, this function does not affect that. + + \note The \a timeout passed to this function will only apply to \e{new} + connections. If a client is already connected it will use the timeout which + was set when it connected. + + \sa handshakeTimeout() +*/ +void QSslServer::setHandshakeTimeout(int timeout) +{ + Q_D(QSslServer); + d->handshakeTimeout = timeout; +} + +/*! + Returns the currently configured handshake timeout. + + \sa setHandshakeTimeout() +*/ +int QSslServer::handshakeTimeout() const +{ + const Q_D(QSslServer); + return d->handshakeTimeout; +} + +/*! Called when a new connection is established. Converts \a socket to a QSslSocket. @@ -241,47 +284,129 @@ void QSslServer::incomingConnection(qintptr socket) pSslSocket->setSslConfiguration(sslConfiguration()); if (Q_LIKELY(pSslSocket->setSocketDescriptor(socket))) { - connect(pSslSocket, &QSslSocket::peerVerifyError, + connect(pSslSocket, &QSslSocket::peerVerifyError, this, [this, pSslSocket](const QSslError &error) { Q_EMIT peerVerifyError(pSslSocket, error); }); - connect(pSslSocket, &QSslSocket::sslErrors, + connect(pSslSocket, &QSslSocket::sslErrors, this, [this, pSslSocket](const QList<QSslError> &errors) { Q_EMIT sslErrors(pSslSocket, errors); }); - connect(pSslSocket, &QAbstractSocket::errorOccurred, + connect(pSslSocket, &QAbstractSocket::errorOccurred, this, [this, pSslSocket](QAbstractSocket::SocketError error) { Q_EMIT errorOccurred(pSslSocket, error); if (!pSslSocket->isEncrypted()) pSslSocket->deleteLater(); }); - connect(pSslSocket, &QSslSocket::encrypted, [this, pSslSocket]() { - pSslSocket->disconnect(); + connect(pSslSocket, &QSslSocket::encrypted, this, [this, pSslSocket]() { + Q_D(QSslServer); + d->removeSocketData(quintptr(pSslSocket)); + pSslSocket->disconnect(this); addPendingConnection(pSslSocket); }); - connect(pSslSocket, &QSslSocket::preSharedKeyAuthenticationRequired, + connect(pSslSocket, &QSslSocket::preSharedKeyAuthenticationRequired, this, [this, pSslSocket](QSslPreSharedKeyAuthenticator *authenticator) { Q_EMIT preSharedKeyAuthenticationRequired(pSslSocket, authenticator); }); - connect(pSslSocket, &QSslSocket::alertSent, + connect(pSslSocket, &QSslSocket::alertSent, this, [this, pSslSocket](QSsl::AlertLevel level, QSsl::AlertType type, const QString &description) { Q_EMIT alertSent(pSslSocket, level, type, description); }); - connect(pSslSocket, &QSslSocket::alertReceived, + connect(pSslSocket, &QSslSocket::alertReceived, this, [this, pSslSocket](QSsl::AlertLevel level, QSsl::AlertType type, const QString &description) { Q_EMIT alertReceived(pSslSocket, level, type, description); }); - connect(pSslSocket, &QSslSocket::handshakeInterruptedOnError, + connect(pSslSocket, &QSslSocket::handshakeInterruptedOnError, this, [this, pSslSocket](const QSslError &error) { Q_EMIT handshakeInterruptedOnError(pSslSocket, error); }); - Q_EMIT startedEncryptionHandshake(pSslSocket); + d_func()->initializeHandshakeProcess(pSslSocket); + } +} + +void QSslServerPrivate::initializeHandshakeProcess(QSslSocket *socket) +{ + Q_Q(QSslServer); + QMetaObject::Connection readyRead = QObject::connect( + socket, &QSslSocket::readyRead, q, [this]() { checkClientHelloAndContinue(); }); + + QMetaObject::Connection destroyed = + QObject::connect(socket, &QSslSocket::destroyed, q, [this](QObject *obj) { + // This cast is not safe to use since the socket is inside the + // QObject dtor, but we only use the pointer value! + removeSocketData(quintptr(obj)); + }); + auto it = socketData.emplace(quintptr(socket), readyRead, destroyed, std::make_shared<QTimer>()); + it->timeoutTimer->setSingleShot(true); + it->timeoutTimer->callOnTimeout(q, [this, socket]() { handleHandshakeTimedOut(socket); }); + it->timeoutTimer->setInterval(handshakeTimeout); + it->timeoutTimer->start(); +} + +// This function may be called while in the socket's QObject dtor, __never__ use +// the socket for anything other than a lookup! +void QSslServerPrivate::removeSocketData(quintptr socket) +{ + auto it = socketData.find(socket); + if (it != socketData.end()) { + it->disconnectSignals(); + socketData.erase(it); + } +} + +int QSslServerPrivate::totalPendingConnections() const +{ + // max pending connections is int, so this cannot exceed that + return QTcpServerPrivate::totalPendingConnections() + int(socketData.size()); +} + +void QSslServerPrivate::checkClientHelloAndContinue() +{ + Q_Q(QSslServer); + QSslSocket *socket = qobject_cast<QSslSocket *>(q->sender()); + if (Q_UNLIKELY(!socket) || socket->bytesAvailable() <= 0) + return; + + char byte = '\0'; + if (socket->peek(&byte, 1) != 1) { + socket->deleteLater(); + return; + } + + auto it = socketData.find(quintptr(socket)); + const bool foundData = it != socketData.end(); + if (foundData && it->readyReadConnection) + QObject::disconnect(std::exchange(it->readyReadConnection, {})); - pSslSocket->startServerEncryption(); + constexpr char CLIENT_HELLO = 0x16; + if (byte != CLIENT_HELLO) { + socket->disconnectFromHost(); + socket->deleteLater(); + return; } + + // Be nice and restart the timeout timer since some progress was made + if (foundData) + it->timeoutTimer->start(); + + socket->startServerEncryption(); + Q_EMIT q->startedEncryptionHandshake(socket); +} + +void QSslServerPrivate::handleHandshakeTimedOut(QSslSocket *socket) +{ + Q_Q(QSslServer); + removeSocketData(quintptr(socket)); + socket->disconnectFromHost(); + Q_EMIT q->errorOccurred(socket, QAbstractSocket::SocketTimeoutError); + socket->deleteLater(); + if (!socketEngine->isReadNotificationEnabled() && totalPendingConnections() < maxConnections) + q->resumeAccepting(); } QT_END_NAMESPACE + +#include "moc_qsslserver.cpp" |