From 9f6aae35468ccb0355901b33ec8311a936761066 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Sun, 12 Jan 2025 16:23:37 +0100 Subject: [PATCH 1/3] REFAC(server): Avoid locking, if token list is empty --- src/murmur/Messages.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/murmur/Messages.cpp b/src/murmur/Messages.cpp index afb96c4b70..2679440aeb 100644 --- a/src/murmur/Messages.cpp +++ b/src/murmur/Messages.cpp @@ -101,6 +101,10 @@ class TemporaryAccessTokenHelper { public: TemporaryAccessTokenHelper(ServerUser *affectedUser, const QStringList &tokens, Server *server) : affectedUser(affectedUser), qslTemporaryTokens(tokens), server(server) { + if (tokens.empty()) { + return; + } + // Add the temporary tokens QMutableStringListIterator it(this->qslTemporaryTokens); From ffd1edadbcb8ea5c3331b7b56e5ead9cead2f908 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Sun, 12 Jan 2025 16:23:50 +0100 Subject: [PATCH 2/3] FEAT(protocol): Most msg now support temp. access tokens Temorary access tokens can be used to increase the permissions of the acting user while processing this specific message. These tokens won't be persisted on the server. Previously, only the UserState message supported temporary access tokens, but there is no reason why other messages shouldn't support them as well. Those messages that still don't support temporary access tokens are those that (typically) don't require any permission checks for processing on the server anyway. --- src/Mumble.proto | 16 + src/mumble/ServerHandler.cpp | 141 +++- src/mumble/ServerHandler.h | 44 +- src/mumble/back.cpp | 1278 ++++++++++++++++++++++++++++++++++ src/mumble/back.h | 223 ++++++ src/murmur/Messages.cpp | 57 ++ 6 files changed, 1709 insertions(+), 50 deletions(-) create mode 100644 src/mumble/back.cpp create mode 100644 src/mumble/back.h diff --git a/src/Mumble.proto b/src/Mumble.proto index 169c18a692..60ac977178 100644 --- a/src/Mumble.proto +++ b/src/Mumble.proto @@ -123,6 +123,8 @@ message ServerSync { // a channel has been removed and clients should be notified. message ChannelRemove { required uint32 channel_id = 1; + // A list of temporary access tokens to be respected when processing this request. + repeated string temporary_access_tokens = 2; } // Used to communicate channel properties between the client and the server. @@ -159,6 +161,8 @@ message ChannelState { optional bool is_enter_restricted = 12; // Whether the receiver of this msg is considered to be able to enter this channel optional bool can_enter = 13; + // A list of temporary access tokens to be respected when processing this request. + repeated string temporary_access_tokens = 14; } // Used to communicate user leaving or being kicked. May be sent by the client @@ -175,6 +179,8 @@ message UserRemove { optional string reason = 3; // True if the kick should result in a ban. optional bool ban = 4; + // A list of temporary access tokens to be respected when processing this request. + repeated string temporary_access_tokens = 5; } // Sent by the server when it communicates new and changed users to client. @@ -266,6 +272,8 @@ message BanList { // True if the server should return the list, false if it should replace old // ban list with the one provided. optional bool query = 2 [default = false]; + // A list of temporary access tokens to be respected when processing this request. + repeated string temporary_access_tokens = 3; } // Used to send and broadcast text messages. @@ -282,6 +290,8 @@ message TextMessage { repeated uint32 tree_id = 4; // The UTF-8 encoded message. May be HTML if the server allows. required string message = 5; + // A list of temporary access tokens to be respected when processing this request. + repeated string temporary_access_tokens = 6; } message PermissionDenied { @@ -374,6 +384,8 @@ message ACL { repeated ChanACL acls = 4; // True if the message is a query for ACLs instead of setting them. optional bool query = 5 [default = false]; + // A list of temporary access tokens to be respected when processing this request. + repeated string temporary_access_tokens = 6; } // Client may use this message to refresh its registered user information. The @@ -449,6 +461,8 @@ message UserList { } // A list of registered users. repeated User users = 1; + // A list of temporary access tokens to be respected when processing this request. + repeated string temporary_access_tokens = 2; } // Sent by the client when it wants to register or clear whisper targets. @@ -553,6 +567,8 @@ message UserStats { // True if the user has a strong certificate. optional bool strong_certificate = 18 [default = false]; optional bool opus = 19 [default = false]; + // A list of temporary access tokens to be respected when processing this request. + repeated string temporary_access_tokens = 20; } // Used by the client to request binary data from the server. By default large diff --git a/src/mumble/ServerHandler.cpp b/src/mumble/ServerHandler.cpp index 3a98b0a44b..0a198ca50f 100644 --- a/src/mumble/ServerHandler.cpp +++ b/src/mumble/ServerHandler.cpp @@ -879,17 +879,16 @@ bool ServerHandler::isStrong() const { return bStrong; } -void ServerHandler::requestUserStats(unsigned int uiSession, bool statsOnly) { +void ServerHandler::requestUserStats(unsigned int uiSession, bool statsOnly, const QStringList &temporaryAccessTokens) { 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; + for (const QString &token : temporaryAccessTokens) { + mpus.add_temporary_access_tokens(token.toUtf8().constData()); + } - joinChannel(uiSession, channel, EMPTY); + sendMessage(mpus); } void ServerHandler::joinChannel(unsigned int uiSession, unsigned int channel, @@ -898,18 +897,19 @@ void ServerHandler::joinChannel(unsigned int uiSession, unsigned int channel, mpus.set_session(uiSession); mpus.set_channel_id(channel); - foreach (const QString &tmpToken, temporaryAccessTokens) { - mpus.add_temporary_access_tokens(tmpToken.toUtf8().constData()); + for (const QString &token : temporaryAccessTokens) { + mpus.add_temporary_access_tokens(token.toUtf8().constData()); } sendMessage(mpus); } -void ServerHandler::startListeningToChannel(unsigned int channel) { - startListeningToChannels({ channel }); +void ServerHandler::startListeningToChannel(unsigned int channel, const QStringList &temporaryAccessTokens) { + startListeningToChannels({ channel }, temporaryAccessTokens); } -void ServerHandler::startListeningToChannels(const QList< unsigned int > &channelIDs) { +void ServerHandler::startListeningToChannels(const QList< unsigned int > &channelIDs, + const QStringList &temporaryAccessTokens) { if (channelIDs.isEmpty()) { return; } @@ -923,14 +923,19 @@ void ServerHandler::startListeningToChannels(const QList< unsigned int > &channe mpus.add_listening_channel_add(currentChannel); } + for (const QString &token : temporaryAccessTokens) { + mpus.add_temporary_access_tokens(token.toUtf8().constData()); + } + sendMessage(mpus); } -void ServerHandler::stopListeningToChannel(unsigned int channel) { - stopListeningToChannels({ channel }); +void ServerHandler::stopListeningToChannel(unsigned int channel, const QStringList &temporaryAccessTokens) { + stopListeningToChannels({ channel }, temporaryAccessTokens); } -void ServerHandler::stopListeningToChannels(const QList< unsigned int > &channelIDs) { +void ServerHandler::stopListeningToChannels(const QList< unsigned int > &channelIDs, + const QStringList &temporaryAccessTokens) { if (channelIDs.isEmpty()) { return; } @@ -944,11 +949,16 @@ void ServerHandler::stopListeningToChannels(const QList< unsigned int > &channel mpus.add_listening_channel_remove(currentChannel); } + for (const QString &token : temporaryAccessTokens) { + mpus.add_temporary_access_tokens(token.toUtf8().constData()); + } + sendMessage(mpus); } void ServerHandler::createChannel(unsigned int parent_id, const QString &name, const QString &description, - unsigned int position, bool temporary, unsigned int maxUsers) { + unsigned int position, bool temporary, unsigned int maxUsers, + const QStringList &temporaryAccessTokens) { MumbleProto::ChannelState mpcs; mpcs.set_parent(parent_id); mpcs.set_name(u8(name)); @@ -956,71 +966,121 @@ void ServerHandler::createChannel(unsigned int parent_id, const QString &name, c mpcs.set_position(static_cast< int >(position)); mpcs.set_temporary(temporary); mpcs.set_max_users(maxUsers); + + for (const QString &token : temporaryAccessTokens) { + mpcs.add_temporary_access_tokens(token.toUtf8().constData()); + } + sendMessage(mpcs); } -void ServerHandler::requestBanList() { +void ServerHandler::requestBanList(const QStringList &temporaryAccessTokens) { MumbleProto::BanList mpbl; mpbl.set_query(true); + + for (const QString &token : temporaryAccessTokens) { + mpbl.add_temporary_access_tokens(token.toUtf8().constData()); + } + sendMessage(mpbl); } -void ServerHandler::requestUserList() { +void ServerHandler::requestUserList(const QStringList &temporaryAccessTokens) { MumbleProto::UserList mpul; + + for (const QString &token : temporaryAccessTokens) { + mpul.add_temporary_access_tokens(token.toUtf8().constData()); + } + sendMessage(mpul); } -void ServerHandler::requestACL(unsigned int channel) { +void ServerHandler::requestACL(unsigned int channel, const QStringList &temporaryAccessTokens) { MumbleProto::ACL mpacl; mpacl.set_channel_id(channel); mpacl.set_query(true); + + for (const QString &token : temporaryAccessTokens) { + mpacl.add_temporary_access_tokens(token.toUtf8().constData()); + } + sendMessage(mpacl); } -void ServerHandler::registerUser(unsigned int uiSession) { +void ServerHandler::registerUser(unsigned int uiSession, const QStringList &temporaryAccessTokens) { MumbleProto::UserState mpus; mpus.set_session(uiSession); mpus.set_user_id(0); + + for (const QString &token : temporaryAccessTokens) { + mpus.add_temporary_access_tokens(token.toUtf8().constData()); + } + sendMessage(mpus); } -void ServerHandler::kickBanUser(unsigned int uiSession, const QString &reason, bool ban) { +void ServerHandler::kickBanUser(unsigned int uiSession, const QString &reason, bool ban, + const QStringList &temporaryAccessTokens) { MumbleProto::UserRemove mpur; mpur.set_session(uiSession); mpur.set_reason(u8(reason)); mpur.set_ban(ban); + + for (const QString &token : temporaryAccessTokens) { + mpur.add_temporary_access_tokens(token.toUtf8().constData()); + } + sendMessage(mpur); } -void ServerHandler::sendUserTextMessage(unsigned int uiSession, const QString &message_) { +void ServerHandler::sendUserTextMessage(unsigned int uiSession, const QString &message, + const QStringList &temporaryAccessTokens) { MumbleProto::TextMessage mptm; mptm.add_session(uiSession); - mptm.set_message(u8(message_)); + mptm.set_message(u8(message)); + + for (const QString &token : temporaryAccessTokens) { + mptm.add_temporary_access_tokens(token.toUtf8().constData()); + } + sendMessage(mptm); } -void ServerHandler::sendChannelTextMessage(unsigned int channel, const QString &message_, bool tree) { +void ServerHandler::sendChannelTextMessage(unsigned int channel, const QString &message, bool tree, + const QStringList &temporaryAccessTokens) { MumbleProto::TextMessage mptm; if (tree) { mptm.add_tree_id(channel); } else { mptm.add_channel_id(channel); - if (message_ == QString::fromUtf8(Global::get().ccHappyEaster + 10)) + if (message == QString::fromUtf8(Global::get().ccHappyEaster + 10)) Global::get().bHappyEaster = true; } - mptm.set_message(u8(message_)); + mptm.set_message(u8(message)); + + for (const QString &token : temporaryAccessTokens) { + mptm.add_temporary_access_tokens(token.toUtf8().constData()); + } + sendMessage(mptm); } -void ServerHandler::setUserComment(unsigned int uiSession, const QString &comment) { +void ServerHandler::setUserComment(unsigned int uiSession, const QString &comment, + const QStringList &temporaryAccessTokens) { MumbleProto::UserState mpus; mpus.set_session(uiSession); mpus.set_comment(u8(comment)); + + for (const QString &token : temporaryAccessTokens) { + mpus.add_temporary_access_tokens(token.toUtf8().constData()); + } + sendMessage(mpus); } -void ServerHandler::setUserTexture(unsigned int uiSession, const QByteArray &qba) { +void ServerHandler::setUserTexture(unsigned int uiSession, const QByteArray &qba, + const QStringList &temporaryAccessTokens) { QByteArray texture; if ((m_version >= Version::fromComponents(1, 2, 2)) || qba.isEmpty()) { @@ -1070,6 +1130,11 @@ void ServerHandler::setUserTexture(unsigned int uiSession, const QByteArray &qba MumbleProto::UserState mpus; mpus.set_session(uiSession); mpus.set_texture(blob(texture)); + + for (const QString &token : temporaryAccessTokens) { + mpus.add_temporary_access_tokens(token.toUtf8().constData()); + } + sendMessage(mpus); if (!texture.isEmpty()) { @@ -1084,23 +1149,39 @@ void ServerHandler::setTokens(const QStringList &tokens) { sendMessage(msg); } -void ServerHandler::removeChannel(unsigned int channel) { +void ServerHandler::removeChannel(unsigned int channel, const QStringList &temporaryAccessTokens) { MumbleProto::ChannelRemove mpcr; mpcr.set_channel_id(channel); + + for (const QString &token : temporaryAccessTokens) { + mpcr.add_temporary_access_tokens(token.toUtf8().constData()); + } + sendMessage(mpcr); } -void ServerHandler::addChannelLink(unsigned int channel, unsigned int link) { +void ServerHandler::addChannelLink(unsigned int channel, unsigned int link, const QStringList &temporaryAccessTokens) { MumbleProto::ChannelState mpcs; mpcs.set_channel_id(channel); mpcs.add_links_add(link); + + for (const QString &token : temporaryAccessTokens) { + mpcs.add_temporary_access_tokens(token.toUtf8().constData()); + } + sendMessage(mpcs); } -void ServerHandler::removeChannelLink(unsigned int channel, unsigned int link) { +void ServerHandler::removeChannelLink(unsigned int channel, unsigned int link, + const QStringList &temporaryAccessTokens) { MumbleProto::ChannelState mpcs; mpcs.set_channel_id(channel); mpcs.add_links_remove(link); + + for (const QString &token : temporaryAccessTokens) { + mpcs.add_temporary_access_tokens(token.toUtf8().constData()); + } + sendMessage(mpcs); } diff --git a/src/mumble/ServerHandler.h b/src/mumble/ServerHandler.h index 3c3c424416..57a4a63bdb 100644 --- a/src/mumble/ServerHandler.h +++ b/src/mumble/ServerHandler.h @@ -152,28 +152,32 @@ class ServerHandler : public QThread { MUMBLE_ALL_TCP_MESSAGES #undef PROCESS_MUMBLE_TCP_MESSAGE - void requestUserStats(unsigned int uiSession, bool statsOnly); - void joinChannel(unsigned int uiSession, unsigned int channel); - void joinChannel(unsigned int uiSession, unsigned int channel, const QStringList &temporaryAccessTokens); - void startListeningToChannel(unsigned int channel); - void startListeningToChannels(const QList< unsigned int > &channelIDs); - void stopListeningToChannel(unsigned int channel); - void stopListeningToChannels(const QList< unsigned int > &channelIDs); + void requestUserStats(unsigned int uiSession, bool statsOnly, const QStringList &temporaryAccessTokens = {}); + void joinChannel(unsigned int uiSession, unsigned int channel, const QStringList &temporaryAccessTokens = {}); + void startListeningToChannel(unsigned int channel, const QStringList &temporaryAccessTokens = {}); + void startListeningToChannels(const QList< unsigned int > &channelIDs, + const QStringList &temporaryAccessTokens = {}); + void stopListeningToChannel(unsigned int channel, const QStringList &temporaryAccessTokens = {}); + void stopListeningToChannels(const QList< unsigned int > &channelIDs, + const QStringList &temporaryAccessTokens = {}); void createChannel(unsigned int parent_id, const QString &name, const QString &description, unsigned int position, - bool temporary, unsigned int maxUsers); - void requestBanList(); - void requestUserList(); - void requestACL(unsigned int channel); - void registerUser(unsigned int uiSession); - void kickBanUser(unsigned int uiSession, const QString &reason, bool ban); - void sendUserTextMessage(unsigned int uiSession, const QString &message_); - void sendChannelTextMessage(unsigned int channel, const QString &message_, bool tree); - void setUserComment(unsigned int uiSession, const QString &comment); - void setUserTexture(unsigned int uiSession, const QByteArray &qba); + bool temporary, unsigned int maxUsers, const QStringList &temporaryAccessTokens = {}); + void requestBanList(const QStringList &temporaryAccessTokens = {}); + void requestUserList(const QStringList &temporaryAccessTokens = {}); + void requestACL(unsigned int channel, const QStringList &temporaryAccessTokens = {}); + void registerUser(unsigned int uiSession, const QStringList &temporaryAccessTokens = {}); + void kickBanUser(unsigned int uiSession, const QString &reason, bool ban, + const QStringList &temporaryAccessTokens = {}); + void sendUserTextMessage(unsigned int uiSession, const QString &message_, + const QStringList &temporaryAccessTokens = {}); + void sendChannelTextMessage(unsigned int channel, const QString &message_, bool tree, + const QStringList &temporaryAccessTokens = {}); + void setUserComment(unsigned int uiSession, const QString &comment, const QStringList &temporaryAccessTokens = {}); + void setUserTexture(unsigned int uiSession, const QByteArray &qba, const QStringList &temporaryAccessTokens = {}); void setTokens(const QStringList &tokens); - void removeChannel(unsigned int channel); - void addChannelLink(unsigned int channel, unsigned int link); - void removeChannelLink(unsigned int channel, unsigned int link); + void removeChannel(unsigned int channel, const QStringList &temporaryAccessTokens = {}); + void addChannelLink(unsigned int channel, unsigned int link, const QStringList &temporaryAccessTokens = {}); + void removeChannelLink(unsigned int channel, unsigned int link, const QStringList &temporaryAccessTokens = {}); void requestChannelPermissions(unsigned int channel); void setSelfMuteDeafState(bool mute, bool deaf); void announceRecordingState(bool recording); diff --git a/src/mumble/back.cpp b/src/mumble/back.cpp new file mode 100644 index 0000000000..2cc36c7552 --- /dev/null +++ b/src/mumble/back.cpp @@ -0,0 +1,1278 @@ +// Copyright 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 . + +#include +#include + +#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 +#include +#include +#include +#include + +#include + +#include + +#ifdef Q_OS_WIN +// 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 +# endif +# include +# include +# include +#else +# if defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) +# include +# endif +# include +# include +#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 >= QT_VERSION_CHECK(6, 3, 0) + qtsSock->setProtocol(QSsl::TlsV1_2OrLater); +#else + qtsSock->setProtocol(QSsl::TlsV1_0OrLater); +#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(), static_cast< int >(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; + + // 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(); + + 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, const QStringList &temporaryAccessTokens) { + MumbleProto::UserStats mpus; + mpus.set_session(uiSession); + mpus.set_stats_only(statsOnly); + + for (const QString &token : temporaryAccessTokens) { + mpus.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mpus); +} + +void ServerHandler::joinChannel(unsigned int uiSession, unsigned int channel, + const QStringList &temporaryAccessTokens) { + MumbleProto::UserState mpus; + mpus.set_session(uiSession); + mpus.set_channel_id(channel); + + for (const QString &token : temporaryAccessTokens) { + mpus.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mpus); +} + +void ServerHandler::startListeningToChannel(unsigned int channel, const QStringList &temporaryAccessTokens) { + startListeningToChannels({ channel }, temporaryAccessTokens); +} + +void ServerHandler::startListeningToChannels(const QList< unsigned int > &channelIDs, + const QStringList &temporaryAccessTokens) { + 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); + } + + for (const QString &token : temporaryAccessTokens) { + mpus.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mpus); +} + +void ServerHandler::stopListeningToChannel(unsigned int channel, const QStringList &temporaryAccessTokens) { + stopListeningToChannels({ channel }, temporaryAccessTokens); +} + +void ServerHandler::stopListeningToChannels(const QList< unsigned int > &channelIDs, + const QStringList &temporaryAccessTokens) { + 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); + } + + for (const QString &token : temporaryAccessTokens) { + mpus.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mpus); +} + +void ServerHandler::createChannel(unsigned int parent_id, const QString &name, const QString &description, + unsigned int position, bool temporary, unsigned int maxUsers, + const QStringList &temporaryAccessTokens) { + 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); + + for (const QString &token : temporaryAccessTokens) { + mpcs.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mpcs); +} + +void ServerHandler::requestBanList(const QStringList &temporaryAccessTokens) { + MumbleProto::BanList mpbl; + mpbl.set_query(true); + + for (const QString &token : temporaryAccessTokens) { + mpbl.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mpbl); +} + +void ServerHandler::requestUserList(const QStringList &temporaryAccessTokens) { + MumbleProto::UserList mpul; + + for (const QString &token : temporaryAccessTokens) { + mpul.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mpul); +} + +void ServerHandler::requestACL(unsigned int channel, const QStringList &temporaryAccessTokens) { + MumbleProto::ACL mpacl; + mpacl.set_channel_id(channel); + mpacl.set_query(true); + + for (const QString &token : temporaryAccessTokens) { + mpacl.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mpacl); +} + +void ServerHandler::registerUser(unsigned int uiSession, const QStringList &temporaryAccessTokens) { + MumbleProto::UserState mpus; + mpus.set_session(uiSession); + mpus.set_user_id(0); + + for (const QString &token : temporaryAccessTokens) { + mpus.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mpus); +} + +void ServerHandler::kickBanUser(unsigned int uiSession, const QString &reason, bool ban, + const QStringList &temporaryAccessTokens) { + MumbleProto::UserRemove mpur; + mpur.set_session(uiSession); + mpur.set_reason(u8(reason)); + mpur.set_ban(ban); + + for (const QString &token : temporaryAccessTokens) { + mpur.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mpur); +} + +void ServerHandler::sendUserTextMessage(unsigned int uiSession, const QString &message, + const QStringList &temporaryAccessTokens) { + MumbleProto::TextMessage mptm; + mptm.add_session(uiSession); + mptm.set_message(u8(message)); + + for (const QString &token : temporaryAccessTokens) { + mptm.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mptm); +} + +void ServerHandler::sendChannelTextMessage(unsigned int channel, const QString &message, bool tree, + const QStringList &temporaryAccessTokens) { + 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)); + + for (const QString &token : temporaryAccessTokens) { + mptm.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mptm); +} + +void ServerHandler::setUserComment(unsigned int uiSession, const QString &comment, + const QStringList &temporaryAccessTokens) { + MumbleProto::UserState mpus; + mpus.set_session(uiSession); + mpus.set_comment(u8(comment)); + + for (const QString &token : temporaryAccessTokens) { + mpus.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mpus); +} + +void ServerHandler::setUserTexture(unsigned int uiSession, const QByteArray &qba, + const QStringList &temporaryAccessTokens) { + 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)); + + for (const QString &token : temporaryAccessTokens) { + mpus.add_temporary_access_tokens(token.toUtf8().constData()); + } + + 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, const QStringList &temporaryAccessTokens) { + MumbleProto::ChannelRemove mpcr; + mpcr.set_channel_id(channel); + + for (const QString &token : temporaryAccessTokens) { + mpcr.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mpcr); +} + +void ServerHandler::addChannelLink(unsigned int channel, unsigned int link, const QStringList &temporaryAccessTokens) { + MumbleProto::ChannelState mpcs; + mpcs.set_channel_id(channel); + mpcs.add_links_add(link); + + for (const QString &token : temporaryAccessTokens) { + mpcs.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mpcs); +} + +void ServerHandler::addChannelLinks(const unsigned int channelCount, const unsigned int *channelSet, + const QStringList &temporaryAccessTokens) { + if (channelCount < 2) { + return; + } + MumbleProto::ChannelState mpcs; + mpcs.set_channel_id(channelSet[0]); + for (unsigned int i = 1; i < channelCount; i++) { + mpcs.add_links_add(channelSet[i]); + } + + for (const QString &token : temporaryAccessTokens) { + mpcs.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mpcs); +} + +void ServerHandler::removeChannelLink(unsigned int channel, unsigned int link, + const QStringList &temporaryAccessTokens) { + MumbleProto::ChannelState mpcs; + mpcs.set_channel_id(channel); + mpcs.add_links_remove(link); + + for (const QString &token : temporaryAccessTokens) { + mpcs.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mpcs); +} + +void ServerHandler::removeChannelLinks(unsigned int channel, const unsigned int channelCount, + const unsigned int *channelSet, const QStringList &temporaryAccessTokens) { + if (channelCount < 1) { + return; + } + MumbleProto::ChannelState mpcs; + mpcs.set_channel_id(channel); + for (unsigned int i = 0; i < channelCount; i++) { + mpcs.add_links_remove(channelSet[i]); + } + + for (const QString &token : temporaryAccessTokens) { + mpcs.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mpcs); +} + +void ServerHandler::removeChannelLinks(const unsigned int channelCount, const unsigned int *channelSet, + const QStringList &temporaryAccessTokens) { + if (channelCount < 2) { + return; + } + MumbleProto::ChannelState mpcs; + unsigned int link = channelSet[0]; + for (unsigned int i = 1; i < channelCount; i++) { + mpcs.set_channel_id(link); + mpcs.add_links_remove(channelSet[i]); + } + + for (const QString &token : temporaryAccessTokens) { + mpcs.add_temporary_access_tokens(token.toUtf8().constData()); + } + + 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; +} diff --git a/src/mumble/back.h b/src/mumble/back.h new file mode 100644 index 0000000000..43da62230c --- /dev/null +++ b/src/mumble/back.h @@ -0,0 +1,223 @@ +// Copyright 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 . + +#ifndef MUMBLE_MUMBLE_SERVERHANDLER_H_ +#define MUMBLE_MUMBLE_SERVERHANDLER_H_ + +#include + +#ifdef Q_OS_WIN +# include "win.h" +#endif + +#ifndef Q_MOC_RUN +# include +# include +# include +# include +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SERVERSEND_EVENT 3501 + +#include "Mumble.pb.h" +#include "MumbleProtocol.h" +#include "ServerAddress.h" +#include "Timer.h" + +class Connection; +class Database; +class PacketDataStream; +class QUdpSocket; +class QSslSocket; +class VoiceRecorder; + +class ServerHandlerMessageEvent : public QEvent { +public: + Mumble::Protocol::TCPMessageType type; + QByteArray qbaMsg; + bool bFlush; + ServerHandlerMessageEvent(const QByteArray &msg, Mumble::Protocol::TCPMessageType type, bool flush = false); +}; + +typedef boost::shared_ptr< Connection > ConnectionPtr; + +class ServerHandler : public QThread { +private: + Q_OBJECT + Q_DISABLE_COPY(ServerHandler) + + Database *database; + + static QMutex nextConnectionIDMutex; + static int nextConnectionID; + +protected: + QString qsHostName; + QString qsUserName; + QString qsPassword; + unsigned short usPort; + unsigned short usResolvedPort; + bool bUdp; + bool bStrong; + int connectionID; + Mumble::Protocol::UDPPingEncoder< Mumble::Protocol::Role::Client > m_udpPingEncoder; + Mumble::Protocol::UDPDecoder< Mumble::Protocol::Role::Client > m_udpDecoder; + Mumble::Protocol::UDPDecoder< Mumble::Protocol::Role::Client > m_tcpTunnelDecoder; + + /// Flag indicating whether the server we are currently connected to has + /// finished synchronizing already. + bool serverSynchronized = false; + +#ifdef Q_OS_WIN + HANDLE hQoS; + DWORD dwFlowUDP; +#endif + + QHostAddress qhaRemote; + QHostAddress qhaLocal; + QUdpSocket *qusUdp; + QMutex qmUdp; + + void handleVoicePacket(const Mumble::Protocol::AudioData &audioData); + +public: + Timer tTimestamp; + int iInFlightTCPPings; + QTimer *tConnectionTimeoutTimer; + QList< QSslError > qlErrors; + QList< QSslCertificate > qscCert; + QSslCipher qscCipher; + ConnectionPtr cConnection; + QByteArray qbaDigest; + boost::shared_ptr< VoiceRecorder > recorder; + QSslSocket *qtsSock; + QList< ServerAddress > qlAddresses; + QHash< ServerAddress, QString > qhHostnames; + ServerAddress saTargetServer; + + Version::full_t m_version; + QString qsRelease; + QString qsOS; + QString qsOSVersion; + + /** + * A flag indicating whether this connection makes use of PFS or not. Note that this flag only has meaning, if the + * used version of Qt is >= 5.7. + */ + bool connectionUsesPerfectForwardSecrecy = false; + + boost::accumulators::accumulator_set< + double, boost::accumulators::stats< boost::accumulators::tag::mean, boost::accumulators::tag::variance, + boost::accumulators::tag::count > > + accTCP, accUDP, accClean; + + ServerHandler(); + ~ServerHandler(); + void setConnectionInfo(const QString &host, unsigned short port, const QString &username, const QString &pw); + void getConnectionInfo(QString &host, unsigned short &port, QString &username, QString &pw) const; + bool isStrong() const; + void customEvent(QEvent *evt) Q_DECL_OVERRIDE; + int getConnectionID() const; + + void setProtocolVersion(Version::full_t version); + + void sendProtoMessage(const ::google::protobuf::Message &msg, Mumble::Protocol::TCPMessageType type); + void sendMessage(const unsigned char *data, int len, bool force = false); + + /// @returns Whether this handler is currently connected to a server. + bool isConnected() const; + + /// @returns Whether the server this handler is currently connected to, has finished + /// synchronizing yet. + bool hasSynchronized() const; + + /// @param synchronized Whether the server has finished synchronization + void setServerSynchronized(bool synchronized); + +#define PROCESS_MUMBLE_TCP_MESSAGE(name, value) \ + void sendMessage(const MumbleProto::name &msg) { sendProtoMessage(msg, Mumble::Protocol::TCPMessageType::name); } + MUMBLE_ALL_TCP_MESSAGES +#undef PROCESS_MUMBLE_TCP_MESSAGE + + void requestUserStats(unsigned int uiSession, bool statsOnly, const QStringList &temporaryAccessTokens = {}); + void joinChannel(unsigned int uiSession, unsigned int channel, const QStringList &temporaryAccessTokens = {}); + void startListeningToChannel(unsigned int channel, const QStringList &temporaryAccessTokens = {}); + void startListeningToChannels(const QList< unsigned int > &channelIDs, + const QStringList &temporaryAccessTokens = {}); + void stopListeningToChannel(unsigned int channel, const QStringList &temporaryAccessTokens = {}); + void stopListeningToChannels(const QList< unsigned int > &channelIDs, + const QStringList &temporaryAccessTokens = {}); + void createChannel(unsigned int parent_id, const QString &name, const QString &description, unsigned int position, + bool temporary, unsigned int maxUsers, const QStringList &temporaryAccessTokens = {}); + void requestBanList(const QStringList &temporaryAccessTokens = {}); + void requestUserList(const QStringList &temporaryAccessTokens = {}); + void requestACL(unsigned int channel, const QStringList &temporaryAccessTokens = {}); + void registerUser(unsigned int uiSession, const QStringList &temporaryAccessTokens = {}); + void kickBanUser(unsigned int uiSession, const QString &reason, bool ban, + const QStringList &temporaryAccessTokens = {}); + void sendUserTextMessage(unsigned int uiSession, const QString &message_, + const QStringList &temporaryAccessTokens = {}); + void sendChannelTextMessage(unsigned int channel, const QString &message_, bool tree, + const QStringList &temporaryAccessTokens = {}); + void setUserComment(unsigned int uiSession, const QString &comment, const QStringList &temporaryAccessTokens = {}); + void setUserTexture(unsigned int uiSession, const QByteArray &qba, const QStringList &temporaryAccessTokens = {}); + void setTokens(const QStringList &tokens); + void removeChannel(unsigned int channel, const QStringList &temporaryAccessTokens = {}); + void addChannelLink(unsigned int channel, unsigned int link, const QStringList &temporaryAccessTokens = {}); + void addChannelLinks(const unsigned int channelCount, const unsigned int *channelSet, + const QStringList &temporaryAccessTokens = {}); + void removeChannelLink(unsigned int channel, unsigned int link, const QStringList &temporaryAccessTokens = {}); + void removeChannelLinks(const unsigned int channel, const unsigned int channelCount, const unsigned int *channelSet, + const QStringList &temporaryAccessTokens = {}); + void removeChannelLinks(const unsigned int channelCount, const unsigned int *channelSet, + const QStringList &temporaryAccessTokens = {}); + void requestChannelPermissions(unsigned int channel); + void setSelfMuteDeafState(bool mute, bool deaf); + void announceRecordingState(bool recording); + + /// Return connection information as a URL + QUrl getServerURL(bool withPassword = false) const; + + void disconnect(); + void run() Q_DECL_OVERRIDE; +signals: + void error(QAbstractSocket::SocketError, QString reason); + // This signal is basically the same as disconnected but it will be emitted + // *right before* disconnected is emitted. Thus this can be used by slots + // that need to block the disconnected signal from being emitted (using a + // direct connection) before they're done. + void aboutToDisconnect(QAbstractSocket::SocketError, QString reason); + void disconnected(QAbstractSocket::SocketError, QString reason); + void connected(); + void pingRequested(); +protected slots: + void message(Mumble::Protocol::TCPMessageType type, const QByteArray &); + void serverConnectionConnected(); + void serverConnectionTimeoutOnConnect(); + void serverConnectionStateChanged(QAbstractSocket::SocketState); + void serverConnectionClosed(QAbstractSocket::SocketError, const QString &); + void setSslErrors(const QList< QSslError > &); + void udpReady(); + void hostnameResolved(); +private slots: + void sendPingInternal(); +public slots: + void sendPing(); +}; + +typedef boost::shared_ptr< ServerHandler > ServerHandlerPtr; + +#endif diff --git a/src/murmur/Messages.cpp b/src/murmur/Messages.cpp index 2679440aeb..b83dd14883 100644 --- a/src/murmur/Messages.cpp +++ b/src/murmur/Messages.cpp @@ -659,6 +659,13 @@ void Server::msgBanList(ServerUser *uSource, MumbleProto::BanList &msg) { MSG_SETUP(ServerUser::Authenticated); + // Handle potential temporary access tokens + QStringList temporaryAccessTokens; + for (int i = 0; i < msg.temporary_access_tokens_size(); i++) { + temporaryAccessTokens << u8(msg.temporary_access_tokens(i)); + } + TemporaryAccessTokenHelper tempTokenHelper(uSource, temporaryAccessTokens, this); + QSet< Ban > previousBans, newBans; if (!hasPermission(uSource, qhChannels.value(0), ChanACL::Ban)) { PERM_DENIED(uSource, qhChannels.value(0), ChanACL::Ban); @@ -1197,6 +1204,13 @@ void Server::msgUserRemove(ServerUser *uSource, MumbleProto::UserRemove &msg) { MSG_SETUP(ServerUser::Authenticated); VICTIM_SETUP; + // Handle potential temporary access tokens + QStringList temporaryAccessTokens; + for (int i = 0; i < msg.temporary_access_tokens_size(); i++) { + temporaryAccessTokens << u8(msg.temporary_access_tokens(i)); + } + TemporaryAccessTokenHelper tempTokenHelper(uSource, temporaryAccessTokens, this); + msg.set_actor(uSource->uiSession); bool ban = msg.has_ban() && msg.ban(); @@ -1247,6 +1261,13 @@ void Server::msgChannelState(ServerUser *uSource, MumbleProto::ChannelState &msg RATELIMIT(uSource); } + // Handle potential temporary access tokens + QStringList temporaryAccessTokens; + for (int i = 0; i < msg.temporary_access_tokens_size(); i++) { + temporaryAccessTokens << u8(msg.temporary_access_tokens(i)); + } + TemporaryAccessTokenHelper tempTokenHelper(uSource, temporaryAccessTokens, this); + // Check if the parent exists if (msg.has_parent()) { p = qhChannels.value(msg.parent()); @@ -1521,6 +1542,13 @@ void Server::msgChannelRemove(ServerUser *uSource, MumbleProto::ChannelRemove &m MSG_SETUP(ServerUser::Authenticated); + // Handle potential temporary access tokens + QStringList temporaryAccessTokens; + for (int i = 0; i < msg.temporary_access_tokens_size(); i++) { + temporaryAccessTokens << u8(msg.temporary_access_tokens(i)); + } + TemporaryAccessTokenHelper tempTokenHelper(uSource, temporaryAccessTokens, this); + Channel *c = qhChannels.value(msg.channel_id()); if (!c) return; @@ -1539,6 +1567,14 @@ void Server::msgTextMessage(ServerUser *uSource, MumbleProto::TextMessage &msg) ZoneScoped; MSG_SETUP(ServerUser::Authenticated); + + // Handle potential temporary access tokens + QStringList temporaryAccessTokens; + for (int i = 0; i < msg.temporary_access_tokens_size(); i++) { + temporaryAccessTokens << u8(msg.temporary_access_tokens(i)); + } + TemporaryAccessTokenHelper tempTokenHelper(uSource, temporaryAccessTokens, this); + QMutexLocker qml(&qmCache); // For signal userTextMessage (RPC consumers) @@ -1755,6 +1791,13 @@ void Server::msgACL(ServerUser *uSource, MumbleProto::ACL &msg) { if (!c) return; + // Handle potential temporary access tokens + QStringList temporaryAccessTokens; + for (int i = 0; i < msg.temporary_access_tokens_size(); i++) { + temporaryAccessTokens << u8(msg.temporary_access_tokens(i)); + } + TemporaryAccessTokenHelper tempTokenHelper(uSource, temporaryAccessTokens, this); + // For changing channel properties (the 'Write') ACL we allow two things: // 1) As per regular ACL propagating mechanism, we check if the user has been // granted Write in the channel they try to edit @@ -2106,6 +2149,13 @@ void Server::msgUserList(ServerUser *uSource, MumbleProto::UserList &msg) { MSG_SETUP(ServerUser::Authenticated); + // Handle potential temporary access tokens + QStringList temporaryAccessTokens; + for (int i = 0; i < msg.temporary_access_tokens_size(); i++) { + temporaryAccessTokens << u8(msg.temporary_access_tokens(i)); + } + TemporaryAccessTokenHelper tempTokenHelper(uSource, temporaryAccessTokens, this); + // The register permission is required on the root channel to be allowed to // view the registered users. if (!hasPermission(uSource, qhChannels.value(0), ChanACL::Register)) { @@ -2252,6 +2302,13 @@ void Server::msgUserStats(ServerUser *uSource, MumbleProto::UserStats &msg) { bool extend = (uSource == pDstServerUser) || hasPermission(uSource, qhChannels.value(0), ChanACL::Register); + // Handle potential temporary access tokens + QStringList temporaryAccessTokens; + for (int i = 0; i < msg.temporary_access_tokens_size(); i++) { + temporaryAccessTokens << u8(msg.temporary_access_tokens(i)); + } + TemporaryAccessTokenHelper tempTokenHelper(uSource, temporaryAccessTokens, this); + if (!extend && !hasPermission(uSource, pDstServerUser->cChannel, ChanACL::Enter)) { PERM_DENIED(uSource, pDstServerUser->cChannel, ChanACL::Enter); return; From af9557e678802c8729aab92b5180eaa3889d2432 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abdullah=20=C3=96mer=20Yama=C3=A7?= Date: Tue, 21 May 2024 12:10:07 +0300 Subject: [PATCH 3/3] FEAT(client): Introduced new mumble API This patch introduces a new API to link, unlink, start to listen, stop the listen the channels and send the messages to the plugins --- plugins/MumblePlugin.h | 128 +++++++++- src/mumble/API.h | 27 +++ src/mumble/API_v_1_x_x.cpp | 424 +++++++++++++++++++++++++++++++++ src/mumble/MumbleAPI_structs.h | 11 + src/mumble/Plugin.cpp | 3 + src/mumble/ServerHandler.cpp | 55 +++++ src/mumble/ServerHandler.h | 6 + 7 files changed, 653 insertions(+), 1 deletion(-) diff --git a/plugins/MumblePlugin.h b/plugins/MumblePlugin.h index 8a70696d41..15dfc7b8f6 100644 --- a/plugins/MumblePlugin.h +++ b/plugins/MumblePlugin.h @@ -43,7 +43,7 @@ # define MUMBLE_PLUGIN_API_MAJOR_MACRO 1 # endif # ifndef MUMBLE_PLUGIN_API_MINOR_MACRO -# define MUMBLE_PLUGIN_API_MINOR_MACRO 2 +# define MUMBLE_PLUGIN_API_MINOR_MACRO 3 # endif # ifndef MUMBLE_PLUGIN_API_PATCH_MACRO # define MUMBLE_PLUGIN_API_PATCH_MACRO 0 @@ -1515,6 +1515,41 @@ struct MUMBLE_API_STRUCT_NAME { mumble_channelid_t channelID, const char **description); +# if SELECTED_API_VERSION >= MUMBLE_PLUGIN_VERSION_CHECK(1, 3, 0) + + /** + * Checks whether the two channels are linked to each other. + * + * @param callerID The ID of the plugin calling this function + * @param connection The ID of the server-connection + * @param firstID The ID of the first channel + * @param secondID The ID of the second channel + * @param[out] linked A pointer to where the result of the check shall be written to + * @returns The error code. If everything went well, STATUS_OK will be returned. + */ + mumble_error_t(MUMBLE_PLUGIN_CALLING_CONVENTION *isChannelLinkedTo)(mumble_plugin_id_t callerID, + mumble_connection_t connection, + mumble_channelid_t firstID, + mumble_channelid_t secondID, bool *linked); + + /** + * Gets the set of channels the given channel is linked to. + * + * @param callerID The ID of the plugin calling this function + * @param connection The ID of the server-connection + * @param channelID The ID of the channel + * @param[out] linkedChannels The set of channel IDs linked to the channelID + * @param[out] linkCount The amount of linked channels + * @returns The error code. If everything went well, STATUS_OK will be returned. Only then the passed pointers + * may be accessed. + */ + mumble_error_t(MUMBLE_PLUGIN_CALLING_CONVENTION *getLinkedChannels)(mumble_plugin_id_t callerID, + mumble_connection_t connection, + mumble_channelid_t channelID, + mumble_channelid_t **linkedChannels, + size_t *linkCount); + +# endif // -------- Request functions -------- @@ -1608,7 +1643,98 @@ struct MUMBLE_API_STRUCT_NAME { mumble_connection_t connection, const char *comment); +# if SELECTED_API_VERSION >= MUMBLE_PLUGIN_VERSION_CHECK(1, 3, 0) + + /** + * Requests Mumble to link all channels in the given set to each other. + * + * @param callerID The ID of the plugin calling this function + * @param connection The ID of the server-connection + * @param channelSet The set of channel IDs to link + * @param channelCount The number of elements in the channel list + * @returns The error code. If everything went well, STATUS_OK will be returned. + */ + mumble_error_t(MUMBLE_PLUGIN_CALLING_CONVENTION *requestLinkChannels)(mumble_plugin_id_t callerID, + mumble_connection_t connection, + mumble_channelid_t *channelSet, + size_t channelCount); + /** + * Requests Mumble to remove any existing links between the provided channel and channels in the provided set. + * + * @param callerID The ID of the plugin calling this function + * @param connection The ID of the server-connection + * @param channelID The ID of the channel to unlink + * @param channelSet The set of channel IDs to remove link from the channelID + * @param channelCount The number of elements in the channel set + * @returns The error code. If everything went well, STATUS_OK will be returned. + */ + mumble_error_t(MUMBLE_PLUGIN_CALLING_CONVENTION *requestUnlinkChannels)(mumble_plugin_id_t callerID, + mumble_connection_t connection, + mumble_channelid_t channelID, + mumble_channelid_t *channelSet, + size_t channelCount); + + + /** + * Requests Mumble to remove all links between the provided channels. + * + * @param callerID The ID of the plugin calling this function + * @param connection The ID of the server-connection + * @param channelSet The set of channel IDs to remove link + * @param channelCount The number of elements in the channel set + * @returns The error code. If everything went well, STATUS_OK will be returned. + */ + mumble_error_t(MUMBLE_PLUGIN_CALLING_CONVENTION *requestUnlinkChannelSet)(mumble_plugin_id_t callerID, + mumble_connection_t connection, + mumble_channelid_t *channelSet, + size_t channelCount); + + + /** + * Starts to listen channel set + * + * @param callerID The ID of the plugin calling this function + * @param connection The ID of the server-connection + * @param channelSet The set of channel IDs to listen + * @param channelCount The number of elements in the channel set + * @returns The error code. If everything went well, STATUS_OK will be returned. + */ + mumble_error_t(MUMBLE_PLUGIN_CALLING_CONVENTION *requestStartListeningToChannels)(mumble_plugin_id_t callerID, + mumble_connection_t connection, + mumble_channelid_t *channelSet, + size_t channelCount); + + /** + * Stops to listen channel set + * + * @param callerID The ID of the plugin calling this function + * @param connection The ID of the server-connection + * @param channelSet The set of channel IDs to stop listen + * @param channelCount The number of elements in the channel set + * @returns The error code. If everything went well, STATUS_OK will be returned. + */ + mumble_error_t(MUMBLE_PLUGIN_CALLING_CONVENTION *requestStopListeningToChannels)(mumble_plugin_id_t callerID, + mumble_connection_t connection, + mumble_channelid_t *channelSet, + size_t channelCount); + + /** + * Send text message to the given users + * + * @param callerID The ID of the plugin calling this function + * @param connection The ID of the server-connection + * @param users List of IDs of the users to send the message to + * @param userAmount The amount of users contained in the users parameter + * @param message The message to send (UTF-8 encoded) + * @param messageSize The size (in bytes) of the message + * @returns The error code. If everything went well, STATUS_OK will be returned. + */ + mumble_error_t(MUMBLE_PLUGIN_CALLING_CONVENTION *requestSendUserTextMessage)( + mumble_plugin_id_t callerID, mumble_connection_t connection, mumble_userid_t *users, std::size_t userAmount, + const char *message, std::size_t messageSize); + +# endif // -------- Find functions -------- diff --git a/src/mumble/API.h b/src/mumble/API.h index a1d0ebdc45..8f6ad6692c 100644 --- a/src/mumble/API.h +++ b/src/mumble/API.h @@ -107,6 +107,9 @@ public slots: void isLocalUserMuted_v_1_0_x(mumble_plugin_id_t callerID, bool *muted, std::shared_ptr< api_promise_t > promise); void isLocalUserDeafened_v_1_0_x(mumble_plugin_id_t callerID, bool *deafened, std::shared_ptr< api_promise_t > promise); + void isChannelLinkedTo_v_1_3_x(mumble_plugin_id_t callerID, mumble_connection_t connection, + mumble_channelid_t firstID, mumble_channelid_t secondID, bool *linked, + std::shared_ptr< api_promise_t > promise); void getUserHash_v_1_0_x(mumble_plugin_id_t callerID, mumble_connection_t connection, mumble_userid_t userID, const char **hash, std::shared_ptr< api_promise_t > promise); void getServerHash_v_1_0_x(mumble_plugin_id_t callerID, mumble_connection_t connection, const char **hash, @@ -119,6 +122,9 @@ public slots: void getChannelDescription_v_1_0_x(mumble_plugin_id_t callerID, mumble_connection_t connection, mumble_channelid_t channelID, const char **description, std::shared_ptr< api_promise_t > promise); + void getLinkedChannels_v_1_3_x(mumble_plugin_id_t callerID, mumble_connection_t connection, + mumble_channelid_t channelID, mumble_channelid_t **linkedChannels, + std::size_t *linkCount, std::shared_ptr< api_promise_t > promise); void requestUserMove_v_1_0_x(mumble_plugin_id_t callerID, mumble_connection_t connection, mumble_userid_t userID, mumble_channelid_t channelID, const char *password, std::shared_ptr< api_promise_t > promise); @@ -132,6 +138,24 @@ public slots: std::shared_ptr< api_promise_t > promise); void requestSetLocalUserComment_v_1_0_x(mumble_plugin_id_t callerID, mumble_connection_t connection, const char *comment, std::shared_ptr< api_promise_t > promise); + void requestLinkChannels_v_1_3_x(mumble_plugin_id_t callerID, mumble_connection_t connection, + mumble_channelid_t *channelList, std::size_t channelCount, + std::shared_ptr< api_promise_t > promise); + void requestUnlinkChannels_v_1_3_x(mumble_plugin_id_t callerID, mumble_connection_t connection, + mumble_channelid_t channelID, mumble_channelid_t *unlinkList, + std::size_t unlinkCount, std::shared_ptr< api_promise_t > promise); + void requestUnlinkChannelSet_v_1_3_x(mumble_plugin_id_t callerID, mumble_connection_t connection, + mumble_channelid_t *unlinkList, std::size_t unlinkCount, + std::shared_ptr< api_promise_t > promise); + void requestStartListeningToChannels_v_1_3_x(mumble_plugin_id_t callerID, mumble_connection_t connection, + mumble_channelid_t *channelList, std::size_t channelCount, + std::shared_ptr< api_promise_t > promise); + void requestStopListeningToChannels_v_1_3_x(mumble_plugin_id_t callerID, mumble_connection_t connection, + mumble_channelid_t *channelList, std::size_t channelCount, + std::shared_ptr< api_promise_t > promise); + void requestSendUserTextMessage_v_1_3_x(mumble_plugin_id_t callerID, mumble_connection_t connection, + mumble_userid_t *users, std::size_t userAmount, const char *message, + std::size_t messageSize, std::shared_ptr< api_promise_t > promise); void findUserByName_v_1_0_x(mumble_plugin_id_t callerID, mumble_connection_t connection, const char *userName, mumble_userid_t *userID, std::shared_ptr< api_promise_t > promise); void findChannelByName_v_1_0_x(mumble_plugin_id_t callerID, mumble_connection_t connection, const char *channelName, @@ -174,6 +198,9 @@ MumbleAPI_v_1_0_x getMumbleAPI_v_1_0_x(); /// @returns The Mumble API struct (v1.2.x) MumbleAPI_v_1_2_x getMumbleAPI_v_1_2_x(); +/// @returns The Mumble API struct (v1.3.x) +MumbleAPI_v_1_3_x getMumbleAPI_v_1_3_x(); + /// Converts from the Qt key-encoding to the API's key encoding. /// /// @param keyCode The Qt key-code that shall be converted diff --git a/src/mumble/API_v_1_x_x.cpp b/src/mumble/API_v_1_x_x.cpp index 894b3a6b3d..433b0de09b 100644 --- a/src/mumble/API_v_1_x_x.cpp +++ b/src/mumble/API_v_1_x_x.cpp @@ -47,12 +47,22 @@ EXIT_WITH(MUMBLE_EC_INVALID_PLUGIN_ID); \ } +#define VERIFY_CHANNEL_ID(id) \ + if (!Channel::get((static_cast< unsigned int >(id)))) { \ + EXIT_WITH(MUMBLE_EC_CHANNEL_NOT_FOUND); \ + } + // Right now there can only be one connection managed by the current ServerHandler #define VERIFY_CONNECTION(connection) \ if (!Global::get().sh || Global::get().sh->getConnectionID() != connection) { \ EXIT_WITH(MUMBLE_EC_CONNECTION_NOT_FOUND); \ } +#define VERIFY_USER_ID(id) \ + if (!ClientUser::get(id)) { \ + EXIT_WITH(MUMBLE_EC_USER_NOT_FOUND); \ + } + // Right now whether or not a connection has finished synchronizing is indicated by Global::get().uiSession. If it is // zero, synchronization is not done yet (or there is no connection to begin with). The connection parameter in the // macro is only present in case it will be needed in the future @@ -658,6 +668,42 @@ void MumbleAPI::isLocalUserDeafened_v_1_0_x(mumble_plugin_id_t callerID, bool *d EXIT_WITH(MUMBLE_STATUS_OK); } +void MumbleAPI::isChannelLinkedTo_v_1_3_x(mumble_plugin_id_t callerID, mumble_connection_t connection, + mumble_channelid_t firstID, mumble_channelid_t secondID, bool *linked, + std::shared_ptr< api_promise_t > promise) { + if (QThread::currentThread() != thread()) { + // Invoke in main thread + QMetaObject::invokeMethod(this, "isChannelLinkedTo_v_1_3_x", Qt::QueuedConnection, + Q_ARG(mumble_plugin_id_t, callerID), Q_ARG(mumble_connection_t, connection), + Q_ARG(mumble_channelid_t, firstID), Q_ARG(mumble_channelid_t, secondID), + Q_ARG(bool *, linked), Q_ARG(std::shared_ptr< api_promise_t >, promise)); + + return; + } + + api_promise_t::lock_guard_t guard = promise->lock(); + if (promise->isCancelled()) { + return; + } + + VERIFY_PLUGIN_ID(callerID); + + VERIFY_CONNECTION(connection); + + const Channel *channel = Channel::get(static_cast< unsigned int >(firstID)); + if (!channel) { + EXIT_WITH(MUMBLE_EC_CHANNEL_NOT_FOUND); + } + + Channel *linkedChannel = Channel::get(static_cast< unsigned int >(secondID)); + if (!linkedChannel) { + EXIT_WITH(MUMBLE_EC_CHANNEL_NOT_FOUND); + } + + *linked = channel->isLinked(linkedChannel); + EXIT_WITH(MUMBLE_STATUS_OK); +} + void MumbleAPI::getUserHash_v_1_0_x(mumble_plugin_id_t callerID, mumble_connection_t connection, mumble_userid_t userID, const char **hash, std::shared_ptr< api_promise_t > promise) { if (QThread::currentThread() != thread()) { @@ -894,6 +940,52 @@ void MumbleAPI::getChannelDescription_v_1_0_x(mumble_plugin_id_t callerID, mumbl EXIT_WITH(MUMBLE_STATUS_OK); } +void MumbleAPI::getLinkedChannels_v_1_3_x(mumble_plugin_id_t callerID, mumble_connection_t connection, + mumble_channelid_t channelID, mumble_channelid_t **linkedChannels, + std::size_t *linkCount, std::shared_ptr< api_promise_t > promise) { + if (QThread::currentThread() != thread()) { + // Invoke in main thread + QMetaObject::invokeMethod(this, "getLinkedChannels_v_1_3_x", Qt::QueuedConnection, + Q_ARG(mumble_plugin_id_t, callerID), Q_ARG(mumble_connection_t, connection), + Q_ARG(mumble_channelid_t, channelID), Q_ARG(mumble_channelid_t **, linkedChannels), + Q_ARG(std::size_t *, linkCount), Q_ARG(std::shared_ptr< api_promise_t >, promise)); + + return; + } + + api_promise_t::lock_guard_t guard = promise->lock(); + if (promise->isCancelled()) { + return; + } + + VERIFY_PLUGIN_ID(callerID); + + VERIFY_CONNECTION(connection); + + Channel *channel = Channel::get(static_cast< unsigned int >(channelID)); + + if (!channel) { + EXIT_WITH(MUMBLE_EC_CHANNEL_NOT_FOUND); + } + + const QSet< Channel * > channelSet = channel->allLinks(); + + auto amount = static_cast< std::size_t >(channelSet.size()); + auto *channelIDs = reinterpret_cast< mumble_channelid_t * >(malloc(sizeof(mumble_channelid_t) * amount)); + + std::size_t index = 0; + for (const Channel *linkedChannel : channelSet) { + channelIDs[index++] = static_cast< mumble_channelid_t >(linkedChannel->iId); + } + + m_curator.m_entries.insert({ channelIDs, { defaultDeleter, callerID, "getLinkedChannels" } }); + + *linkedChannels = channelIDs; + *linkCount = amount; + + EXIT_WITH(MUMBLE_STATUS_OK); +} + void MumbleAPI::requestUserMove_v_1_0_x(mumble_plugin_id_t callerID, mumble_connection_t connection, mumble_userid_t userID, mumble_channelid_t channelID, const char *password, std::shared_ptr< api_promise_t > promise) { @@ -1093,6 +1185,226 @@ void MumbleAPI::requestSetLocalUserComment_v_1_0_x(mumble_plugin_id_t callerID, EXIT_WITH(MUMBLE_STATUS_OK); } +void MumbleAPI::requestLinkChannels_v_1_3_x(mumble_plugin_id_t callerID, mumble_connection_t connection, + mumble_channelid_t *channelSet, std::size_t channelCount, + std::shared_ptr< api_promise_t > promise) { + if (QThread::currentThread() != thread()) { + // Invoke in main thread + QMetaObject::invokeMethod(this, "requestLinkChannels_v_1_3_x", Qt::QueuedConnection, + Q_ARG(mumble_plugin_id_t, callerID), Q_ARG(mumble_connection_t, connection), + Q_ARG(mumble_channelid_t *, channelSet), Q_ARG(std::size_t, channelCount), + Q_ARG(std::shared_ptr< api_promise_t >, promise)); + + return; + } + + api_promise_t::lock_guard_t guard = promise->lock(); + if (promise->isCancelled()) { + return; + } + + VERIFY_PLUGIN_ID(callerID); + + VERIFY_CONNECTION(connection); + + if (channelCount < 2) { + EXIT_WITH(MUMBLE_STATUS_OK); + } + + for (std::size_t i = 0; i < channelCount; i++) { + VERIFY_CHANNEL_ID(channelSet[i]); + } + + Global::get().sh->addChannelLinks(static_cast< unsigned int >(channelCount), + reinterpret_cast< const unsigned int * >(channelSet)); + + EXIT_WITH(MUMBLE_STATUS_OK); +} + +void MumbleAPI::requestUnlinkChannels_v_1_3_x(mumble_plugin_id_t callerID, mumble_connection_t connection, + mumble_channelid_t channelID, mumble_channelid_t *channelSet, + std::size_t channelCount, std::shared_ptr< api_promise_t > promise) { + if (QThread::currentThread() != thread()) { + // Invoke in main thread + QMetaObject::invokeMethod(this, "requestUnlinkChannels_v_1_3_x", Qt::QueuedConnection, + Q_ARG(mumble_plugin_id_t, callerID), Q_ARG(mumble_connection_t, connection), + Q_ARG(mumble_channelid_t, channelID), Q_ARG(mumble_channelid_t *, channelSet), + Q_ARG(std::size_t, channelCount), Q_ARG(std::shared_ptr< api_promise_t >, promise)); + + return; + } + + api_promise_t::lock_guard_t guard = promise->lock(); + if (promise->isCancelled()) { + return; + } + + VERIFY_PLUGIN_ID(callerID); + + VERIFY_CONNECTION(connection); + + if (channelCount < 1) { + EXIT_WITH(MUMBLE_STATUS_OK); + } + + VERIFY_CHANNEL_ID(channelID); + + for (std::size_t i = 0; i < channelCount; i++) { + VERIFY_CHANNEL_ID(channelSet[i]); + } + + Global::get().sh->removeChannelLinks(static_cast< unsigned int >(channelID), + static_cast< unsigned int >(channelCount), + reinterpret_cast< const unsigned int * >(channelSet)); + + EXIT_WITH(MUMBLE_STATUS_OK); +} + +void MumbleAPI::requestUnlinkChannelSet_v_1_3_x(mumble_plugin_id_t callerID, mumble_connection_t connection, + mumble_channelid_t *channelSet, std::size_t channelCount, + std::shared_ptr< api_promise_t > promise) { + if (QThread::currentThread() != thread()) { + // Invoke in main thread + QMetaObject::invokeMethod(this, "requestUnlinkChannelSet_v_1_3_x", Qt::QueuedConnection, + Q_ARG(mumble_plugin_id_t, callerID), Q_ARG(mumble_connection_t, connection), + Q_ARG(mumble_channelid_t *, channelSet), Q_ARG(std::size_t, channelCount), + Q_ARG(std::shared_ptr< api_promise_t >, promise)); + + return; + } + + api_promise_t::lock_guard_t guard = promise->lock(); + if (promise->isCancelled()) { + return; + } + + VERIFY_PLUGIN_ID(callerID); + + VERIFY_CONNECTION(connection); + + if (channelCount < 1) { + EXIT_WITH(MUMBLE_STATUS_OK); + } + + for (std::size_t i = 0; i < channelCount; i++) { + VERIFY_CHANNEL_ID(channelSet[i]); + } + + Global::get().sh->removeChannelLinks(static_cast< unsigned int >(channelCount), + reinterpret_cast< const unsigned int * >(channelSet)); + + EXIT_WITH(MUMBLE_STATUS_OK); +} + +void MumbleAPI::requestStartListeningToChannels_v_1_3_x(mumble_plugin_id_t callerID, mumble_connection_t connection, + mumble_channelid_t *channelSet, std::size_t channelCount, + std::shared_ptr< api_promise_t > promise) { + if (QThread::currentThread() != thread()) { + // Invoke in main thread + QMetaObject::invokeMethod(this, "requestStartListeningToChannels_v_1_3_x", Qt::QueuedConnection, + Q_ARG(mumble_plugin_id_t, callerID), Q_ARG(mumble_connection_t, connection), + Q_ARG(mumble_channelid_t *, channelSet), Q_ARG(std::size_t, channelCount), + Q_ARG(std::shared_ptr< api_promise_t >, promise)); + + return; + } + + api_promise_t::lock_guard_t guard = promise->lock(); + if (promise->isCancelled()) { + return; + } + + VERIFY_PLUGIN_ID(callerID); + + VERIFY_CONNECTION(connection); + + if (channelCount < 1) { + EXIT_WITH(MUMBLE_STATUS_OK); + } + + QList< unsigned int > channelList; + for (std::size_t i = 0; i < channelCount; i++) { + VERIFY_CHANNEL_ID(channelSet[i]); + channelList.append(static_cast< unsigned int >(channelSet[i])); + } + + Global::get().sh->startListeningToChannels(channelList); + + EXIT_WITH(MUMBLE_STATUS_OK); +} + +void MumbleAPI::requestStopListeningToChannels_v_1_3_x(mumble_plugin_id_t callerID, mumble_connection_t connection, + mumble_channelid_t *channelSet, std::size_t channelCount, + std::shared_ptr< api_promise_t > promise) { + if (QThread::currentThread() != thread()) { + // Invoke in main thread + QMetaObject::invokeMethod(this, "requestStopListeningToChannels_v_1_3_x", Qt::QueuedConnection, + Q_ARG(mumble_plugin_id_t, callerID), Q_ARG(mumble_connection_t, connection), + Q_ARG(mumble_channelid_t *, channelSet), Q_ARG(std::size_t, channelCount), + Q_ARG(std::shared_ptr< api_promise_t >, promise)); + + return; + } + + api_promise_t::lock_guard_t guard = promise->lock(); + if (promise->isCancelled()) { + return; + } + + VERIFY_PLUGIN_ID(callerID); + + VERIFY_CONNECTION(connection); + + if (channelCount < 1) { + EXIT_WITH(MUMBLE_STATUS_OK); + } + + QList< unsigned int > channelList; + for (std::size_t i = 0; i < channelCount; i++) { + VERIFY_CHANNEL_ID(channelSet[i]); + channelList.append(static_cast< unsigned int >(channelSet[i])); + } + + Global::get().sh->stopListeningToChannels(channelList); + + EXIT_WITH(MUMBLE_STATUS_OK); +} + +void MumbleAPI::requestSendUserTextMessage_v_1_3_x(mumble_plugin_id_t callerID, mumble_connection_t connection, + mumble_userid_t *users, std::size_t userAmount, const char *message, + std::size_t messageSize, std::shared_ptr< api_promise_t > promise) { + if (QThread::currentThread() != thread()) { + // Invoke in main thread + QMetaObject::invokeMethod(this, "requestSendUserTextMessage_v_1_3_x", Qt::QueuedConnection, + Q_ARG(mumble_plugin_id_t, callerID), Q_ARG(mumble_connection_t, connection), + Q_ARG(mumble_userid_t *, users), Q_ARG(std::size_t, userAmount), + Q_ARG(const char *, message), Q_ARG(std::size_t, messageSize), + Q_ARG(std::shared_ptr< api_promise_t >, promise)); + + return; + } + + api_promise_t::lock_guard_t guard = promise->lock(); + if (promise->isCancelled()) { + return; + } + + VERIFY_PLUGIN_ID(callerID); + + VERIFY_CONNECTION(connection); + + ENSURE_CONNECTION_SYNCHRONIZED(connection); + + for (std::size_t i = 0; i < userAmount; i++) { + VERIFY_USER_ID(users[i]); + const ClientUser *user = ClientUser::get(users[i]); + Global::get().sh->sendUserTextMessage(user->uiSession, + QString::fromUtf8(message, static_cast< int >(messageSize))); + } + + EXIT_WITH(MUMBLE_STATUS_OK); +} + void MumbleAPI::findUserByName_v_1_0_x(mumble_plugin_id_t callerID, mumble_connection_t connection, const char *userName, mumble_userid_t *userID, std::shared_ptr< api_promise_t > promise) { @@ -1767,6 +2079,14 @@ C_WRAPPER(isLocalUserDeafened_v_1_0_x) #undef TYPED_ARGS #undef ARG_NAMES +#define TYPED_ARGS \ + mumble_plugin_id_t callerID, mumble_connection_t connection, mumble_channelid_t firstID, \ + mumble_channelid_t secondID, bool *linked +#define ARG_NAMES callerID, connection, firstID, secondID, linked +C_WRAPPER(isChannelLinkedTo_v_1_3_x) +#undef TYPED_ARGS +#undef ARG_NAMES + #define TYPED_ARGS \ mumble_plugin_id_t callerID, mumble_connection_t connection, mumble_userid_t userID, const char **hash #define ARG_NAMES callerID, connection, userID, hash @@ -1800,6 +2120,14 @@ C_WRAPPER(getChannelDescription_v_1_0_x) #undef TYPED_ARGS #undef ARG_NAMES +#define TYPED_ARGS \ + mumble_plugin_id_t callerID, mumble_connection_t connection, mumble_channelid_t channelID, \ + mumble_channelid_t **linkedChannels, std::size_t *linkCount +#define ARG_NAMES callerID, connection, channelID, linkedChannels, linkCount +C_WRAPPER(getLinkedChannels_v_1_3_x) +#undef TYPED_ARGS +#undef ARG_NAMES + #define TYPED_ARGS \ mumble_plugin_id_t callerID, mumble_connection_t connection, mumble_userid_t userID, mumble_channelid_t channelID, \ const char *password @@ -1838,6 +2166,53 @@ C_WRAPPER(requestSetLocalUserComment_v_1_0_x) #undef TYPED_ARGS #undef ARG_NAMES +#define TYPED_ARGS \ + mumble_plugin_id_t callerID, mumble_connection_t connection, mumble_channelid_t *channelList, \ + std::size_t channelCount +#define ARG_NAMES callerID, connection, channelList, channelCount +C_WRAPPER(requestLinkChannels_v_1_3_x) +#undef TYPED_ARGS +#undef ARG_NAMES + +#define TYPED_ARGS \ + mumble_plugin_id_t callerID, mumble_connection_t connection, mumble_channelid_t channelID, \ + mumble_channelid_t *unlinkList, std::size_t unlinkCount +#define ARG_NAMES callerID, connection, channelID, unlinkList, unlinkCount +C_WRAPPER(requestUnlinkChannels_v_1_3_x) +#undef TYPED_ARGS +#undef ARG_NAMES + +#define TYPED_ARGS \ + mumble_plugin_id_t callerID, mumble_connection_t connection, mumble_channelid_t *unlinkList, std::size_t unlinkCount +#define ARG_NAMES callerID, connection, unlinkList, unlinkCount +C_WRAPPER(requestUnlinkChannelSet_v_1_3_x) +#undef TYPED_ARGS +#undef ARG_NAMES + +#define TYPED_ARGS \ + mumble_plugin_id_t callerID, mumble_connection_t connection, mumble_channelid_t *channelSet, \ + std::size_t channelCount +#define ARG_NAMES callerID, connection, channelSet, channelCount +C_WRAPPER(requestStartListeningToChannels_v_1_3_x) +#undef TYPED_ARGS +#undef ARG_NAMES + +#define TYPED_ARGS \ + mumble_plugin_id_t callerID, mumble_connection_t connection, mumble_channelid_t *channelSet, \ + std::size_t channelCount +#define ARG_NAMES callerID, connection, channelSet, channelCount +C_WRAPPER(requestStopListeningToChannels_v_1_3_x) +#undef TYPED_ARGS +#undef ARG_NAMES + +#define TYPED_ARGS \ + mumble_plugin_id_t callerID, mumble_connection_t connection, mumble_userid_t *users, std::size_t userAmount, \ + const char *message, std::size_t messageSize +#define ARG_NAMES callerID, connection, users, userAmount, message, messageSize +C_WRAPPER(requestSendUserTextMessage_v_1_3_x) +#undef TYPED_ARGS +#undef ARG_NAMES + #define TYPED_ARGS \ mumble_plugin_id_t callerID, mumble_connection_t connection, const char *userName, mumble_userid_t *userID #define ARG_NAMES callerID, connection, userName, userID @@ -2016,6 +2391,55 @@ MumbleAPI_v_1_2_x getMumbleAPI_v_1_2_x() { playSample_v_1_2_x }; } +MumbleAPI_v_1_3_x getMumbleAPI_v_1_3_x() { + return { freeMemory_v_1_0_x, + getActiveServerConnection_v_1_0_x, + isConnectionSynchronized_v_1_0_x, + getLocalUserID_v_1_0_x, + getUserName_v_1_0_x, + getChannelName_v_1_0_x, + getAllUsers_v_1_0_x, + getAllChannels_v_1_0_x, + getChannelOfUser_v_1_0_x, + getUsersInChannel_v_1_0_x, + getLocalUserTransmissionMode_v_1_0_x, + isUserLocallyMuted_v_1_0_x, + isLocalUserMuted_v_1_0_x, + isLocalUserDeafened_v_1_0_x, + getUserHash_v_1_0_x, + getServerHash_v_1_0_x, + getUserComment_v_1_0_x, + getChannelDescription_v_1_0_x, + isChannelLinkedTo_v_1_3_x, + getLinkedChannels_v_1_3_x, + requestLocalUserTransmissionMode_v_1_0_x, + requestUserMove_v_1_0_x, + requestMicrophoneActivationOverwrite_v_1_0_x, + requestLocalMute_v_1_0_x, + requestLocalUserMute_v_1_0_x, + requestLocalUserDeaf_v_1_0_x, + requestSetLocalUserComment_v_1_0_x, + requestLinkChannels_v_1_3_x, + requestUnlinkChannels_v_1_3_x, + requestUnlinkChannelSet_v_1_3_x, + requestStartListeningToChannels_v_1_3_x, + requestStopListeningToChannels_v_1_3_x, + requestSendUserTextMessage_v_1_3_x, + findUserByName_v_1_0_x, + findChannelByName_v_1_0_x, + getMumbleSetting_bool_v_1_0_x, + getMumbleSetting_int_v_1_0_x, + getMumbleSetting_double_v_1_0_x, + getMumbleSetting_string_v_1_0_x, + setMumbleSetting_bool_v_1_0_x, + setMumbleSetting_int_v_1_0_x, + setMumbleSetting_double_v_1_0_x, + setMumbleSetting_string_v_1_0_x, + sendData_v_1_0_x, + log_v_1_0_x, + playSample_v_1_2_x }; +} + #define MAP(qtName, apiName) \ case Qt::Key_##qtName: \ return MUMBLE_KC_##apiName diff --git a/src/mumble/MumbleAPI_structs.h b/src/mumble/MumbleAPI_structs.h index c606a030e7..12999ba561 100644 --- a/src/mumble/MumbleAPI_structs.h +++ b/src/mumble/MumbleAPI_structs.h @@ -15,6 +15,17 @@ // First, include the latest plugin API header file completely #include "MumblePlugin.h" +// Now, include all older API structs for backward compatibility +// Re-include the API definition +#undef EXTERNAL_MUMBLE_PLUGIN_MUMBLE_API_ +// But this time, overwrite the version +#undef MUMBLE_PLUGIN_API_MAJOR_MACRO +#define MUMBLE_PLUGIN_API_MAJOR_MACRO 1 +#undef MUMBLE_PLUGIN_API_MINOR_MACRO +#define MUMBLE_PLUGIN_API_MINOR_MACRO 2 + +#include "MumblePlugin.h" + // Re-include the API definition #undef EXTERNAL_MUMBLE_PLUGIN_MUMBLE_API_ diff --git a/src/mumble/Plugin.cpp b/src/mumble/Plugin.cpp index af5253f2aa..e5e35325c3 100644 --- a/src/mumble/Plugin.cpp +++ b/src/mumble/Plugin.cpp @@ -329,6 +329,9 @@ mumble_error_t Plugin::init() { } else if (apiVersion >= mumble_version_t({ 1, 2, 0 }) && apiVersion < mumble_version_t({ 1, 3, 0 })) { MumbleAPI_v_1_2_x api = API::getMumbleAPI_v_1_2_x(); registerAPIFunctions(&api); + } else if (apiVersion >= mumble_version_t({ 1, 3, 0 }) && apiVersion < mumble_version_t({ 1, 4, 0 })) { + MumbleAPI_v_1_3_x api = API::getMumbleAPI_v_1_3_x(); + registerAPIFunctions(&api); } else { // The API version could not be obtained -> this is an invalid plugin that shouldn't have been loaded in the // first place diff --git a/src/mumble/ServerHandler.cpp b/src/mumble/ServerHandler.cpp index 0a198ca50f..2cc36c7552 100644 --- a/src/mumble/ServerHandler.cpp +++ b/src/mumble/ServerHandler.cpp @@ -1172,6 +1172,24 @@ void ServerHandler::addChannelLink(unsigned int channel, unsigned int link, cons sendMessage(mpcs); } +void ServerHandler::addChannelLinks(const unsigned int channelCount, const unsigned int *channelSet, + const QStringList &temporaryAccessTokens) { + if (channelCount < 2) { + return; + } + MumbleProto::ChannelState mpcs; + mpcs.set_channel_id(channelSet[0]); + for (unsigned int i = 1; i < channelCount; i++) { + mpcs.add_links_add(channelSet[i]); + } + + for (const QString &token : temporaryAccessTokens) { + mpcs.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mpcs); +} + void ServerHandler::removeChannelLink(unsigned int channel, unsigned int link, const QStringList &temporaryAccessTokens) { MumbleProto::ChannelState mpcs; @@ -1185,6 +1203,43 @@ void ServerHandler::removeChannelLink(unsigned int channel, unsigned int link, sendMessage(mpcs); } +void ServerHandler::removeChannelLinks(unsigned int channel, const unsigned int channelCount, + const unsigned int *channelSet, const QStringList &temporaryAccessTokens) { + if (channelCount < 1) { + return; + } + MumbleProto::ChannelState mpcs; + mpcs.set_channel_id(channel); + for (unsigned int i = 0; i < channelCount; i++) { + mpcs.add_links_remove(channelSet[i]); + } + + for (const QString &token : temporaryAccessTokens) { + mpcs.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mpcs); +} + +void ServerHandler::removeChannelLinks(const unsigned int channelCount, const unsigned int *channelSet, + const QStringList &temporaryAccessTokens) { + if (channelCount < 2) { + return; + } + MumbleProto::ChannelState mpcs; + unsigned int link = channelSet[0]; + for (unsigned int i = 1; i < channelCount; i++) { + mpcs.set_channel_id(link); + mpcs.add_links_remove(channelSet[i]); + } + + for (const QString &token : temporaryAccessTokens) { + mpcs.add_temporary_access_tokens(token.toUtf8().constData()); + } + + sendMessage(mpcs); +} + void ServerHandler::requestChannelPermissions(unsigned int channel) { MumbleProto::PermissionQuery mppq; mppq.set_channel_id(channel); diff --git a/src/mumble/ServerHandler.h b/src/mumble/ServerHandler.h index 57a4a63bdb..43da62230c 100644 --- a/src/mumble/ServerHandler.h +++ b/src/mumble/ServerHandler.h @@ -177,7 +177,13 @@ class ServerHandler : public QThread { void setTokens(const QStringList &tokens); void removeChannel(unsigned int channel, const QStringList &temporaryAccessTokens = {}); void addChannelLink(unsigned int channel, unsigned int link, const QStringList &temporaryAccessTokens = {}); + void addChannelLinks(const unsigned int channelCount, const unsigned int *channelSet, + const QStringList &temporaryAccessTokens = {}); void removeChannelLink(unsigned int channel, unsigned int link, const QStringList &temporaryAccessTokens = {}); + void removeChannelLinks(const unsigned int channel, const unsigned int channelCount, const unsigned int *channelSet, + const QStringList &temporaryAccessTokens = {}); + void removeChannelLinks(const unsigned int channelCount, const unsigned int *channelSet, + const QStringList &temporaryAccessTokens = {}); void requestChannelPermissions(unsigned int channel); void setSelfMuteDeafState(bool mute, bool deaf); void announceRecordingState(bool recording);