summaryrefslogtreecommitdiffstats
path: root/src/network/ssl/qsslserver.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/network/ssl/qsslserver.cpp')
-rw-r--r--src/network/ssl/qsslserver.cpp151
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"