mumble-voip_mumble/src/mumble/ServerHandler.cpp

1150 lines
33 KiB
C++

// Copyright 2007-2023 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
#include <QSslConfiguration>
#include <QtCore/QtGlobal>
#ifdef Q_OS_WIN
# include "win.h"
#endif
#include "ServerHandler.h"
#include "AudioInput.h"
#include "AudioOutput.h"
#include "Cert.h"
#include "Connection.h"
#include "Database.h"
#include "HostAddress.h"
#include "MainWindow.h"
#include "Net.h"
#include "NetworkConfig.h"
#include "OSInfo.h"
#include "PacketDataStream.h"
#include "ProtoUtils.h"
#include "RichTextEditor.h"
#include "SSL.h"
#include "ServerResolver.h"
#include "ServerResolverRecord.h"
#include "User.h"
#include "Utils.h"
#include "Global.h"
#include <QPainter>
#include <QtCore/QtEndian>
#include <QtGui/QImageReader>
#include <QtNetwork/QSslConfiguration>
#include <QtNetwork/QUdpSocket>
#include <openssl/crypto.h>
#include <cassert>
#ifdef Q_OS_WIN
// <delayimp.h> is not protected with an include guard on MinGW, resulting in
// redefinitions if the PCH header is used.
// The workaround consists in including the header only if _DELAY_IMP_VER
// (defined in the header) is not defined.
# ifndef _DELAY_IMP_VER
# include <delayimp.h>
# endif
# include <qos2.h>
# include <wincrypt.h>
# include <winsock2.h>
#else
# if defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
# include <netinet/in.h>
# endif
# include <netinet/ip.h>
# include <sys/socket.h>
#endif
// Init ServerHandler::nextConnectionID
int ServerHandler::nextConnectionID = -1;
QMutex ServerHandler::nextConnectionIDMutex;
ServerHandlerMessageEvent::ServerHandlerMessageEvent(const QByteArray &msg, Mumble::Protocol::TCPMessageType type,
bool flush)
: QEvent(static_cast< QEvent::Type >(SERVERSEND_EVENT)) {
qbaMsg = msg;
this->type = type;
bFlush = flush;
}
#ifdef Q_OS_WIN
static HANDLE loadQoS() {
HANDLE hQoS = nullptr;
HRESULT hr = E_FAIL;
// We don't support delay-loading QoS on MinGW. Only enable it for MSVC.
# ifdef _MSC_VER
__try {
hr = __HrLoadAllImportsForDll("qwave.dll");
}
__except (EXCEPTION_EXECUTE_HANDLER) {
hr = E_FAIL;
}
# endif
if (!SUCCEEDED(hr)) {
qWarning("ServerHandler: Failed to load qWave.dll, no QoS available");
} else {
QOS_VERSION qvVer;
qvVer.MajorVersion = 1;
qvVer.MinorVersion = 0;
if (!QOSCreateHandle(&qvVer, &hQoS)) {
qWarning("ServerHandler: Failed to create QOS2 handle");
hQoS = nullptr;
} else {
qWarning("ServerHandler: QOS2 loaded");
}
}
return hQoS;
}
#endif
ServerHandler::ServerHandler() : database(new Database(QLatin1String("ServerHandler"))) {
cConnection.reset();
qusUdp = nullptr;
bStrong = false;
usPort = 0;
bUdp = true;
tConnectionTimeoutTimer = nullptr;
m_version = Version::UNKNOWN;
iInFlightTCPPings = 0;
// assign connection ID
{
QMutexLocker lock(&nextConnectionIDMutex);
nextConnectionID++;
connectionID = nextConnectionID;
}
// Historically, the qWarning line below initialized OpenSSL for us.
// It used to have this comment:
//
// "For some strange reason, on Win32, we have to call
// supportsSsl before the cipher list is ready."
//
// Now, OpenSSL is initialized in main() via MumbleSSL::initialize(),
// but since it's handy to have the OpenSSL version available, we
// keep this one around as well.
qWarning("OpenSSL Support: %d (%s)", QSslSocket::supportsSsl(), SSLeay_version(SSLEAY_VERSION));
MumbleSSL::addSystemCA();
{
QList< QSslCipher > ciphers = MumbleSSL::ciphersFromOpenSSLCipherString(Global::get().s.qsSslCiphers);
if (ciphers.isEmpty()) {
qFatal("Invalid 'net/sslciphers' config option. Either the cipher string is invalid or none of the ciphers "
"are available:: \"%s\"",
qPrintable(Global::get().s.qsSslCiphers));
}
QSslConfiguration config = QSslConfiguration::defaultConfiguration();
config.setCiphers(ciphers);
QSslConfiguration::setDefaultConfiguration(config);
QStringList pref;
foreach (QSslCipher c, ciphers) { pref << c.name(); }
qWarning("ServerHandler: TLS cipher preference is \"%s\"", qPrintable(pref.join(QLatin1String(":"))));
}
#ifdef Q_OS_WIN
hQoS = loadQoS();
if (hQoS)
Connection::setQoS(hQoS);
#endif
connect(this, SIGNAL(pingRequested()), this, SLOT(sendPingInternal()), Qt::QueuedConnection);
}
ServerHandler::~ServerHandler() {
wait();
cConnection.reset();
#ifdef Q_OS_WIN
if (hQoS) {
QOSCloseHandle(hQoS);
Connection::setQoS(nullptr);
}
#endif
}
void ServerHandler::customEvent(QEvent *evt) {
if (evt->type() != SERVERSEND_EVENT)
return;
ServerHandlerMessageEvent *shme = static_cast< ServerHandlerMessageEvent * >(evt);
ConnectionPtr connection(cConnection);
if (connection) {
if (shme->qbaMsg.size() > 0) {
connection->sendMessage(shme->qbaMsg);
if (shme->bFlush)
connection->forceFlush();
} else {
exit(0);
}
}
}
int ServerHandler::getConnectionID() const {
return connectionID;
}
void ServerHandler::setProtocolVersion(Version::full_t version) {
m_version = version;
m_udpPingEncoder.setProtocolVersion(version);
m_udpDecoder.setProtocolVersion(version);
m_tcpTunnelDecoder.setProtocolVersion(version);
}
void ServerHandler::udpReady() {
while (qusUdp->hasPendingDatagrams()) {
char encrypted[Mumble::Protocol::MAX_UDP_PACKET_SIZE];
unsigned int buflen = static_cast< unsigned int >(qusUdp->pendingDatagramSize());
if (buflen > Mumble::Protocol::MAX_UDP_PACKET_SIZE) {
// Discard datagrams that exceed our buffer's size as we'd have to trim them down anyways and it is not very
// likely that the data is valid in the trimmed down form.
// As we're using a maxSize of 0 it is okay to pass nullptr as the data buffer. Qt's docs (5.15) ensures
// that a maxSize of 0 means discarding the datagram.
qusUdp->readDatagram(nullptr, 0);
continue;
}
QHostAddress senderAddr;
quint16 senderPort;
qusUdp->readDatagram(encrypted, buflen, &senderAddr, &senderPort);
if (!(HostAddress(senderAddr) == HostAddress(qhaRemote)) || (senderPort != usResolvedPort))
continue;
ConnectionPtr connection(cConnection);
if (!connection)
continue;
if (!connection->csCrypt->isValid())
continue;
if (buflen < 5)
continue;
gsl::span< Mumble::Protocol::byte > buffer = m_udpDecoder.getBuffer();
// 4 bytes is the overhead of the encryption
assert(buffer.size() >= buflen - 4);
if (!connection->csCrypt->decrypt(reinterpret_cast< const unsigned char * >(encrypted), buffer.data(),
buflen)) {
if (connection->csCrypt->tLastGood.elapsed() > 5000000ULL) {
if (connection->csCrypt->tLastRequest.elapsed() > 5000000ULL) {
connection->csCrypt->tLastRequest.restart();
MumbleProto::CryptSetup mpcs;
sendMessage(mpcs);
}
}
continue;
}
if (m_udpDecoder.decode(buffer.subspan(0, buflen - 4))) {
switch (m_udpDecoder.getMessageType()) {
case Mumble::Protocol::UDPMessageType::Ping: {
const Mumble::Protocol::PingData pingData = m_udpDecoder.getPingData();
accUDP(static_cast< double >(tTimestamp.elapsed() - pingData.timestamp) / 1000.0);
break;
}
case Mumble::Protocol::UDPMessageType::Audio: {
const Mumble::Protocol::AudioData audioData = m_udpDecoder.getAudioData();
handleVoicePacket(audioData);
break;
};
}
}
}
}
void ServerHandler::handleVoicePacket(const Mumble::Protocol::AudioData &audioData) {
if (audioData.usedCodec != Mumble::Protocol::AudioCodec::Opus) {
qWarning("Dropping audio packet using invalid codec (not Opus): %d", static_cast< int >(audioData.usedCodec));
return;
}
ClientUser *sender = ClientUser::get(audioData.senderSession);
AudioOutputPtr ao = Global::get().ao;
if (ao && sender
&& !((audioData.targetOrContext == Mumble::Protocol::AudioContext::WHISPER) && Global::get().s.bWhisperFriends
&& sender->qsFriendName.isEmpty())) {
ao->addFrameToBuffer(sender, audioData);
}
}
void ServerHandler::sendMessage(const unsigned char *data, int len, bool force) {
static std::vector< unsigned char > crypto;
crypto.resize(static_cast< std::size_t >(len + 4));
QMutexLocker qml(&qmUdp);
if (!qusUdp)
return;
ConnectionPtr connection(cConnection);
if (!connection || !connection->csCrypt->isValid())
return;
if (!force && (NetworkConfig::TcpModeEnabled() || !bUdp)) {
QByteArray qba;
qba.resize(len + 6);
unsigned char *uc = reinterpret_cast< unsigned char * >(qba.data());
*reinterpret_cast< quint16 * >(&uc[0]) =
qToBigEndian(static_cast< quint16 >(Mumble::Protocol::TCPMessageType::UDPTunnel));
*reinterpret_cast< quint32 * >(&uc[2]) = qToBigEndian(static_cast< quint32 >(len));
memcpy(uc + 6, data, static_cast< std::size_t >(len));
QApplication::postEvent(this,
new ServerHandlerMessageEvent(qba, Mumble::Protocol::TCPMessageType::UDPTunnel, true));
} else {
if (!connection->csCrypt->encrypt(reinterpret_cast< const unsigned char * >(data), crypto.data(),
static_cast< unsigned int >(len))) {
return;
}
qusUdp->writeDatagram(reinterpret_cast< const char * >(crypto.data()), len + 4, qhaRemote, usResolvedPort);
}
}
void ServerHandler::sendProtoMessage(const ::google::protobuf::Message &msg, Mumble::Protocol::TCPMessageType type) {
QByteArray qba;
if (QThread::currentThread() != thread()) {
Connection::messageToNetwork(msg, type, qba);
ServerHandlerMessageEvent *shme = new ServerHandlerMessageEvent(qba, type, false);
QApplication::postEvent(this, shme);
} else {
ConnectionPtr connection(cConnection);
if (!connection)
return;
connection->sendMessage(msg, type, qba);
}
}
bool ServerHandler::isConnected() const {
// If the digest isn't empty, then we are currently connected to a server (the digest being a hash
// of the server's certificate)
return !qbaDigest.isEmpty();
}
bool ServerHandler::hasSynchronized() const {
return serverSynchronized;
}
void ServerHandler::setServerSynchronized(bool synchronized) {
serverSynchronized = synchronized;
}
void ServerHandler::hostnameResolved() {
ServerResolver *sr = qobject_cast< ServerResolver * >(QObject::sender());
QList< ServerResolverRecord > records = sr->records();
// Exit the ServerHandler thread's event loop with an
// error code in case our hostname lookup failed.
if (records.isEmpty()) {
exit(-1);
return;
}
// Create the list of target host:port pairs
// that the ServerHandler should try to connect to.
QList< ServerAddress > ql;
QHash< ServerAddress, QString > qh;
foreach (ServerResolverRecord record, records) {
foreach (HostAddress addr, record.addresses()) {
auto sa = ServerAddress(addr, record.port());
ql.append(sa);
qh[sa] = record.hostname();
}
}
qlAddresses = ql;
qhHostnames = qh;
// Exit the event loop with 'success' status code,
// to continue connecting to the server.
exit(0);
}
void ServerHandler::run() {
// Resolve the hostname...
{
ServerResolver sr;
QObject::connect(&sr, SIGNAL(resolved()), this, SLOT(hostnameResolved()));
sr.resolve(qsHostName, usPort);
int ret = exec();
if (ret < 0) {
qWarning("ServerHandler: failed to resolve hostname");
emit error(QAbstractSocket::HostNotFoundError, tr("Unable to resolve hostname"));
return;
}
}
QList< ServerAddress > targetAddresses(qlAddresses);
bool shouldTryNextTargetServer = true;
do {
saTargetServer = qlAddresses.takeFirst();
tConnectionTimeoutTimer = nullptr;
qbaDigest = QByteArray();
bStrong = true;
qtsSock = new QSslSocket(this);
qtsSock->setPeerVerifyName(qhHostnames[saTargetServer]);
if (!Global::get().s.bSuppressIdentity && CertWizard::validateCert(Global::get().s.kpCertificate)) {
qtsSock->setPrivateKey(Global::get().s.kpCertificate.second);
qtsSock->setLocalCertificate(Global::get().s.kpCertificate.first.at(0));
QSslConfiguration config = qtsSock->sslConfiguration();
QList< QSslCertificate > certs = config.caCertificates();
certs << Global::get().s.kpCertificate.first;
config.setCaCertificates(certs);
qtsSock->setSslConfiguration(config);
}
{
ConnectionPtr connection(new Connection(this, qtsSock));
cConnection = connection;
// Technically it isn't necessary to reset this flag here since a ServerHandler will not be used
// for multiple connections in a row but just in case that at some point it will, we'll reset the
// flag here.
serverSynchronized = false;
qlErrors.clear();
qscCert.clear();
connect(qtsSock, &QSslSocket::encrypted, this, &ServerHandler::serverConnectionConnected);
connect(qtsSock, &QSslSocket::stateChanged, this, &ServerHandler::serverConnectionStateChanged);
connect(connection.get(), &Connection::connectionClosed, this, &ServerHandler::serverConnectionClosed);
connect(connection.get(), &Connection::message, this, &ServerHandler::message);
connect(connection.get(), &Connection::handleSslErrors, this, &ServerHandler::setSslErrors);
}
bUdp = false;
#if QT_VERSION >= 0x050500
qtsSock->setProtocol(QSsl::TlsV1_0OrLater);
#elif QT_VERSION >= 0x050400
// In Qt 5.4, QSsl::SecureProtocols is equivalent
// to "TLSv1.0 or later", which we require.
qtsSock->setProtocol(QSsl::SecureProtocols);
#else
qtsSock->setProtocol(QSsl::TlsV1_0);
#endif
qtsSock->connectToHost(saTargetServer.host.toAddress(), saTargetServer.port);
tTimestamp.restart();
// Setup ping timer;
QTimer *ticker = new QTimer(this);
connect(ticker, SIGNAL(timeout()), this, SLOT(sendPing()));
ticker->start(Global::get().s.iPingIntervalMsec);
Global::get().mw->rtLast = MumbleProto::Reject_RejectType_None;
accUDP = accTCP = accClean;
m_version = Version::UNKNOWN;
qsRelease = QString();
qsOS = QString();
qsOSVersion = QString();
int ret = exec();
if (ret == -2) {
shouldTryNextTargetServer = true;
} else {
shouldTryNextTargetServer = false;
}
if (qusUdp) {
QMutexLocker qml(&qmUdp);
#ifdef Q_OS_WIN
if (hQoS) {
if (!QOSRemoveSocketFromFlow(hQoS, 0, dwFlowUDP, 0)) {
qWarning("ServerHandler: Failed to remove UDP from QoS. QOSRemoveSocketFromFlow() failed with "
"error %lu!",
GetLastError());
}
dwFlowUDP = 0;
}
#endif
delete qusUdp;
qusUdp = nullptr;
}
ticker->stop();
ConnectionPtr cptr(cConnection);
if (cptr) {
cptr->disconnectSocket(true);
}
cConnection.reset();
while (!cptr.unique()) {
msleep(100);
}
delete qtsSock;
delete tConnectionTimeoutTimer;
} while (shouldTryNextTargetServer && !qlAddresses.isEmpty());
}
#ifdef Q_OS_WIN
extern DWORD WinVerifySslCert(const QByteArray &cert);
#endif
void ServerHandler::setSslErrors(const QList< QSslError > &errors) {
ConnectionPtr connection(cConnection);
if (!connection)
return;
qscCert = connection->peerCertificateChain();
QList< QSslError > newErrors = errors;
#ifdef Q_OS_WIN
bool bRevalidate = false;
QList< QSslError > errorsToRemove;
foreach (const QSslError &e, errors) {
switch (e.error()) {
case QSslError::UnableToGetLocalIssuerCertificate:
case QSslError::SelfSignedCertificateInChain:
bRevalidate = true;
errorsToRemove << e;
break;
default:
break;
}
}
if (bRevalidate) {
QByteArray der = qscCert.first().toDer();
DWORD errorStatus = WinVerifySslCert(der);
if (errorStatus == CERT_TRUST_NO_ERROR) {
foreach (const QSslError &e, errorsToRemove) { newErrors.removeOne(e); }
}
if (newErrors.isEmpty()) {
connection->proceedAnyway();
return;
}
}
#endif
bStrong = false;
if ((qscCert.size() > 0)
&& (QString::fromLatin1(qscCert.at(0).digest(QCryptographicHash::Sha1).toHex())
== database->getDigest(qsHostName, usPort)))
connection->proceedAnyway();
else
qlErrors = newErrors;
}
void ServerHandler::sendPing() {
emit pingRequested();
}
void ServerHandler::sendPingInternal() {
ConnectionPtr connection(cConnection);
if (!connection)
return;
if (qtsSock->state() != QAbstractSocket::ConnectedState) {
return;
}
// Ensure the TLS handshake has completed before sending pings.
if (!qtsSock->isEncrypted()) {
return;
}
if (Global::get().s.iMaxInFlightTCPPings > 0 && iInFlightTCPPings >= Global::get().s.iMaxInFlightTCPPings) {
serverConnectionClosed(QAbstractSocket::UnknownSocketError, tr("Server is not responding to TCP pings"));
return;
}
quint64 t = tTimestamp.elapsed();
if (qusUdp) {
Mumble::Protocol::PingData pingData;
pingData.timestamp = t;
pingData.requestAdditionalInformation = false;
m_udpPingEncoder.setProtocolVersion(m_version);
gsl::span< const Mumble::Protocol::byte > encodedPacket = m_udpPingEncoder.encodePingPacket(pingData);
sendMessage(encodedPacket.data(), static_cast< int >(encodedPacket.size()), true);
}
MumbleProto::Ping mpp;
mpp.set_timestamp(t);
mpp.set_good(connection->csCrypt->uiGood);
mpp.set_late(connection->csCrypt->uiLate);
mpp.set_lost(connection->csCrypt->uiLost);
mpp.set_resync(connection->csCrypt->uiResync);
if (boost::accumulators::count(accUDP)) {
mpp.set_udp_ping_avg(static_cast< float >(boost::accumulators::mean(accUDP)));
mpp.set_udp_ping_var(static_cast< float >(boost::accumulators::variance(accUDP)));
}
mpp.set_udp_packets(static_cast< unsigned int >(boost::accumulators::count(accUDP)));
if (boost::accumulators::count(accTCP)) {
mpp.set_tcp_ping_avg(static_cast< float >(boost::accumulators::mean(accTCP)));
mpp.set_tcp_ping_var(static_cast< float >(boost::accumulators::variance(accTCP)));
}
mpp.set_tcp_packets(static_cast< unsigned int >(boost::accumulators::count(accTCP)));
sendMessage(mpp);
iInFlightTCPPings += 1;
}
void ServerHandler::message(Mumble::Protocol::TCPMessageType type, const QByteArray &qbaMsg) {
const char *ptr = qbaMsg.constData();
if (type == Mumble::Protocol::TCPMessageType::UDPTunnel) {
// audio tunneled through tcp.
// since it could happen that we are receiving udp and tcp messages at the same time (e.g. the server used to
// send us packages via TCP but has now switched to UDP again and the first UDP packages arrive at the same time
// as the last TCP ones), we want to use a dedicated decoder for this (to make sure there is no concurrent
// access to the decoder's internal buffer).
if (m_tcpTunnelDecoder.decode(
{ reinterpret_cast< const Mumble::Protocol::byte * >(ptr), static_cast< std::size_t >(qbaMsg.size()) })
&& m_tcpTunnelDecoder.getMessageType() == Mumble::Protocol::UDPMessageType::Audio) {
handleVoicePacket(m_tcpTunnelDecoder.getAudioData());
}
} else if (type == Mumble::Protocol::TCPMessageType::Ping) {
MumbleProto::Ping msg;
if (msg.ParseFromArray(qbaMsg.constData(), qbaMsg.size())) {
ConnectionPtr connection(cConnection);
if (!connection)
return;
// Reset in-flight TCP ping counter to 0.
// We've received a ping. That means the
// connection is still OK.
iInFlightTCPPings = 0;
connection->csCrypt->uiRemoteGood = msg.good();
connection->csCrypt->uiRemoteLate = msg.late();
connection->csCrypt->uiRemoteLost = msg.lost();
connection->csCrypt->uiRemoteResync = msg.resync();
accTCP(static_cast< double >(tTimestamp.elapsed() - msg.timestamp()) / 1000.0);
if (((connection->csCrypt->uiRemoteGood == 0) || (connection->csCrypt->uiGood == 0)) && bUdp
&& (tTimestamp.elapsed() > 20000000ULL)) {
bUdp = false;
if (!NetworkConfig::TcpModeEnabled()) {
if ((connection->csCrypt->uiRemoteGood == 0) && (connection->csCrypt->uiGood == 0))
Global::get().mw->msgBox(
tr("UDP packets cannot be sent to or received from the server. Switching to TCP mode."));
else if (connection->csCrypt->uiRemoteGood == 0)
Global::get().mw->msgBox(
tr("UDP packets cannot be sent to the server. Switching to TCP mode."));
else
Global::get().mw->msgBox(
tr("UDP packets cannot be received from the server. Switching to TCP mode."));
database->setUdp(qbaDigest, false);
}
} else if (!bUdp && (connection->csCrypt->uiRemoteGood > 3) && (connection->csCrypt->uiGood > 3)) {
bUdp = true;
if (!NetworkConfig::TcpModeEnabled()) {
Global::get().mw->msgBox(
tr("UDP packets can be sent to and received from the server. Switching back to UDP mode."));
database->setUdp(qbaDigest, true);
}
}
}
} else {
ServerHandlerMessageEvent *shme = new ServerHandlerMessageEvent(qbaMsg, type, false);
QApplication::postEvent(Global::get().mw, shme);
}
}
void ServerHandler::disconnect() {
// Actual TCP object is in a different thread, so signal it
// The actual type of this event doesn't matter as we are only abusing the event mechanism to signal the thread to
// exit.
QByteArray qbaBuffer;
ServerHandlerMessageEvent *shme =
new ServerHandlerMessageEvent(qbaBuffer, Mumble::Protocol::TCPMessageType::Ping, false);
QApplication::postEvent(this, shme);
}
void ServerHandler::serverConnectionClosed(QAbstractSocket::SocketError err, const QString &reason) {
Connection *c = cConnection.get();
if (!c)
return;
if (c->bDisconnectedEmitted)
return;
c->bDisconnectedEmitted = true;
AudioOutputPtr ao = Global::get().ao;
if (ao)
ao->wipe();
// Try next server in the list if possible.
// Otherwise, emit disconnect and exit with
// a normal status code.
if (!qlAddresses.isEmpty()) {
if (err == QAbstractSocket::ConnectionRefusedError || err == QAbstractSocket::SocketTimeoutError) {
qWarning("ServerHandler: connection attempt to %s:%i failed: %s (%li); trying next server....",
qPrintable(saTargetServer.host.toString()), static_cast< int >(saTargetServer.port),
qPrintable(reason), static_cast< long >(err));
exit(-2);
return;
}
}
// Having 2 signals here that basically fire at the same time is wanted behavior!
// See the documentation of "aboutToDisconnect" for an explanation.
emit aboutToDisconnect(err, reason);
emit disconnected(err, reason);
exit(0);
}
void ServerHandler::serverConnectionTimeoutOnConnect() {
ConnectionPtr connection(cConnection);
if (connection)
connection->disconnectSocket(true);
serverConnectionClosed(QAbstractSocket::SocketTimeoutError, tr("Connection timed out"));
}
void ServerHandler::serverConnectionStateChanged(QAbstractSocket::SocketState state) {
if (state == QAbstractSocket::ConnectingState) {
// Start timer for connection timeout during connect after resolving is completed
tConnectionTimeoutTimer = new QTimer();
connect(tConnectionTimeoutTimer, SIGNAL(timeout()), this, SLOT(serverConnectionTimeoutOnConnect()));
tConnectionTimeoutTimer->setSingleShot(true);
tConnectionTimeoutTimer->start(Global::get().s.iConnectionTimeoutDurationMsec);
} else if (state == QAbstractSocket::ConnectedState) {
// Start TLS handshake
qtsSock->startClientEncryption();
}
}
void ServerHandler::serverConnectionConnected() {
ConnectionPtr connection(cConnection);
if (!connection)
return;
#if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0)
// The ephemeralServerKey property is only a non-null key, if forward secrecy is used.
// See also https://doc.qt.io/qt-5/qsslconfiguration.html#ephemeralServerKey
connectionUsesPerfectForwardSecrecy = !qtsSock->sslConfiguration().ephemeralServerKey().isNull();
#endif
iInFlightTCPPings = 0;
tConnectionTimeoutTimer->stop();
if (Global::get().s.bQoS)
connection->setToS();
qscCert = connection->peerCertificateChain();
qscCipher = connection->sessionCipher();
if (!qscCert.isEmpty()) {
// Get the server's immediate SSL certificate
const QSslCertificate &qsc = qscCert.first();
qbaDigest = sha1(qsc.publicKey().toDer());
bUdp = database->getUdp(qbaDigest);
} else {
// Shouldn't reach this
qCritical("Server must have a certificate. Dropping connection");
disconnect();
return;
}
MumbleProto::Version mpv;
mpv.set_release(u8(Version::getRelease()));
MumbleProto::setVersion(mpv, Version::get());
if (!Global::get().s.bHideOS) {
mpv.set_os(u8(OSInfo::getOS()));
mpv.set_os_version(u8(OSInfo::getOSDisplayableVersion()));
}
sendMessage(mpv);
MumbleProto::Authenticate mpa;
mpa.set_username(u8(qsUserName));
mpa.set_password(u8(qsPassword));
QStringList tokens = database->getTokens(qbaDigest);
foreach (const QString &qs, tokens)
mpa.add_tokens(u8(qs));
mpa.set_opus(true);
sendMessage(mpa);
{
QMutexLocker qml(&qmUdp);
qhaRemote = connection->peerAddress();
qhaLocal = connection->localAddress();
usResolvedPort = connection->peerPort();
if (qhaLocal.isNull()) {
qFatal("ServerHandler: qhaLocal is unexpectedly a null addr");
}
qusUdp = new QUdpSocket(this);
if (!qusUdp) {
qFatal("ServerHandler: qusUdp is unexpectedly a null addr");
}
if (Global::get().s.bUdpForceTcpAddr) {
qusUdp->bind(qhaLocal, 0);
} else {
if (qhaRemote.protocol() == QAbstractSocket::IPv6Protocol) {
qusUdp->bind(QHostAddress(QHostAddress::AnyIPv6), 0);
} else {
qusUdp->bind(QHostAddress(QHostAddress::Any), 0);
}
}
connect(qusUdp, SIGNAL(readyRead()), this, SLOT(udpReady()));
if (Global::get().s.bQoS) {
#if defined(Q_OS_UNIX)
int val = 0xe0;
if (setsockopt(static_cast< int >(qusUdp->socketDescriptor()), IPPROTO_IP, IP_TOS, &val, sizeof(val))) {
val = 0x80;
if (setsockopt(static_cast< int >(qusUdp->socketDescriptor()), IPPROTO_IP, IP_TOS, &val, sizeof(val)))
qWarning("ServerHandler: Failed to set TOS for UDP Socket");
}
# if defined(SO_PRIORITY)
socklen_t optlen = sizeof(val);
if (getsockopt(static_cast< int >(qusUdp->socketDescriptor()), SOL_SOCKET, SO_PRIORITY, &val, &optlen)
== 0) {
if (val == 0) {
val = 6;
setsockopt(static_cast< int >(qusUdp->socketDescriptor()), SOL_SOCKET, SO_PRIORITY, &val,
sizeof(val));
}
}
# endif
#elif defined(Q_OS_WIN)
if (hQoS) {
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(usPort);
addr.sin_addr.s_addr = htonl(qhaRemote.toIPv4Address());
dwFlowUDP = 0;
if (!QOSAddSocketToFlow(hQoS, qusUdp->socketDescriptor(), reinterpret_cast< sockaddr * >(&addr),
QOSTrafficTypeVoice, QOS_NON_ADAPTIVE_FLOW,
reinterpret_cast< PQOS_FLOWID >(&dwFlowUDP)))
qWarning("ServerHandler: Failed to add UDP to QOS");
}
#endif
}
}
emit connected();
}
void ServerHandler::setConnectionInfo(const QString &host, unsigned short port, const QString &username,
const QString &pw) {
qsHostName = host;
usPort = port;
qsUserName = username;
qsPassword = pw;
}
void ServerHandler::getConnectionInfo(QString &host, unsigned short &port, QString &username, QString &pw) const {
host = qsHostName;
port = usPort;
username = qsUserName;
pw = qsPassword;
}
bool ServerHandler::isStrong() const {
return bStrong;
}
void ServerHandler::requestUserStats(unsigned int uiSession, bool statsOnly) {
MumbleProto::UserStats mpus;
mpus.set_session(uiSession);
mpus.set_stats_only(statsOnly);
sendMessage(mpus);
}
void ServerHandler::joinChannel(unsigned int uiSession, unsigned int channel) {
static const QStringList EMPTY;
joinChannel(uiSession, channel, EMPTY);
}
void ServerHandler::joinChannel(unsigned int uiSession, unsigned int channel,
const QStringList &temporaryAccessTokens) {
MumbleProto::UserState mpus;
mpus.set_session(uiSession);
mpus.set_channel_id(channel);
foreach (const QString &tmpToken, temporaryAccessTokens) {
mpus.add_temporary_access_tokens(tmpToken.toUtf8().constData());
}
sendMessage(mpus);
}
void ServerHandler::startListeningToChannel(unsigned int channel) {
startListeningToChannels({ channel });
}
void ServerHandler::startListeningToChannels(const QList< unsigned int > &channelIDs) {
if (channelIDs.isEmpty()) {
return;
}
MumbleProto::UserState mpus;
mpus.set_session(Global::get().uiSession);
for (unsigned int currentChannel : channelIDs) {
// The naming of the function is a bit unfortunate but what this does is to add
// the channel ID to the message field listening_channel_add
mpus.add_listening_channel_add(currentChannel);
}
sendMessage(mpus);
}
void ServerHandler::stopListeningToChannel(unsigned int channel) {
stopListeningToChannels({ channel });
}
void ServerHandler::stopListeningToChannels(const QList< unsigned int > &channelIDs) {
if (channelIDs.isEmpty()) {
return;
}
MumbleProto::UserState mpus;
mpus.set_session(Global::get().uiSession);
for (unsigned int currentChannel : channelIDs) {
// The naming of the function is a bit unfortunate but what this does is to add
// the channel ID to the message field listening_channel_remove
mpus.add_listening_channel_remove(currentChannel);
}
sendMessage(mpus);
}
void ServerHandler::createChannel(unsigned int parent_id, const QString &name, const QString &description,
unsigned int position, bool temporary, unsigned int maxUsers) {
MumbleProto::ChannelState mpcs;
mpcs.set_parent(parent_id);
mpcs.set_name(u8(name));
mpcs.set_description(u8(description));
mpcs.set_position(static_cast< int >(position));
mpcs.set_temporary(temporary);
mpcs.set_max_users(maxUsers);
sendMessage(mpcs);
}
void ServerHandler::requestBanList() {
MumbleProto::BanList mpbl;
mpbl.set_query(true);
sendMessage(mpbl);
}
void ServerHandler::requestUserList() {
MumbleProto::UserList mpul;
sendMessage(mpul);
}
void ServerHandler::requestACL(unsigned int channel) {
MumbleProto::ACL mpacl;
mpacl.set_channel_id(channel);
mpacl.set_query(true);
sendMessage(mpacl);
}
void ServerHandler::registerUser(unsigned int uiSession) {
MumbleProto::UserState mpus;
mpus.set_session(uiSession);
mpus.set_user_id(0);
sendMessage(mpus);
}
void ServerHandler::kickBanUser(unsigned int uiSession, const QString &reason, bool ban) {
MumbleProto::UserRemove mpur;
mpur.set_session(uiSession);
mpur.set_reason(u8(reason));
mpur.set_ban(ban);
sendMessage(mpur);
}
void ServerHandler::sendUserTextMessage(unsigned int uiSession, const QString &message_) {
MumbleProto::TextMessage mptm;
mptm.add_session(uiSession);
mptm.set_message(u8(message_));
sendMessage(mptm);
}
void ServerHandler::sendChannelTextMessage(unsigned int channel, const QString &message_, bool tree) {
MumbleProto::TextMessage mptm;
if (tree) {
mptm.add_tree_id(channel);
} else {
mptm.add_channel_id(channel);
if (message_ == QString::fromUtf8(Global::get().ccHappyEaster + 10))
Global::get().bHappyEaster = true;
}
mptm.set_message(u8(message_));
sendMessage(mptm);
}
void ServerHandler::setUserComment(unsigned int uiSession, const QString &comment) {
MumbleProto::UserState mpus;
mpus.set_session(uiSession);
mpus.set_comment(u8(comment));
sendMessage(mpus);
}
void ServerHandler::setUserTexture(unsigned int uiSession, const QByteArray &qba) {
QByteArray texture;
if ((m_version >= Version::fromComponents(1, 2, 2)) || qba.isEmpty()) {
texture = qba;
} else {
QByteArray raw = qba;
QBuffer qb(&raw);
qb.open(QIODevice::ReadOnly);
QImageReader qir;
qir.setDecideFormatFromContent(false);
QByteArray fmt;
if (!RichTextImage::isValidImage(qba, fmt)) {
return;
}
qir.setFormat(fmt);
qir.setDevice(&qb);
QSize sz = qir.size();
const int TEX_MAX_WIDTH = 600;
const int TEX_MAX_HEIGHT = 60;
const int TEX_RGBA_SIZE = TEX_MAX_WIDTH * TEX_MAX_HEIGHT * 4;
sz.scale(TEX_MAX_WIDTH, TEX_MAX_HEIGHT, Qt::KeepAspectRatio);
qir.setScaledSize(sz);
QImage tex = qir.read();
if (tex.isNull()) {
return;
}
raw = QByteArray(TEX_RGBA_SIZE, 0);
QImage img(reinterpret_cast< unsigned char * >(raw.data()), TEX_MAX_WIDTH, TEX_MAX_HEIGHT,
QImage::Format_ARGB32);
QPainter imgp(&img);
imgp.setRenderHint(QPainter::Antialiasing);
imgp.setRenderHint(QPainter::TextAntialiasing);
imgp.setCompositionMode(QPainter::CompositionMode_SourceOver);
imgp.drawImage(0, 0, tex);
texture = qCompress(QByteArray(reinterpret_cast< const char * >(img.bits()), TEX_RGBA_SIZE));
}
MumbleProto::UserState mpus;
mpus.set_session(uiSession);
mpus.set_texture(blob(texture));
sendMessage(mpus);
if (!texture.isEmpty()) {
database->setBlob(sha1(texture), texture);
}
}
void ServerHandler::setTokens(const QStringList &tokens) {
MumbleProto::Authenticate msg;
foreach (const QString &qs, tokens)
msg.add_tokens(u8(qs));
sendMessage(msg);
}
void ServerHandler::removeChannel(unsigned int channel) {
MumbleProto::ChannelRemove mpcr;
mpcr.set_channel_id(channel);
sendMessage(mpcr);
}
void ServerHandler::addChannelLink(unsigned int channel, unsigned int link) {
MumbleProto::ChannelState mpcs;
mpcs.set_channel_id(channel);
mpcs.add_links_add(link);
sendMessage(mpcs);
}
void ServerHandler::removeChannelLink(unsigned int channel, unsigned int link) {
MumbleProto::ChannelState mpcs;
mpcs.set_channel_id(channel);
mpcs.add_links_remove(link);
sendMessage(mpcs);
}
void ServerHandler::requestChannelPermissions(unsigned int channel) {
MumbleProto::PermissionQuery mppq;
mppq.set_channel_id(channel);
sendMessage(mppq);
}
void ServerHandler::setSelfMuteDeafState(bool mute, bool deaf) {
MumbleProto::UserState mpus;
mpus.set_self_mute(mute);
mpus.set_self_deaf(deaf);
sendMessage(mpus);
}
void ServerHandler::announceRecordingState(bool recording) {
MumbleProto::UserState mpus;
mpus.set_recording(recording);
sendMessage(mpus);
}
QUrl ServerHandler::getServerURL(bool withPassword) const {
QUrl url;
url.setScheme(QLatin1String("mumble"));
url.setHost(qsHostName);
if (usPort != DEFAULT_MUMBLE_PORT) {
url.setPort(usPort);
}
url.setUserName(qsUserName);
if (withPassword && !qsPassword.isEmpty()) {
url.setPassword(qsPassword);
}
return url;
}