mumble/src/murmur/Messages.cpp
Robert Adam 7271bcaf05 FIX(server): Respect Qt's desired initialization order
In a Qt application, the QApplication object should be the first QObject
that is created. However, the `Meta` class used to have a static member
called `mp`, which means that this member gets initialized _before_
main() runs and therefore before the QApplication is created.

This has caused an "Invalid nullptr in QObject::connect" warning
somewhere in Qt's internals (since the move to Qt 6). The impact of this
warning is unclear at this point.

This commit makes the mp parameter a std::unique_ptr that gets
explicitly initialized in the main function (more or less right after
the QApplication object is created). This guarantees that the MetaParams
object does not get created before the QApplication object, fixing the
observed warning.

It is worth noting that we do have a couple of other static QObject
variables in the main translation unit, but these seem to be
inconsequential (at least they don't seem to trigger a similar warning).

Fixes #6669
2025-01-11 17:56:39 +01:00

2450 lines
74 KiB
C++

// 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 <https://www.mumble.info/LICENSE>.
#include "ACL.h"
#include "Channel.h"
#include "ChannelListenerManager.h"
#include "ClientType.h"
#include "Connection.h"
#include "Group.h"
#include "Meta.h"
#include "MumbleConstants.h"
#include "ProtoUtils.h"
#include "QtUtils.h"
#include "Server.h"
#include "ServerDB.h"
#include "ServerUser.h"
#include "User.h"
#include "Version.h"
#include "crypto/CryptState.h"
#include <cassert>
#include <unordered_map>
#include <QtCore/QStack>
#include <QtCore/QTimeZone>
#include <QtCore/QtEndian>
#include <tracy/Tracy.hpp>
#define RATELIMIT(user) \
if (user->leakyBucket.ratelimit(1)) { \
return; \
}
#define MSG_SETUP(st) \
if (uSource->sState != st) { \
return; \
} \
uSource->bwr.resetIdleSeconds()
#define MSG_SETUP_NO_UNIDLE(st) \
if (uSource->sState != st) \
return
#define VICTIM_SETUP \
ServerUser *pDstServerUser = uSource; \
if (msg.has_session()) \
pDstServerUser = qhUsers.value(msg.session()); \
if (!pDstServerUser) \
return; \
Q_UNUSED(pDstServerUser)
#define PERM_DENIED(who, where, what) \
{ \
MumbleProto::PermissionDenied mppd; \
mppd.set_permission(static_cast< unsigned int >(what)); \
mppd.set_channel_id(where->iId); \
mppd.set_session(who->uiSession); \
mppd.set_type(MumbleProto::PermissionDenied_DenyType_Permission); \
sendMessage(uSource, mppd); \
log(uSource, \
QString("%1 not allowed to %2 in %3").arg(who->qsName).arg(ChanACL::permName(what)).arg(where->qsName)); \
}
#define PERM_DENIED_TYPE(type) \
{ \
MumbleProto::PermissionDenied mppd; \
mppd.set_type(MumbleProto::PermissionDenied_DenyType_##type); \
sendMessage(uSource, mppd); \
}
#define PERM_DENIED_FALLBACK(type, version, text) \
{ \
MumbleProto::PermissionDenied mppd; \
mppd.set_type(MumbleProto::PermissionDenied_DenyType_##type); \
if (uSource->m_version < version) \
mppd.set_reason(u8(text)); \
sendMessage(uSource, mppd); \
}
#define PERM_DENIED_HASH(user) \
{ \
MumbleProto::PermissionDenied mppd; \
mppd.set_type(MumbleProto::PermissionDenied_DenyType_MissingCertificate); \
if (user) \
mppd.set_session(user->uiSession); \
sendMessage(uSource, mppd); \
}
/// A helper class for managing temporary access tokens.
/// It will add the tokens in the comstructor and remove them again in the destructor effectively
/// turning the tokens into a scope-based property.
class TemporaryAccessTokenHelper {
protected:
ServerUser *affectedUser;
QStringList qslTemporaryTokens;
Server *server;
public:
TemporaryAccessTokenHelper(ServerUser *affectedUser, const QStringList &tokens, Server *server)
: affectedUser(affectedUser), qslTemporaryTokens(tokens), server(server) {
// Add the temporary tokens
QMutableStringListIterator it(this->qslTemporaryTokens);
{
QMutexLocker qml(&server->qmCache);
while (it.hasNext()) {
QString &token = it.next();
// If tokens are treated case-insensitively, transform all temp. tokens to lowercase first
if (Group::accessTokenCaseSensitivity == Qt::CaseInsensitive) {
token = token.toLower();
}
if (!this->affectedUser->qslAccessTokens.contains(token, Group::accessTokenCaseSensitivity)) {
// Add token
this->affectedUser->qslAccessTokens << token;
} else {
// It appears, as if the user already has this token set -> it's not a temporary one or a duplicate
it.remove();
}
}
}
if (!this->qslTemporaryTokens.isEmpty()) {
// Clear the cache in order for tokens to take effect
server->clearACLCache(this->affectedUser);
}
}
~TemporaryAccessTokenHelper() {
if (!this->qslTemporaryTokens.isEmpty()) {
{
QMutexLocker qml(&server->qmCache);
// remove the temporary tokens
foreach (const QString &token, this->qslTemporaryTokens) {
this->affectedUser->qslAccessTokens.removeOne(token);
}
}
// Clear cache to actually get rid of the temporary tokens
server->clearACLCache(this->affectedUser);
}
}
};
/// Checks whether the given channel has restrictions affecting the ENTER privilege
///
/// @param c A pointer to the Channel that should be checked
/// @return Whether the provided channel has an ACL denying ENTER
bool isChannelEnterRestricted(Channel *c) {
// A channel is enter restricted if there's an ACL denying enter privileges
foreach (ChanACL *acl, c->qlACL) {
if (acl->pDeny & ChanACL::Enter) {
return true;
}
}
return false;
}
void Server::msgAuthenticate(ServerUser *uSource, MumbleProto::Authenticate &msg) {
ZoneScoped;
if (uSource->sState == ServerUser::Authenticated && (msg.tokens_size() > 0 || !uSource->qslAccessTokens.empty())) {
// Process a change in access tokens for already authenticated users
QStringList qsl;
for (int i = 0; i < msg.tokens_size(); ++i) {
qsl << u8(msg.tokens(i));
}
{
QMutexLocker qml(&qmCache);
uSource->qslAccessTokens = qsl;
}
clearACLCache(uSource);
// Send back updated enter states of all channels
MumbleProto::ChannelState mpcs;
for (Channel *chan : qhChannels) {
mpcs.set_channel_id(static_cast< unsigned int >(chan->iId));
mpcs.set_can_enter(hasPermission(uSource, chan, ChanACL::Enter));
// As no ACLs have changed, we don't need to update the is_access_restricted message field
sendMessage(uSource, mpcs);
}
}
MSG_SETUP(ServerUser::Connected);
// As the first thing, assign a session ID to this client. Given that the client initiated
// the authentication procedure we can be sure that this is not just a random TCP connection.
// Thus it is about time we assign the ID to this client in order to be able to reference it
// in the following.
{
QWriteLocker wl(&qrwlVoiceThread);
uSource->uiSession = qqIds.dequeue();
qhUsers.insert(uSource->uiSession, uSource);
qhHostUsers[uSource->haAddress].insert(uSource);
}
Channel *root = qhChannels.value(0);
Channel *c;
uSource->qsName = u8(msg.username()).trimmed();
bool ok = false;
bool nameok = validateUserName(uSource->qsName);
QString pw = u8(msg.password());
// Fetch ID and stored username.
// This function needs to support the fact that sessions may go away.
int id = authenticate(uSource->qsName, pw, static_cast< int >(uSource->uiSession), uSource->qslEmail,
uSource->qsHash, uSource->bVerified, uSource->peerCertificateChain());
uSource->iId = id >= 0 ? id : -1;
QString reason;
MumbleProto::Reject_RejectType rtType = MumbleProto::Reject_RejectType_None;
if (id == -2 && !nameok) {
reason = "Invalid username";
rtType = MumbleProto::Reject_RejectType_InvalidUsername;
} else if (id == -1) {
reason = "Wrong certificate or password for existing user";
rtType = MumbleProto::Reject_RejectType_WrongUserPW;
} else if (id == -2 && !qsPassword.isEmpty() && qsPassword != pw) {
reason = "Invalid server password";
rtType = MumbleProto::Reject_RejectType_WrongServerPW;
} else if (id == -3) {
reason = "Your account information can not be verified currently. Please try again later";
rtType = MumbleProto::Reject_RejectType_AuthenticatorFail;
} else {
ok = true;
}
ServerUser *uOld = nullptr;
foreach (ServerUser *u, qhUsers) {
if (u == uSource)
continue;
if (((u->iId >= 0) && (u->iId == uSource->iId)) || (u->qsName.toLower() == uSource->qsName.toLower())) {
uOld = u;
break;
}
}
// Allow reuse of name from same IP
if (ok && uOld && (uSource->iId == -1)) {
if ((uOld->peerAddress() != uSource->peerAddress())
&& (uSource->qsHash.isEmpty() || (uSource->qsHash != uOld->qsHash))) {
reason = "Username already in use";
rtType = MumbleProto::Reject_RejectType_UsernameInUse;
ok = false;
}
}
if ((id != 0) && (static_cast< unsigned int >(qhUsers.count()) > iMaxUsers)) {
reason = QString::fromLatin1("Server is full (max %1 users)").arg(iMaxUsers);
rtType = MumbleProto::Reject_RejectType_ServerFull;
ok = false;
}
if ((id != 0) && (uSource->qsHash.isEmpty() && bCertRequired)) {
reason = QString::fromLatin1("A certificate is required to connect to this server");
rtType = MumbleProto::Reject_RejectType_NoCertificate;
ok = false;
}
if (ok) {
if (msg.tokens_size() > 0) {
// Set the access tokens for the newly connected user
QStringList qsl;
for (int i = 0; i < msg.tokens_size(); ++i) {
qsl << u8(msg.tokens(i));
}
{
QMutexLocker qml(&qmCache);
uSource->qslAccessTokens = qsl;
}
}
// Clear cache as the "auth" ACL depends on the user id
// and any cached ACL won't have taken that into account yet
// Also, if we set access tokens above, we have to reset
// the ACL cache anyway.
clearACLCache(uSource);
}
Channel *lc = nullptr;
int lastChannelID = readLastChannel(uSource->iId);
if (lastChannelID >= 0) {
lc = qhChannels.value(static_cast< unsigned int >(lastChannelID));
}
if (!lc || !hasPermission(uSource, lc, ChanACL::Enter) || isChannelFull(lc, uSource)) {
lc = qhChannels.value(iDefaultChan);
if (!lc || !hasPermission(uSource, lc, ChanACL::Enter) || isChannelFull(lc, uSource)) {
lc = root;
if (isChannelFull(lc, uSource)) {
reason = QString::fromLatin1("Server channels are full");
rtType = MumbleProto::Reject_RejectType_ServerFull;
ok = false;
}
}
}
if (!ok) {
log(uSource, QString("Rejected connection from %1: %2")
.arg(addressToString(uSource->peerAddress(), uSource->peerPort()), reason));
MumbleProto::Reject mpr;
mpr.set_reason(u8(reason));
mpr.set_type(rtType);
sendMessage(uSource, mpr);
uSource->disconnectSocket();
return;
}
startThread();
// Kick ghost
if (uOld) {
log(uSource, "Disconnecting ghost");
MumbleProto::UserRemove mpur;
mpur.set_session(uOld->uiSession);
mpur.set_reason("You connected to the server from another device");
sendMessage(uOld, mpur);
uOld->forceFlush();
uOld->disconnectSocket(true);
}
// Setup UDP encryption
{
QMutexLocker l(&uSource->qmCrypt);
uSource->csCrypt->genKey();
MumbleProto::CryptSetup mpcrypt;
mpcrypt.set_key(uSource->csCrypt->getRawKey());
mpcrypt.set_server_nonce(uSource->csCrypt->getEncryptIV());
mpcrypt.set_client_nonce(uSource->csCrypt->getDecryptIV());
sendMessage(uSource, mpcrypt);
}
bool fake_celt_support = false;
if (msg.celt_versions_size() > 0) {
for (int i = 0; i < msg.celt_versions_size(); ++i)
uSource->qlCodecs.append(msg.celt_versions(i));
} else {
uSource->qlCodecs.append(static_cast< qint32 >(0x8000000b));
fake_celt_support = true;
}
uSource->bOpus = msg.opus();
recheckCodecVersions(uSource);
MumbleProto::CodecVersion mpcv;
mpcv.set_alpha(iCodecAlpha);
mpcv.set_beta(iCodecBeta);
mpcv.set_prefer_alpha(bPreferAlpha);
mpcv.set_opus(bOpus);
sendMessage(uSource, mpcv);
if (!bOpus && uSource->bOpus && fake_celt_support) {
sendTextMessage(
nullptr, uSource, false,
QLatin1String("<strong>WARNING:</strong> Your client doesn't support the CELT codec, you won't be able to "
"talk to or hear most clients. Please make sure your client was built with CELT support."));
}
// Transmit channel tree
QQueue< Channel * > q;
QSet< Channel * > chans;
q << root;
MumbleProto::ChannelState mpcs;
while (!q.isEmpty()) {
c = q.dequeue();
chans.insert(c);
mpcs.Clear();
mpcs.set_channel_id(c->iId);
if (c->cParent)
mpcs.set_parent(c->cParent->iId);
if (c->iId == 0)
mpcs.set_name(u8(qsRegName.isEmpty() ? QLatin1String("Root") : qsRegName));
else
mpcs.set_name(u8(c->qsName));
mpcs.set_position(c->iPosition);
if ((uSource->m_version >= Version::fromComponents(1, 2, 2)) && !c->qbaDescHash.isEmpty())
mpcs.set_description_hash(blob(c->qbaDescHash));
else if (!c->qsDesc.isEmpty())
mpcs.set_description(u8(c->qsDesc));
mpcs.set_max_users(c->uiMaxUsers);
// Include info about enter restrictions of this channel
mpcs.set_is_enter_restricted(isChannelEnterRestricted(c));
mpcs.set_can_enter(hasPermission(uSource, c, ChanACL::Enter));
sendMessage(uSource, mpcs);
foreach (c, c->qlChannels)
q.enqueue(c);
}
// Transmit links
foreach (c, chans) {
if (c->qhLinks.count() > 0) {
mpcs.Clear();
mpcs.set_channel_id(c->iId);
foreach (Channel *l, c->qhLinks.keys())
mpcs.add_links(l->iId);
sendMessage(uSource, mpcs);
}
}
loadChannelListenersOf(*uSource);
// Transmit user profile
MumbleProto::UserState mpus;
userEnterChannel(uSource, lc, mpus);
{
QWriteLocker wl(&qrwlVoiceThread);
uSource->sState = ServerUser::Authenticated;
}
mpus.set_session(uSource->uiSession);
mpus.set_name(u8(uSource->qsName));
if (uSource->iId >= 0) {
mpus.set_user_id(static_cast< unsigned int >(uSource->iId));
hashAssign(uSource->qbaTexture, uSource->qbaTextureHash, getUserTexture(uSource->iId));
if (!uSource->qbaTextureHash.isEmpty())
mpus.set_texture_hash(blob(uSource->qbaTextureHash));
else if (!uSource->qbaTexture.isEmpty())
mpus.set_texture(blob(uSource->qbaTexture));
const QMap< int, QString > &info = getRegistration(uSource->iId);
if (info.contains(ServerDB::User_Comment)) {
hashAssign(uSource->qsComment, uSource->qbaCommentHash, info.value(ServerDB::User_Comment));
if (!uSource->qbaCommentHash.isEmpty())
mpus.set_comment_hash(blob(uSource->qbaCommentHash));
else if (!uSource->qsComment.isEmpty())
mpus.set_comment(u8(uSource->qsComment));
}
}
if (!uSource->qsHash.isEmpty())
mpus.set_hash(u8(uSource->qsHash));
mpus.set_channel_id(uSource->cChannel->iId);
sendAll(mpus, Version::fromComponents(1, 2, 2), Version::CompareMode::AtLeast);
if ((uSource->qbaTexture.length() >= 4)
&& (qFromBigEndian< unsigned int >(reinterpret_cast< const unsigned char * >(uSource->qbaTexture.constData()))
== 600 * 60 * 4))
mpus.set_texture(blob(uSource->qbaTexture));
if (!uSource->qsComment.isEmpty())
mpus.set_comment(u8(uSource->qsComment));
sendAll(mpus, Version::fromComponents(1, 2, 2), Version::CompareMode::LessThan);
// Transmit other users profiles
foreach (ServerUser *u, qhUsers) {
if (u->sState != ServerUser::Authenticated)
continue;
if (u == uSource)
continue;
mpus.Clear();
mpus.set_session(u->uiSession);
mpus.set_name(u8(u->qsName));
if (u->iId >= 0)
mpus.set_user_id(static_cast< unsigned int >(u->iId));
if (uSource->m_version >= Version::fromComponents(1, 2, 2)) {
if (!u->qbaTextureHash.isEmpty())
mpus.set_texture_hash(blob(u->qbaTextureHash));
else if (!u->qbaTexture.isEmpty())
mpus.set_texture(blob(u->qbaTexture));
} else if ((uSource->qbaTexture.length() >= 4)
&& (qFromBigEndian< unsigned int >(
reinterpret_cast< const unsigned char * >(uSource->qbaTexture.constData()))
== 600 * 60 * 4)) {
mpus.set_texture(blob(u->qbaTexture));
}
if (u->cChannel->iId != 0)
mpus.set_channel_id(u->cChannel->iId);
if (u->bDeaf)
mpus.set_deaf(true);
else if (u->bMute)
mpus.set_mute(true);
if (u->bSuppress)
mpus.set_suppress(true);
if (u->bPrioritySpeaker)
mpus.set_priority_speaker(true);
if (u->bRecording)
mpus.set_recording(true);
if (u->bSelfDeaf)
mpus.set_self_deaf(true);
else if (u->bSelfMute)
mpus.set_self_mute(true);
if ((uSource->m_version >= Version::fromComponents(1, 2, 2)) && !u->qbaCommentHash.isEmpty())
mpus.set_comment_hash(blob(u->qbaCommentHash));
else if (!u->qsComment.isEmpty())
mpus.set_comment(u8(u->qsComment));
if (!u->qsHash.isEmpty())
mpus.set_hash(u8(u->qsHash));
for (unsigned int channelID : m_channelListenerManager.getListenedChannelsForUser(u->uiSession)) {
mpus.add_listening_channel_add(channelID);
if (broadcastListenerVolumeAdjustments) {
VolumeAdjustment volume = m_channelListenerManager.getListenerVolumeAdjustment(u->uiSession, channelID);
MumbleProto::UserState::VolumeAdjustment *adjustment = mpus.add_listening_volume_adjustment();
adjustment->set_listening_channel(channelID);
adjustment->set_volume_adjustment(volume.factor);
}
}
sendMessage(uSource, mpus);
}
// Send synchronisation packet
MumbleProto::ServerSync mpss;
mpss.set_session(uSource->uiSession);
if (!qsWelcomeText.isEmpty())
mpss.set_welcome_text(u8(qsWelcomeText));
mpss.set_max_bandwidth(static_cast< unsigned int >(iMaxBandwidth));
if (uSource->iId == 0) {
mpss.set_permissions(ChanACL::All);
} else {
QMutexLocker qml(&qmCache);
ChanACL::hasPermission(uSource, root, ChanACL::Enter, &acCache);
mpss.set_permissions(acCache.value(uSource)->value(root));
}
sendMessage(uSource, mpss);
// Transmit user's listeners - this has to be done AFTER the server-sync message has been sent to uSource as the
// client may require its own session ID for processing the listeners properly.
mpus.Clear();
mpus.set_session(uSource->uiSession);
for (unsigned int channelID : m_channelListenerManager.getListenedChannelsForUser(uSource->uiSession)) {
mpus.add_listening_channel_add(channelID);
}
// If we are not intending to broadcast the volume adjustments to everyone, we have to send the message to all but
// uSource without the volume adjustments. Then append the adjustments, but only send them to uSource. If we are in
// fact broadcasting, just append the adjustments and send to everyone.
if (!broadcastListenerVolumeAdjustments && mpus.listening_channel_add_size() > 0) {
sendExcept(uSource, mpus, Version::fromComponents(1, 2, 2), Version::CompareMode::AtLeast);
}
std::unordered_map< unsigned int, VolumeAdjustment > volumeAdjustments =
m_channelListenerManager.getAllListenerVolumeAdjustments(uSource->uiSession);
for (auto it = volumeAdjustments.begin(); it != volumeAdjustments.end(); ++it) {
MumbleProto::UserState::VolumeAdjustment *adjustment = mpus.add_listening_volume_adjustment();
adjustment->set_listening_channel(it->first);
adjustment->set_volume_adjustment(it->second.factor);
}
if (mpus.listening_channel_add_size() > 0 || mpus.listening_volume_adjustment_size() > 0) {
if (!broadcastListenerVolumeAdjustments) {
if (uSource->m_version >= Version::fromComponents(1, 2, 2)) {
sendMessage(uSource, mpus);
}
} else {
sendAll(mpus, Version::fromComponents(1, 2, 2), Version::CompareMode::AtLeast);
}
}
MumbleProto::ServerConfig mpsc;
mpsc.set_allow_html(bAllowHTML);
mpsc.set_message_length(static_cast< unsigned int >(iMaxTextMessageLength));
mpsc.set_image_message_length(static_cast< unsigned int >(iMaxImageMessageLength));
mpsc.set_max_users(static_cast< unsigned int >(iMaxUsers));
mpsc.set_recording_allowed(allowRecording);
sendMessage(uSource, mpsc);
MumbleProto::SuggestConfig mpsug;
if (m_suggestVersion != Version::UNKNOWN) {
MumbleProto::setSuggestedVersion(mpsug, m_suggestVersion);
}
if (!qvSuggestPositional.isNull())
mpsug.set_positional(qvSuggestPositional.toBool());
if (!qvSuggestPushToTalk.isNull())
mpsug.set_push_to_talk(qvSuggestPushToTalk.toBool());
#if GOOGLE_PROTOBUF_VERSION >= 3004000
if (mpsug.ByteSizeLong() > 0) {
#else
// ByteSize() has been deprecated as of protobuf v3.4
if (mpsug.ByteSize() > 0) {
#endif
sendMessage(uSource, mpsug);
}
if (uSource->m_version < Version::fromComponents(1, 4, 0) && Meta::mp->iMaxListenersPerChannel != 0
&& Meta::mp->iMaxListenerProxiesPerUser != 0) {
// The server has the ChannelListener feature enabled but the client that connects doesn't have version 1.4.0 or
// newer meaning that this client doesn't know what ChannelListeners are. Thus we'll send that user a
// text-message informing about this.
MumbleProto::TextMessage mptm;
if (Meta::mp->bAllowHTML) {
mptm.set_message("<b>[WARNING]</b>: This server has the <b>ChannelListener</b> feature enabled but your "
"client version does not support it. "
"This means that users <b>might be listening to what you are saying in your channel "
"without you noticing!</b> "
"You can solve this issue by upgrading to Mumble 1.4.0 or newer.");
} else {
mptm.set_message(
"[WARNING]: This server has the ChannelListener feature enabled but your client version does not "
"support it. "
"This means that users might be listening to what you are saying in your channel without you noticing! "
"You can solve this issue by upgrading to Mumble 1.4.0 or newer.");
}
sendMessage(uSource, mptm);
}
switch (msg.client_type()) {
case static_cast< int >(ClientType::BOT):
uSource->m_clientType = ClientType::BOT;
m_botCount++;
break;
case static_cast< int >(ClientType::REGULAR):
// No-op (also applies to unknown values of msg.client_type())
// (The default client type is regular anyway, so we don't need to change anything here)
break;
}
log(uSource, "Authenticated");
emit userConnected(uSource);
}
void Server::msgBanList(ServerUser *uSource, MumbleProto::BanList &msg) {
ZoneScoped;
MSG_SETUP(ServerUser::Authenticated);
QSet< Ban > previousBans, newBans;
if (!hasPermission(uSource, qhChannels.value(0), ChanACL::Ban)) {
PERM_DENIED(uSource, qhChannels.value(0), ChanACL::Ban);
return;
}
if (msg.query()) {
msg.clear_query();
msg.clear_bans();
foreach (const Ban &b, qlBans) {
MumbleProto::BanList_BanEntry *be = msg.add_bans();
be->set_address(b.haAddress.toStdString());
be->set_mask(static_cast< unsigned int >(b.iMask));
be->set_name(u8(b.qsUsername));
be->set_hash(u8(b.qsHash));
be->set_reason(u8(b.qsReason));
be->set_start(u8(b.qdtStart.toString(Qt::ISODate)));
be->set_duration(b.iDuration);
}
sendMessage(uSource, msg);
} else {
previousBans = QSet< Ban >(qlBans.begin(), qlBans.end());
qlBans.clear();
for (int i = 0; i < msg.bans_size(); ++i) {
const MumbleProto::BanList_BanEntry &be = msg.bans(i);
Ban b;
b.haAddress = be.address();
b.iMask = static_cast< int >(be.mask());
b.qsUsername = u8(be.name());
b.qsHash = u8(be.hash());
b.qsReason = u8(be.reason());
if (be.has_start()) {
b.qdtStart = QDateTime::fromString(u8(be.start()), Qt::ISODate);
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
b.qdtStart.setTimeZone(QTimeZone::UTC);
#else
b.qdtStart.setTimeSpec(Qt::UTC);
#endif
} else {
b.qdtStart = QDateTime::currentDateTime().toUTC();
}
b.iDuration = be.duration();
if (b.isValid()) {
qlBans << b;
}
}
newBans = QSet< Ban >(qlBans.begin(), qlBans.end());
QSet< Ban > removed = previousBans - newBans;
QSet< Ban > added = newBans - previousBans;
foreach (const Ban &b, removed) { log(uSource, QString("Removed ban: %1").arg(b.toString())); }
foreach (const Ban &b, added) { log(uSource, QString("New ban: %1").arg(b.toString())); }
saveBans();
log(uSource, "Updated banlist");
}
}
void Server::msgReject(ServerUser *, MumbleProto::Reject &) {
}
void Server::msgServerSync(ServerUser *, MumbleProto::ServerSync &) {
}
void Server::msgPermissionDenied(ServerUser *, MumbleProto::PermissionDenied &) {
}
void Server::msgUDPTunnel(ServerUser *, MumbleProto::UDPTunnel &) {
// This code should be unreachable
assert(false);
qWarning("Messages: Reached theoretically unreachable function msgUDPTunnel");
}
void Server::msgUserState(ServerUser *uSource, MumbleProto::UserState &msg) {
ZoneScoped;
MSG_SETUP(ServerUser::Authenticated);
VICTIM_SETUP;
Channel *root = qhChannels.value(0);
/*
First check all permissions involved
*/
if ((pDstServerUser->iId == 0) && (uSource->iId != 0)) {
// Don't allow any action on the SuperUser except initiated directly by the SuperUser himself/herself
PERM_DENIED_TYPE(SuperUser);
return;
}
msg.set_session(pDstServerUser->uiSession);
msg.set_actor(uSource->uiSession);
if (msg.has_name()) {
PERM_DENIED_TYPE(UserName);
return;
}
if (uSource == pDstServerUser) {
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);
if (msg.has_channel_id()) {
Channel *c = qhChannels.value(msg.channel_id());
if (!c || (c == pDstServerUser->cChannel))
return;
if ((uSource != pDstServerUser) && (!hasPermission(uSource, pDstServerUser->cChannel, ChanACL::Move))) {
PERM_DENIED(uSource, pDstServerUser->cChannel, ChanACL::Move);
return;
}
if (!hasPermission(uSource, c, ChanACL::Move) && !hasPermission(pDstServerUser, c, ChanACL::Enter)) {
PERM_DENIED(pDstServerUser, c, ChanACL::Enter);
return;
}
if (isChannelFull(c, uSource)) {
PERM_DENIED_FALLBACK(ChannelFull, Version::fromComponents(1, 2, 1), QLatin1String("Channel is full"));
return;
}
}
QList< Channel * > listeningChannelsAdd;
int passedChannelListener = 0;
// Check permission for each channel
for (int i = 0; i < msg.listening_channel_add_size(); i++) {
Channel *c = qhChannels.value(msg.listening_channel_add(i));
if (!c) {
continue;
}
if (!hasPermission(pDstServerUser, c, ChanACL::Listen)) {
PERM_DENIED(pDstServerUser, c, ChanACL::Listen);
continue;
}
if (Meta::mp->iMaxListenersPerChannel >= 0
&& Meta::mp->iMaxListenersPerChannel - m_channelListenerManager.getListenerCountForChannel(c->iId) - 1
< 0) {
// A limit for the amount of listener proxies per channel is set and it has been reached already
PERM_DENIED_FALLBACK(ChannelListenerLimit, Version::fromComponents(1, 4, 0),
QLatin1String("No more listeners allowed in this channel"));
continue;
}
if (Meta::mp->iMaxListenerProxiesPerUser >= 0
&& Meta::mp->iMaxListenerProxiesPerUser
- m_channelListenerManager.getListenedChannelCountForUser(uSource->uiSession)
- passedChannelListener - 1
< 0) {
// A limit for the amount of listener proxies per user is set and it has been reached already
PERM_DENIED_FALLBACK(UserListenerLimit, Version::fromComponents(1, 4, 0),
QLatin1String("No more listeners allowed in this channel"));
continue;
}
passedChannelListener++;
listeningChannelsAdd << c;
}
if (msg.has_mute() || msg.has_deaf() || msg.has_suppress() || msg.has_priority_speaker()) {
if (pDstServerUser->iId == 0) {
PERM_DENIED_TYPE(SuperUser);
return;
}
if (!hasPermission(uSource, pDstServerUser->cChannel, ChanACL::MuteDeafen) || msg.suppress()) {
PERM_DENIED(uSource, pDstServerUser->cChannel, ChanACL::MuteDeafen);
return;
}
}
if (msg.has_mute() || msg.has_deaf() || msg.has_suppress()) {
if (pDstServerUser->cChannel->bTemporary) {
// If the destination user is inside a temporary channel,
// the source user needs to have the MuteDeafen ACL in the first
// non-temporary parent channel.
Channel *c = pDstServerUser->cChannel;
while (c && c->bTemporary) {
c = c->cParent;
}
if (!c || !hasPermission(uSource, c, ChanACL::MuteDeafen)) {
PERM_DENIED_TYPE(TemporaryChannel);
return;
}
}
}
QString comment;
if (msg.has_comment()) {
bool changed = false;
comment = u8(msg.comment());
if (uSource != pDstServerUser) {
if (!hasPermission(uSource, root, ChanACL::ResetUserContent)) {
PERM_DENIED(uSource, root, ChanACL::ResetUserContent);
return;
}
if (comment.length() > 0) {
PERM_DENIED_TYPE(TextTooLong);
return;
}
}
if (!isTextAllowed(comment, changed)) {
PERM_DENIED_TYPE(TextTooLong);
return;
}
if (changed)
msg.set_comment(u8(comment));
}
if (msg.has_texture()) {
if (iMaxImageMessageLength > 0
&& (msg.texture().length() > static_cast< unsigned int >(iMaxImageMessageLength))) {
PERM_DENIED_TYPE(TextTooLong);
return;
}
if (uSource != pDstServerUser) {
if (!hasPermission(uSource, root, ChanACL::ResetUserContent)) {
PERM_DENIED(uSource, root, ChanACL::ResetUserContent);
return;
}
if (msg.texture().length() > 0) {
PERM_DENIED_TYPE(TextTooLong);
return;
}
}
}
if (msg.has_user_id()) {
ChanACL::Perm p = (uSource == pDstServerUser) ? ChanACL::SelfRegister : ChanACL::Register;
if ((pDstServerUser->iId >= 0) || !hasPermission(uSource, root, p)) {
PERM_DENIED(uSource, root, p);
return;
}
if (pDstServerUser->qsHash.isEmpty()) {
PERM_DENIED_HASH(pDstServerUser);
return;
}
}
// Prevent self-targeting state changes from being applied to others
if ((pDstServerUser != uSource)
&& (msg.has_self_deaf() || msg.has_self_mute() || msg.has_plugin_context() || msg.has_plugin_identity()
|| msg.has_recording() || msg.listening_channel_add_size() > 0
|| msg.listening_channel_remove_size() > 0)) {
return;
}
/*
-------------------- Permission checks done. Now act --------------------
*/
bool bBroadcast = false;
if (msg.has_texture()) {
QByteArray qba = blob(msg.texture());
if (pDstServerUser->iId > 0) {
// For registered users store the texture we just received in the database
if (!setTexture(pDstServerUser->iId, qba)) {
return;
}
} else {
// For unregistered users or SuperUser only get the hash
hashAssign(pDstServerUser->qbaTexture, pDstServerUser->qbaTextureHash, qba);
}
// The texture will be sent out later in this function
bBroadcast = true;
}
// Writing to bSelfMute, bSelfDeaf and ssContext
// requires holding a write lock on qrwlVoiceThread.
{
QWriteLocker wl(&qrwlVoiceThread);
if (msg.has_self_deaf()) {
pDstServerUser->bSelfDeaf = msg.self_deaf();
if (pDstServerUser->bSelfDeaf)
msg.set_self_mute(true);
bBroadcast = true;
}
if (msg.has_self_mute()) {
pDstServerUser->bSelfMute = msg.self_mute();
if (!pDstServerUser->bSelfMute) {
msg.set_self_deaf(false);
pDstServerUser->bSelfDeaf = false;
}
bBroadcast = true;
}
if (msg.has_plugin_context()) {
pDstServerUser->ssContext = msg.plugin_context();
// Make sure to clear this from the packet so we don't broadcast it
msg.clear_plugin_context();
}
}
if (msg.has_plugin_identity()) {
pDstServerUser->qsIdentity = u8(msg.plugin_identity());
// Make sure to clear this from the packet so we don't broadcast it
msg.clear_plugin_identity();
}
if (!comment.isNull()) {
hashAssign(pDstServerUser->qsComment, pDstServerUser->qbaCommentHash, comment);
if (pDstServerUser->iId >= 0) {
QMap< int, QString > info;
info.insert(ServerDB::User_Comment, pDstServerUser->qsComment);
setInfo(pDstServerUser->iId, info);
}
bBroadcast = true;
}
if (msg.has_mute() || msg.has_deaf() || msg.has_suppress() || msg.has_priority_speaker()) {
// Writing to bDeaf, bMute and bSuppress requires
// holding a write lock on qrwlVoiceThread.
QWriteLocker wl(&qrwlVoiceThread);
if (msg.has_deaf()) {
pDstServerUser->bDeaf = msg.deaf();
if (pDstServerUser->bDeaf)
msg.set_mute(true);
}
if (msg.has_mute()) {
pDstServerUser->bMute = msg.mute();
if (!pDstServerUser->bMute) {
msg.set_deaf(false);
pDstServerUser->bDeaf = false;
}
}
if (msg.has_suppress())
pDstServerUser->bSuppress = msg.suppress();
if (msg.has_priority_speaker())
pDstServerUser->bPrioritySpeaker = msg.priority_speaker();
log(uSource, QString("Changed speak-state of %1 (%2 %3 %4 %5)")
.arg(QString(*pDstServerUser), QString::number(pDstServerUser->bMute),
QString::number(pDstServerUser->bDeaf), QString::number(pDstServerUser->bSuppress),
QString::number(pDstServerUser->bPrioritySpeaker)));
bBroadcast = true;
}
if (msg.has_recording() && (pDstServerUser->bRecording != msg.recording())) {
assert(uSource == pDstServerUser);
pDstServerUser->bRecording = msg.recording();
MumbleProto::TextMessage mptm;
mptm.add_tree_id(0);
if (pDstServerUser->bRecording) {
if (!allowRecording) {
// User tried to start recording even though this server forbids it
// -> Kick user
MumbleProto::UserRemove mpur;
mpur.set_session(uSource->uiSession);
mpur.set_reason("Recording is not allowed on this server");
sendMessage(uSource, mpur);
uSource->forceFlush();
uSource->disconnectSocket(true);
// We just kicked this user, so there is no point in further processing his/her message
return;
} else {
mptm.set_message(u8(QString(QLatin1String("User '%1' started recording")).arg(pDstServerUser->qsName)));
}
} else {
mptm.set_message(u8(QString(QLatin1String("User '%1' stopped recording")).arg(pDstServerUser->qsName)));
}
sendAll(mptm, Version::fromComponents(1, 2, 3), Version::CompareMode::LessThan);
bBroadcast = true;
}
if (msg.has_channel_id()) {
Channel *c = qhChannels.value(msg.channel_id());
userEnterChannel(pDstServerUser, c, msg);
log(uSource, QString("Moved %1 to %2").arg(QString(*pDstServerUser), QString(*c)));
bBroadcast = true;
}
// Handle channel listening
// Note that it is important to handle the listening channels after channel-joins
QSet< unsigned int > volumeAdjustedChannels;
for (int i = 0; i < msg.listening_volume_adjustment_size(); i++) {
const MumbleProto::UserState::VolumeAdjustment &adjustment = msg.listening_volume_adjustment(i);
const Channel *channel = qhChannels.value(adjustment.listening_channel());
if (channel) {
setChannelListenerVolume(*pDstServerUser, *channel, adjustment.volume_adjustment());
volumeAdjustedChannels << channel->iId;
} else {
log(uSource, QString::fromLatin1("Invalid channel ID \"%1\" in volume adjustment")
.arg(adjustment.listening_channel()));
}
}
for (Channel *c : listeningChannelsAdd) {
addChannelListener(*pDstServerUser, *c);
log(QString::fromLatin1("\"%1\" is now listening to channel \"%2\"")
.arg(QString(*pDstServerUser))
.arg(QString(*c)));
float volumeAdjustment =
m_channelListenerManager.getListenerVolumeAdjustment(pDstServerUser->uiSession, c->iId).factor;
if (volumeAdjustment != 1.0f && !volumeAdjustedChannels.contains(c->iId)) {
MumbleProto::UserState::VolumeAdjustment *adjustment = msg.add_listening_volume_adjustment();
adjustment->set_listening_channel(c->iId);
adjustment->set_volume_adjustment(volumeAdjustment);
}
}
for (int i = 0; i < msg.listening_channel_remove_size(); i++) {
Channel *c = qhChannels.value(msg.listening_channel_remove(i));
if (c) {
disableChannelListener(*pDstServerUser, *c);
log(QString::fromLatin1("\"%1\" is no longer listening to \"%2\"")
.arg(QString(*pDstServerUser))
.arg(QString(*c)));
}
}
bool listenerVolumeChanged = msg.listening_volume_adjustment_size() > 0;
bool listenerChanged = !listeningChannelsAdd.isEmpty() || msg.listening_channel_remove_size() > 0;
bool broadcastingBecauseOfVolumeChange = !bBroadcast && listenerVolumeChanged;
bBroadcast = bBroadcast || listenerChanged || listenerVolumeChanged;
if (listenerChanged || listenerVolumeChanged) {
// As whisper targets also contain information about ChannelListeners and
// their associated volume adjustment, we have to clear the target cache
clearWhisperTargetCache();
}
bool bDstAclChanged = false;
if (msg.has_user_id()) {
// Handle user (Self-)Registration
QMap< int, QString > info;
info.insert(ServerDB::User_Name, pDstServerUser->qsName);
info.insert(ServerDB::User_Hash, pDstServerUser->qsHash);
if (!pDstServerUser->qslEmail.isEmpty())
info.insert(ServerDB::User_Email, pDstServerUser->qslEmail.first());
int id = registerUser(info);
if (id > 0) {
pDstServerUser->iId = id;
setLastChannel(pDstServerUser);
msg.set_user_id(static_cast< unsigned int >(id));
bDstAclChanged = true;
} else {
// Registration failed
msg.clear_user_id();
}
bBroadcast = true;
}
if (bBroadcast) {
// Texture handling for clients < 1.2.2.
// Send the texture data in the message.
if (msg.has_texture() && (pDstServerUser->qbaTexture.length() >= 4)
&& (qFromBigEndian< unsigned int >(
reinterpret_cast< const unsigned char * >(pDstServerUser->qbaTexture.constData()))
!= 600 * 60 * 4)) {
// This is a new style texture, don't send it because the client doesn't handle it correctly / crashes.
msg.clear_texture();
sendAll(msg, Version::fromComponents(1, 2, 2), Version::CompareMode::LessThan);
msg.set_texture(blob(pDstServerUser->qbaTexture));
} else {
// This is an old style texture, empty texture or there was no texture in this packet,
// send the message unchanged.
sendAll(msg, Version::fromComponents(1, 2, 2), Version::CompareMode::LessThan);
}
// Texture / comment handling for clients >= 1.2.2.
// Send only a hash of the texture / comment text. The client will request the actual data if necessary.
if (msg.has_texture() && !pDstServerUser->qbaTextureHash.isEmpty()) {
msg.clear_texture();
msg.set_texture_hash(blob(pDstServerUser->qbaTextureHash));
}
if (msg.has_comment() && !pDstServerUser->qbaCommentHash.isEmpty()) {
msg.clear_comment();
msg.set_comment_hash(blob(pDstServerUser->qbaCommentHash));
}
if (uSource->m_version >= Version::fromComponents(1, 2, 2)) {
sendMessage(uSource, msg);
}
if (!broadcastListenerVolumeAdjustments) {
// Don't broadcast the volume adjustments to everyone
msg.clear_listening_volume_adjustment();
}
if (broadcastListenerVolumeAdjustments || !broadcastingBecauseOfVolumeChange) {
sendExcept(uSource, msg, Version::fromComponents(1, 2, 2), Version::CompareMode::AtLeast);
}
if (bDstAclChanged) {
clearACLCache(pDstServerUser);
} else if (listenerChanged || listenerVolumeChanged) {
// We only have to do this if the ACLs didn't change as
// clearACLCache calls clearWhisperTargetChache anyways
clearWhisperTargetCache();
}
}
emit userStateChanged(pDstServerUser);
}
void Server::msgUserRemove(ServerUser *uSource, MumbleProto::UserRemove &msg) {
ZoneScoped;
MSG_SETUP(ServerUser::Authenticated);
VICTIM_SETUP;
msg.set_actor(uSource->uiSession);
bool ban = msg.has_ban() && msg.ban();
Channel *c = qhChannels.value(0);
QFlags< ChanACL::Perm > perm = ban ? ChanACL::Ban : (ChanACL::Ban | ChanACL::Kick);
if ((pDstServerUser->iId == 0) || !hasPermission(uSource, c, perm)) {
PERM_DENIED(uSource, c, perm);
return;
}
if (ban) {
Ban b;
b.haAddress = pDstServerUser->haAddress;
b.iMask = 128;
b.qsReason = u8(msg.reason());
b.qsUsername = pDstServerUser->qsName;
b.qsHash = pDstServerUser->qsHash;
b.qdtStart = QDateTime::currentDateTime().toUTC();
b.iDuration = 0;
qlBans << b;
saveBans();
}
sendAll(msg);
if (ban)
log(uSource, QString("Kickbanned %1 (%2)").arg(QString(*pDstServerUser), u8(msg.reason())));
else
log(uSource, QString("Kicked %1 (%2)").arg(QString(*pDstServerUser), u8(msg.reason())));
pDstServerUser->disconnectSocket();
}
void Server::msgChannelState(ServerUser *uSource, MumbleProto::ChannelState &msg) {
ZoneScoped;
MSG_SETUP(ServerUser::Authenticated);
Channel *c = nullptr;
Channel *p = nullptr;
// If this message relates to an existing channel check if the id is really valid
if (msg.has_channel_id()) {
c = qhChannels.value(msg.channel_id());
if (!c)
return;
} else {
RATELIMIT(uSource);
}
// Check if the parent exists
if (msg.has_parent()) {
p = qhChannels.value(msg.parent());
if (!p)
return;
}
msg.clear_links();
QString qsName;
QString qsDesc;
if (msg.has_description()) {
qsDesc = u8(msg.description());
bool changed = false;
if (!isTextAllowed(qsDesc, changed)) {
PERM_DENIED_TYPE(TextTooLong);
return;
}
if (changed)
msg.set_description(u8(qsDesc));
}
if (msg.has_name()) {
// If we are sent a channel name this means we want to create this channel so
// check if the name is valid and not already in use.
qsName = u8(msg.name());
if (!validateChannelName(qsName)) {
PERM_DENIED_TYPE(ChannelName);
return;
}
if (qsName.length() == 0) {
PERM_DENIED_TYPE(ChannelName);
return;
}
if (p || (c && c->iId != 0)) {
Channel *cp = p ? p : c->cParent;
foreach (Channel *sibling, cp->qlChannels) {
if (sibling->qsName == qsName) {
PERM_DENIED_TYPE(ChannelName);
return;
}
}
}
}
if (p) {
// Having a parent channel given means we either want to create
// a channel in or move a channel into this parent.
if (!canNest(p, c)) {
PERM_DENIED_FALLBACK(NestingLimit, Version::fromComponents(1, 2, 4),
QLatin1String("Channel nesting limit reached"));
return;
}
}
if (!c) {
// If we don't have a channel handle up to now we want to create a new channel
// so check if the user has enough rights and we got everything we need.
if (!p || qsName.isNull())
return;
if (iChannelCountLimit != 0 && qhChannels.count() >= iChannelCountLimit) {
PERM_DENIED_FALLBACK(ChannelCountLimit, Version::fromComponents(1, 3, 0),
QLatin1String("Channel count limit reached"));
return;
}
ChanACL::Perm perm = msg.temporary() ? ChanACL::MakeTempChannel : ChanACL::MakeChannel;
if (!hasPermission(uSource, p, perm)) {
PERM_DENIED(uSource, p, perm);
return;
}
if ((uSource->iId < 0) && uSource->qsHash.isEmpty()) {
PERM_DENIED_HASH(uSource);
return;
}
if (p->bTemporary) {
PERM_DENIED_TYPE(TemporaryChannel);
return;
}
c = addChannel(p, qsName, msg.temporary(), msg.position(), msg.max_users());
hashAssign(c->qsDesc, c->qbaDescHash, qsDesc);
if (uSource->iId >= 0) {
Group *g = new Group(c, "admin");
g->qsAdd << uSource->iId;
}
if (!hasPermission(uSource, c, ChanACL::Write)) {
ChanACL *a = new ChanACL(c);
a->bApplyHere = true;
a->bApplySubs = false;
if (uSource->iId >= 0)
a->iUserId = uSource->iId;
else
a->qsGroup = QLatin1Char('$') + uSource->qsHash;
a->pDeny = ChanACL::None;
a->pAllow = ChanACL::Write | ChanACL::Traverse;
clearACLCache();
}
updateChannel(c);
msg.set_channel_id(c->iId);
log(uSource, QString("Added channel %1 under %2").arg(QString(*c), QString(*p)));
emit channelCreated(c);
sendAll(msg, Version::fromComponents(1, 2, 2), Version::CompareMode::LessThan);
if (!c->qbaDescHash.isEmpty()) {
msg.clear_description();
msg.set_description_hash(blob(c->qbaDescHash));
}
sendAll(msg, Version::fromComponents(1, 2, 2), Version::CompareMode::AtLeast);
if (c->bTemporary) {
// If a temporary channel has been created move the creator right in there
MumbleProto::UserState mpus;
mpus.set_session(uSource->uiSession);
mpus.set_channel_id(c->iId);
userEnterChannel(uSource, c, mpus);
sendAll(mpus);
emit userStateChanged(uSource);
}
} else {
// The message is related to an existing channel c so check if the user is allowed to modify it
// and perform the modifications
if (!qsName.isNull()) {
if (!hasPermission(uSource, c, ChanACL::Write) || (c->iId == 0)) {
PERM_DENIED(uSource, c, ChanACL::Write);
return;
}
}
if (!qsDesc.isNull()) {
if (!hasPermission(uSource, c, ChanACL::Write)) {
PERM_DENIED(uSource, c, ChanACL::Write);
return;
}
}
if (msg.has_position()) {
if (!hasPermission(uSource, c, ChanACL::Write)) {
PERM_DENIED(uSource, c, ChanACL::Write);
return;
}
}
if (p) {
// If we received a parent channel check if it differs from the old one and is not
// Temporary. If that is the case check if the user has enough rights and if the
// channel name is not used in the target location. Abort otherwise.
if (p == c->cParent)
return;
Channel *ip = p;
while (ip) {
if (ip == c)
return;
ip = ip->cParent;
}
if (p->bTemporary) {
PERM_DENIED_TYPE(TemporaryChannel);
return;
}
if (!hasPermission(uSource, c, ChanACL::Write)) {
PERM_DENIED(uSource, c, ChanACL::Write);
return;
}
QFlags< ChanACL::Perm > parentMakePermission =
c->bTemporary ? ChanACL::MakeTempChannel : ChanACL::MakeChannel;
if (!hasPermission(uSource, p, parentMakePermission)) {
PERM_DENIED(uSource, p, parentMakePermission);
return;
}
QString name = qsName.isNull() ? c->qsName : qsName;
foreach (Channel *sibling, p->qlChannels) {
if (sibling->qsName == name) {
PERM_DENIED_TYPE(ChannelName);
return;
}
}
}
QList< Channel * > qlAdd;
QList< Channel * > qlRemove;
if (msg.links_add_size() || msg.links_remove_size()) {
if (!hasPermission(uSource, c, ChanACL::LinkChannel)) {
PERM_DENIED(uSource, c, ChanACL::LinkChannel);
return;
}
if (msg.links_remove_size()) {
for (int i = 0; i < msg.links_remove_size(); ++i) {
unsigned int link = msg.links_remove(i);
Channel *l = qhChannels.value(link);
if (!l)
return;
qlRemove << l;
}
}
if (msg.links_add_size()) {
for (int i = 0; i < msg.links_add_size(); ++i) {
unsigned int link = msg.links_add(i);
Channel *l = qhChannels.value(link);
if (!l)
return;
if (!hasPermission(uSource, l, ChanACL::LinkChannel)) {
PERM_DENIED(uSource, l, ChanACL::LinkChannel);
return;
}
qlAdd << l;
}
}
}
if (msg.has_max_users()) {
if (!hasPermission(uSource, c, ChanACL::Write)) {
PERM_DENIED(uSource, c, ChanACL::Write);
return;
}
}
// All permission checks done -- the update is good.
if (p) {
log(uSource, QString("Moved channel %1 from %2 to %3").arg(QString(*c), QString(*c->cParent), QString(*p)));
{
QWriteLocker wl(&qrwlVoiceThread);
c->cParent->removeChannel(c);
p->addChannel(c);
}
}
if (!qsName.isNull()) {
log(uSource, QString("Renamed channel %1 to %2").arg(QString(*c), QString(qsName)));
c->qsName = qsName;
}
if (!qsDesc.isNull())
hashAssign(c->qsDesc, c->qbaDescHash, qsDesc);
if (msg.has_position())
c->iPosition = msg.position();
foreach (Channel *l, qlAdd) { addLink(c, l); }
foreach (Channel *l, qlRemove) { removeLink(c, l); }
if (msg.has_max_users())
c->uiMaxUsers = msg.max_users();
updateChannel(c);
emit channelStateChanged(c);
sendAll(msg, Version::fromComponents(1, 2, 2), Version::CompareMode::LessThan);
if (msg.has_description() && !c->qbaDescHash.isEmpty()) {
msg.clear_description();
msg.set_description_hash(blob(c->qbaDescHash));
}
sendAll(msg, Version::fromComponents(1, 2, 2), Version::CompareMode::AtLeast);
}
}
void Server::msgChannelRemove(ServerUser *uSource, MumbleProto::ChannelRemove &msg) {
ZoneScoped;
MSG_SETUP(ServerUser::Authenticated);
Channel *c = qhChannels.value(msg.channel_id());
if (!c)
return;
if (!hasPermission(uSource, c, ChanACL::Write) || (c->iId == 0)) {
PERM_DENIED(uSource, c, ChanACL::Write);
return;
}
log(uSource, QString("Removed channel %1").arg(*c));
removeChannel(c);
}
void Server::msgTextMessage(ServerUser *uSource, MumbleProto::TextMessage &msg) {
ZoneScoped;
MSG_SETUP(ServerUser::Authenticated);
QMutexLocker qml(&qmCache);
// For signal userTextMessage (RPC consumers)
TextMessage tm;
// List of users to route the message to
QSet< ServerUser * > users;
// List of channels used if dest is a tree of channels
QQueue< Channel * > q;
RATELIMIT(uSource);
int res = 0;
emit textMessageFilterSig(res, uSource, msg);
switch (res) {
// Accept
case 0:
// No-op.
break;
// Reject
case 1:
PERM_DENIED(uSource, uSource->cChannel, ChanACL::TextMessage);
return;
// Drop
case 2:
return;
}
QString text = u8(msg.message());
bool changed = false;
if (!isTextAllowed(text, changed)) {
PERM_DENIED_TYPE(TextTooLong);
return;
}
if (text.isEmpty()) {
return;
}
if (changed) {
msg.set_message(u8(text));
}
tm.qsText = text;
{ // Happy easter
char m[29] = { 0117, 0160, 0145, 0156, 040, 0164, 0150, 0145, 040, 0160, 0157, 0144, 040, 0142, 0141,
0171, 040, 0144, 0157, 0157, 0162, 0163, 054, 040, 0110, 0101, 0114, 056, 0 };
if (msg.channel_id_size() == 1 && msg.channel_id(0) == 0 && msg.message() == m) {
PERM_DENIED_TYPE(H9K);
return;
}
}
msg.set_actor(uSource->uiSession);
// Send the message to all users that are in (= have joined) OR are
// "listening" to channels to which the message has been directed to
for (int i = 0; i < msg.channel_id_size(); ++i) {
unsigned int id = msg.channel_id(i);
Channel *c = qhChannels.value(id);
if (!c) {
return;
}
if (!ChanACL::hasPermission(uSource, c, ChanACL::TextMessage, &acCache)) {
PERM_DENIED(uSource, c, ChanACL::TextMessage);
return;
}
// Users directly in that channel
foreach (User *p, c->qlUsers) { users.insert(static_cast< ServerUser * >(p)); }
// Users only listening in that channel
foreach (unsigned int session, m_channelListenerManager.getListenersForChannel(c->iId)) {
ServerUser *currentUser = qhUsers.value(session);
if (currentUser) {
users.insert(currentUser);
}
}
tm.qlChannels.append(id);
}
// If the message is sent to trees of channels, find all affected channels
// and append them to q
for (int i = 0; i < msg.tree_id_size(); ++i) {
unsigned int id = msg.tree_id(i);
Channel *c = qhChannels.value(id);
if (!c) {
return;
}
if (!ChanACL::hasPermission(uSource, c, ChanACL::TextMessage, &acCache)) {
PERM_DENIED(uSource, c, ChanACL::TextMessage);
return;
}
q.enqueue(c);
tm.qlTrees.append(id);
}
// Go through all channels in q and append all users in those channels
// to the list of recipients
// Sub-channels are enqued so they are also checked by a later loop-iteration
while (!q.isEmpty()) {
Channel *c = q.dequeue();
if (ChanACL::hasPermission(uSource, c, ChanACL::TextMessage, &acCache)) {
foreach (Channel *sub, c->qlChannels) { q.enqueue(sub); }
// Users directly in that channel
foreach (User *p, c->qlUsers) { users.insert(static_cast< ServerUser * >(p)); }
// Users only listening in that channel
foreach (unsigned int session, m_channelListenerManager.getListenersForChannel(c->iId)) {
ServerUser *currentUser = qhUsers.value(session);
if (currentUser) {
users.insert(currentUser);
}
}
}
}
// Go through all users the message is sent to directly
for (int i = 0; i < msg.session_size(); ++i) {
unsigned int session = msg.session(i);
ServerUser *u = qhUsers.value(session);
if (u) {
if (!ChanACL::hasPermission(uSource, u->cChannel, ChanACL::TextMessage, &acCache)) {
PERM_DENIED(uSource, u->cChannel, ChanACL::TextMessage);
return;
}
users.insert(u);
}
tm.qlSessions.append(session);
}
// Remove the message sender from the list of users to send the message to
users.remove(uSource);
// Actually send the original message to the affected users
foreach (ServerUser *u, users) { sendMessage(u, msg); }
// Emit the signal for RPC consumers
emit userTextMessage(uSource, tm);
}
/// Helper function to log the groups of the given channel.
///
/// @param server A pointer to the server object the provided channel lives on
/// @param c A pointer to the channel the groups should be logged for
/// @param prefix An optional QString that is being printed before the groups
void logGroups(Server *server, const Channel *c, QString prefix = QString()) {
if (!prefix.isEmpty()) {
server->log(prefix);
}
if (c->qhGroups.isEmpty()) {
server->log(QString::fromLatin1("Channel %1 (%2) has no groups set").arg(c->qsName).arg(c->iId));
return;
} else {
server->log(QString::fromLatin1("%1Listing groups specified for channel \"%2\" (%3)...")
.arg(prefix.isEmpty() ? QLatin1String("") : QLatin1String("\t"))
.arg(c->qsName)
.arg(c->iId));
}
foreach (Group *currentGroup, c->qhGroups) {
QString memberList;
foreach (int m, currentGroup->members()) {
memberList += QString::fromLatin1("\"%1\"").arg(server->getUserName(m));
memberList += ", ";
}
if (currentGroup->members().size() > 0) {
memberList.remove(memberList.length() - 2, 2);
server->log(QString::fromLatin1("%1Group: \"%2\" contains following users: %3")
.arg(prefix.isEmpty() ? QLatin1String("\t") : QLatin1String("\t\t"))
.arg(currentGroup->qsName)
.arg(memberList));
} else {
server->log(QString::fromLatin1("%1Group \"%2\" doesn't contain any users")
.arg(prefix.isEmpty() ? QLatin1String("\t") : QLatin1String("\t\t"))
.arg(currentGroup->qsName));
}
}
}
/// Helper function to log the ACLs of the given channel.
///
/// @param server A pointer to the server object the provided channel lives on
/// @param c A pointer to the channel the ACLs should be logged for
/// @param prefix An optional QString that is being printed before the ACLs
void logACLs(Server *server, const Channel *c, QString prefix = QString()) {
if (!prefix.isEmpty()) {
server->log(prefix);
}
foreach (const ChanACL *a, c->qlACL) {
server->log(QString::fromLatin1("%1%2")
.arg(prefix.isEmpty() ? QLatin1String("") : QLatin1String("\t"))
.arg(static_cast< QString >(*a)));
}
}
void Server::msgACL(ServerUser *uSource, MumbleProto::ACL &msg) {
ZoneScoped;
MSG_SETUP(ServerUser::Authenticated);
Channel *c = qhChannels.value(msg.channel_id());
if (!c)
return;
// 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
// 2) We allow all users who have been granted 'Write' on the root channel
// to be able to edit _all_ channels, independent of actual propagated ACLs
// This is done to prevent users who have permission to create (temporary)
// channels being able to "lock-out" admins by denying them 'Write' in their
// channel effectively becoming ungovernable.
if (!hasPermission(uSource, c, ChanACL::Write) && !hasPermission(uSource, qhChannels.value(0), ChanACL::Write)) {
PERM_DENIED(uSource, c, ChanACL::Write);
return;
}
RATELIMIT(uSource);
if (msg.has_query() && msg.query()) {
QStack< Channel * > chans;
Channel *p;
ChanACL *acl;
QSet< unsigned int > qsId;
msg.clear_groups();
msg.clear_acls();
msg.clear_query();
msg.set_inherit_acls(c->bInheritACL);
p = c;
while (p) {
chans.push(p);
if ((p == c) || p->bInheritACL)
p = p->cParent;
else
p = nullptr;
}
while (!chans.isEmpty()) {
p = chans.pop();
foreach (acl, p->qlACL) {
if ((p == c) || (acl->bApplySubs)) {
MumbleProto::ACL_ChanACL *mpacl = msg.add_acls();
mpacl->set_inherited(p != c);
mpacl->set_apply_here(acl->bApplyHere);
mpacl->set_apply_subs(acl->bApplySubs);
if (acl->iUserId >= 0) {
mpacl->set_user_id(static_cast< unsigned int >(acl->iUserId));
qsId.insert(static_cast< unsigned int >(acl->iUserId));
} else
mpacl->set_group(u8(acl->qsGroup));
mpacl->set_grant(acl->pAllow);
mpacl->set_deny(acl->pDeny);
}
}
}
p = c->cParent;
QSet< QString > allnames = Group::groupNames(c);
foreach (const QString &name, allnames) {
Group *g = c->qhGroups.value(name);
Group *pg = p ? Group::getGroup(p, name) : nullptr;
MumbleProto::ACL_ChanGroup *group = msg.add_groups();
group->set_name(u8(name));
group->set_inherit(g ? g->bInherit : true);
group->set_inheritable(g ? g->bInheritable : true);
group->set_inherited(pg && pg->bInheritable);
if (g) {
foreach (int id, g->qsAdd) {
qsId.insert(static_cast< unsigned int >(id));
group->add_add(static_cast< unsigned int >(id));
}
foreach (int id, g->qsRemove) {
qsId.insert(static_cast< unsigned int >(id));
group->add_remove(static_cast< unsigned int >(id));
}
}
if (pg) {
foreach (int id, pg->members()) {
qsId.insert(static_cast< unsigned int >(id));
group->add_inherited_members(static_cast< unsigned int >(id));
}
}
}
sendMessage(uSource, msg);
MumbleProto::QueryUsers mpqu;
foreach (unsigned int id, qsId) {
QString uname = getUserName(static_cast< int >(id));
if (!uname.isEmpty()) {
mpqu.add_ids(id);
mpqu.add_names(u8(uname));
}
}
if (mpqu.ids_size())
sendMessage(uSource, mpqu);
} else {
Group *g;
ChanACL *a;
{
QWriteLocker wl(&qrwlVoiceThread);
QHash< QString, QSet< int > > hOldTemp;
if (Meta::mp->bLogGroupChanges || Meta::mp->bLogACLChanges) {
log(uSource, QString::fromLatin1("Updating ACL in channel %1").arg(*c));
}
if (Meta::mp->bLogGroupChanges) {
logGroups(this, c, QLatin1String("These are the groups before applying the change:"));
}
foreach (g, c->qhGroups) {
hOldTemp.insert(g->qsName, g->qsTemporary);
delete g;
}
if (Meta::mp->bLogACLChanges) {
logACLs(this, c, QLatin1String("These are the ACLs before applying the changed:"));
}
// Clear old ACLs
foreach (a, c->qlACL) { delete a; }
c->qhGroups.clear();
c->qlACL.clear();
c->bInheritACL = msg.inherit_acls();
// Add new groups
for (int i = 0; i < msg.groups_size(); ++i) {
const MumbleProto::ACL_ChanGroup &group = msg.groups(i);
g = new Group(c, u8(group.name()));
g->bInherit = group.inherit();
g->bInheritable = group.inheritable();
for (int j = 0; j < group.add_size(); ++j)
if (!getUserName(static_cast< int >(group.add(j))).isEmpty())
g->qsAdd << static_cast< int >(group.add(j));
for (int j = 0; j < group.remove_size(); ++j)
if (!getUserName(static_cast< int >(group.remove(j))).isEmpty())
g->qsRemove << static_cast< int >(group.remove(j));
g->qsTemporary = hOldTemp.value(g->qsName);
}
if (Meta::mp->bLogGroupChanges) {
logGroups(this, c, QLatin1String("And these are the new groups:"));
}
// Add new ACLs
for (int i = 0; i < msg.acls_size(); ++i) {
const MumbleProto::ACL_ChanACL &mpacl = msg.acls(i);
if (mpacl.has_user_id() && getUserName(static_cast< int >(mpacl.user_id())).isEmpty())
continue;
a = new ChanACL(c);
a->bApplyHere = mpacl.apply_here();
a->bApplySubs = mpacl.apply_subs();
if (mpacl.has_user_id())
a->iUserId = static_cast< int >(mpacl.user_id());
else
a->qsGroup = u8(mpacl.group());
a->pDeny = static_cast< ChanACL::Permissions >(mpacl.deny()) & ChanACL::All;
a->pAllow = static_cast< ChanACL::Permissions >(mpacl.grant()) & ChanACL::All;
}
if (Meta::mp->bLogACLChanges) {
logACLs(this, c, QLatin1String("And these are the new ACLs:"));
}
}
clearACLCache();
if (!hasPermission(uSource, c, ChanACL::Write) && ((uSource->iId >= 0) || !uSource->qsHash.isEmpty())) {
{
QWriteLocker wl(&qrwlVoiceThread);
a = new ChanACL(c);
a->bApplyHere = true;
a->bApplySubs = false;
if (uSource->iId >= 0)
a->iUserId = uSource->iId;
else
a->qsGroup = QLatin1Char('$') + uSource->qsHash;
a->iUserId = uSource->iId;
a->pDeny = ChanACL::None;
a->pAllow = ChanACL::Write | ChanACL::Traverse;
}
clearACLCache();
}
updateChannel(c);
log(uSource, QString("Updated ACL in channel %1").arg(*c));
// Send refreshed enter states of this channel to all clients
MumbleProto::ChannelState mpcs;
mpcs.set_channel_id(c->iId);
for (ServerUser *user : qhUsers) {
mpcs.set_is_enter_restricted(isChannelEnterRestricted(c));
mpcs.set_can_enter(hasPermission(user, c, ChanACL::Enter));
sendMessage(user, mpcs);
}
}
}
void Server::msgQueryUsers(ServerUser *uSource, MumbleProto::QueryUsers &msg) {
ZoneScoped;
MSG_SETUP(ServerUser::Authenticated);
MumbleProto::QueryUsers reply;
for (int i = 0; i < msg.ids_size(); ++i) {
unsigned int id = msg.ids(i);
const QString &name = getUserName(static_cast< int >(id));
if (!name.isEmpty()) {
reply.add_ids(id);
reply.add_names(u8(name));
}
}
for (int i = 0; i < msg.names_size(); ++i) {
QString name = u8(msg.names(i));
int id = getUserID(name);
if (id >= 0) {
name = getUserName(id);
reply.add_ids(static_cast< unsigned int >(id));
reply.add_names(u8(name));
}
}
sendMessage(uSource, reply);
}
void Server::msgPing(ServerUser *uSource, MumbleProto::Ping &msg) {
ZoneScoped;
MSG_SETUP_NO_UNIDLE(ServerUser::Authenticated);
QMutexLocker l(&uSource->qmCrypt);
uSource->csCrypt->uiRemoteGood = msg.good();
uSource->csCrypt->uiRemoteLate = msg.late();
uSource->csCrypt->uiRemoteLost = msg.lost();
uSource->csCrypt->uiRemoteResync = msg.resync();
uSource->dUDPPingAvg = msg.udp_ping_avg();
uSource->dUDPPingVar = msg.udp_ping_var();
uSource->uiUDPPackets = msg.udp_packets();
uSource->dTCPPingAvg = msg.tcp_ping_avg();
uSource->dTCPPingVar = msg.tcp_ping_var();
uSource->uiTCPPackets = msg.tcp_packets();
quint64 ts = msg.timestamp();
msg.Clear();
msg.set_timestamp(ts);
msg.set_good(uSource->csCrypt->uiGood);
msg.set_late(uSource->csCrypt->uiLate);
msg.set_lost(uSource->csCrypt->uiLost);
msg.set_resync(uSource->csCrypt->uiResync);
sendMessage(uSource, msg);
}
void Server::msgCryptSetup(ServerUser *uSource, MumbleProto::CryptSetup &msg) {
ZoneScoped;
MSG_SETUP_NO_UNIDLE(ServerUser::Authenticated);
QMutexLocker l(&uSource->qmCrypt);
if (!msg.has_client_nonce()) {
log(uSource, "Requested crypt-nonce resync");
msg.set_server_nonce(uSource->csCrypt->getEncryptIV());
sendMessage(uSource, msg);
} else {
const std::string &str = msg.client_nonce();
uSource->csCrypt->uiResync++;
if (!uSource->csCrypt->setDecryptIV(str)) {
qWarning("Messages: Cipher resync failed: Invalid nonce from the client!");
}
}
}
void Server::msgContextActionModify(ServerUser *, MumbleProto::ContextActionModify &) {
}
void Server::msgContextAction(ServerUser *uSource, MumbleProto::ContextAction &msg) {
ZoneScoped;
MSG_SETUP(ServerUser::Authenticated);
unsigned int session = msg.has_session() ? msg.session() : 0;
int id = msg.has_channel_id() ? static_cast< int >(msg.channel_id()) : -1;
if (session && !qhUsers.contains(session))
return;
if ((id >= 0) && !qhChannels.contains(static_cast< unsigned int >(id)))
return;
emit contextAction(uSource, u8(msg.action()), session, id);
}
/// @param str The std::string to convert
/// @param maxSize The maximum allowed size for this string
/// @returns The given std::string converted to a QString, if its size is less
/// than or equal to the given maxSize. If it is bigger, "[[Invalid]]"
/// is returned.
QString convertWithSizeRestriction(const std::string &str, size_t maxSize) {
if (str.size() > maxSize) {
return QLatin1String("[[Invalid]]");
}
return QString::fromStdString(str);
}
void Server::msgVersion(ServerUser *uSource, MumbleProto::Version &msg) {
ZoneScoped;
RATELIMIT(uSource);
uSource->m_version = MumbleProto::getVersion(msg);
if (msg.has_release()) {
uSource->qsRelease = convertWithSizeRestriction(msg.release(), 100);
}
if (msg.has_os()) {
uSource->qsOS = convertWithSizeRestriction(msg.os(), 40);
if (msg.has_os_version()) {
uSource->qsOSVersion = convertWithSizeRestriction(msg.os_version(), 60);
}
}
log(uSource, QString("Client version %1 (%2 %3: %4)")
.arg(Version::toString(uSource->m_version))
.arg(uSource->qsOS)
.arg(uSource->qsOSVersion)
.arg(uSource->qsRelease));
}
void Server::msgUserList(ServerUser *uSource, MumbleProto::UserList &msg) {
ZoneScoped;
MSG_SETUP(ServerUser::Authenticated);
// 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)) {
PERM_DENIED(uSource, qhChannels.value(0), ChanACL::Register);
return;
}
if (msg.users_size() == 0) {
// Query mode.
QList< UserInfo > users = getRegisteredUsersEx();
QList< UserInfo >::const_iterator it = users.constBegin();
for (; it != users.constEnd(); ++it) {
// Skip the SuperUser
if (it->user_id > 0) {
::MumbleProto::UserList_User *user = msg.add_users();
user->set_user_id(static_cast< unsigned int >(it->user_id));
user->set_name(u8(it->name));
if (it->last_channel >= 0) {
user->set_last_channel(static_cast< unsigned int >(*it->last_channel));
}
user->set_last_seen(u8(it->last_active.toString(Qt::ISODate)));
}
}
sendMessage(uSource, msg);
} else {
// Update mode
for (int i = 0; i < msg.users_size(); ++i) {
const MumbleProto::UserList_User &user = msg.users(i);
unsigned int id = user.user_id();
if (id == 0)
continue;
if (!user.has_name()) {
log(uSource, QString::fromLatin1("Unregistered user %1").arg(id));
unregisterUser(static_cast< int >(id));
} else {
const QString &name = u8(user.name()).trimmed();
if (validateUserName(name)) {
log(uSource, QString::fromLatin1("Renamed user %1 to '%2'").arg(QString::number(id), name));
QMap< int, QString > info;
info.insert(ServerDB::User_Name, name);
setInfo(static_cast< int >(id), info);
MumbleProto::UserState mpus;
foreach (ServerUser *serverUser, qhUsers) {
if (serverUser->iId == static_cast< int >(id)) {
serverUser->qsName = name;
mpus.set_session(serverUser->uiSession);
break;
}
}
if (mpus.has_session()) {
mpus.set_actor(uSource->uiSession);
mpus.set_name(u8(name));
sendAll(mpus);
}
} else {
MumbleProto::PermissionDenied mppd;
mppd.set_type(MumbleProto::PermissionDenied_DenyType_UserName);
if (uSource->m_version < Version::fromComponents(1, 2, 1))
mppd.set_reason(u8(QString::fromLatin1("%1 is not a valid username").arg(name)));
else
mppd.set_name(u8(name));
sendMessage(uSource, mppd);
}
}
}
}
}
void Server::msgVoiceTarget(ServerUser *uSource, MumbleProto::VoiceTarget &msg) {
ZoneScoped;
MSG_SETUP_NO_UNIDLE(ServerUser::Authenticated);
int target = static_cast< int >(msg.id());
if ((target < 1) || (target >= 0x1f))
return;
QWriteLocker lock(&qrwlVoiceThread);
uSource->qmTargetCache.remove(target);
int count = msg.targets_size();
if (count == 0) {
uSource->qmTargets.remove(target);
} else {
WhisperTarget wt;
for (int i = 0; i < count; ++i) {
const MumbleProto::VoiceTarget_Target &t = msg.targets(i);
for (int j = 0; j < t.session_size(); ++j) {
unsigned int s = t.session(j);
if (qhUsers.contains(s)) {
wt.sessions.push_back(s);
}
}
if (t.has_channel_id()) {
unsigned int id = t.channel_id();
if (qhChannels.contains(id)) {
WhisperTarget::Channel wtc;
wtc.id = id;
wtc.includeChildren = t.children();
wtc.includeLinks = t.links();
if (t.has_group()) {
wtc.targetGroup = u8(t.group());
}
wt.channels.push_back(wtc);
}
}
}
if (wt.sessions.empty() && wt.channels.empty()) {
uSource->qmTargets.remove(target);
} else {
uSource->qmTargets.insert(target, std::move(wt));
}
}
}
void Server::msgPermissionQuery(ServerUser *uSource, MumbleProto::PermissionQuery &msg) {
ZoneScoped;
MSG_SETUP_NO_UNIDLE(ServerUser::Authenticated);
Channel *c = qhChannels.value(msg.channel_id());
if (!c)
return;
sendClientPermission(uSource, c, true);
}
void Server::msgCodecVersion(ServerUser *, MumbleProto::CodecVersion &) {
}
void Server::msgUserStats(ServerUser *uSource, MumbleProto::UserStats &msg) {
ZoneScoped;
MSG_SETUP_NO_UNIDLE(ServerUser::Authenticated);
VICTIM_SETUP;
const BandwidthRecord &bwr = pDstServerUser->bwr;
const QList< QSslCertificate > &certs = pDstServerUser->peerCertificateChain();
bool extend = (uSource == pDstServerUser) || hasPermission(uSource, qhChannels.value(0), ChanACL::Register);
if (!extend && !hasPermission(uSource, pDstServerUser->cChannel, ChanACL::Enter)) {
PERM_DENIED(uSource, pDstServerUser->cChannel, ChanACL::Enter);
return;
}
bool details = extend;
bool local = extend || (pDstServerUser->cChannel == uSource->cChannel);
if (msg.stats_only())
details = false;
msg.Clear();
msg.set_session(pDstServerUser->uiSession);
if (details) {
foreach (const QSslCertificate &cert, certs) {
const QByteArray &der = cert.toDer();
msg.add_certificates(blob(der));
}
msg.set_strong_certificate(pDstServerUser->bVerified);
}
if (local) {
MumbleProto::UserStats_Stats *mpusss;
QMutexLocker l(&pDstServerUser->qmCrypt);
mpusss = msg.mutable_from_client();
mpusss->set_good(pDstServerUser->csCrypt->uiGood);
mpusss->set_late(pDstServerUser->csCrypt->uiLate);
mpusss->set_lost(pDstServerUser->csCrypt->uiLost);
mpusss->set_resync(pDstServerUser->csCrypt->uiResync);
mpusss = msg.mutable_from_server();
mpusss->set_good(pDstServerUser->csCrypt->uiRemoteGood);
mpusss->set_late(pDstServerUser->csCrypt->uiRemoteLate);
mpusss->set_lost(pDstServerUser->csCrypt->uiRemoteLost);
mpusss->set_resync(pDstServerUser->csCrypt->uiRemoteResync);
}
msg.set_udp_packets(pDstServerUser->uiUDPPackets);
msg.set_tcp_packets(pDstServerUser->uiTCPPackets);
msg.set_udp_ping_avg(pDstServerUser->dUDPPingAvg);
msg.set_udp_ping_var(pDstServerUser->dUDPPingVar);
msg.set_tcp_ping_avg(pDstServerUser->dTCPPingAvg);
msg.set_tcp_ping_var(pDstServerUser->dTCPPingVar);
if (details) {
MumbleProto::Version *mpv;
mpv = msg.mutable_version();
if (pDstServerUser->m_version != Version::UNKNOWN) {
MumbleProto::setVersion(*mpv, pDstServerUser->m_version);
}
if (!pDstServerUser->qsRelease.isEmpty()) {
mpv->set_release(u8(pDstServerUser->qsRelease));
}
if (!pDstServerUser->qsOS.isEmpty()) {
mpv->set_os(u8(pDstServerUser->qsOS));
if (!pDstServerUser->qsOSVersion.isEmpty())
mpv->set_os_version(u8(pDstServerUser->qsOSVersion));
}
foreach (int v, pDstServerUser->qlCodecs)
msg.add_celt_versions(v);
msg.set_opus(pDstServerUser->bOpus);
msg.set_address(pDstServerUser->haAddress.toStdString());
}
if (local)
msg.set_bandwidth(static_cast< unsigned int >(bwr.bandwidth()));
msg.set_onlinesecs(static_cast< unsigned int >(bwr.onlineSeconds()));
if (local)
msg.set_idlesecs(static_cast< unsigned int >(bwr.idleSeconds()));
sendMessage(uSource, msg);
}
void Server::msgRequestBlob(ServerUser *uSource, MumbleProto::RequestBlob &msg) {
ZoneScoped;
MSG_SETUP_NO_UNIDLE(ServerUser::Authenticated);
int ntextures = msg.session_texture_size();
int ncomments = msg.session_comment_size();
int ndescriptions = msg.channel_description_size();
if (ndescriptions) {
MumbleProto::ChannelState mpcs;
for (int i = 0; i < ndescriptions; ++i) {
unsigned int id = msg.channel_description(i);
Channel *c = qhChannels.value(id);
if (c && !c->qsDesc.isEmpty()) {
mpcs.set_channel_id(id);
mpcs.set_description(u8(c->qsDesc));
sendMessage(uSource, mpcs);
}
}
}
if (ntextures || ncomments) {
MumbleProto::UserState mpus;
for (int i = 0; i < ntextures; ++i) {
unsigned int session = msg.session_texture(i);
ServerUser *su = qhUsers.value(session);
if (su && !su->qbaTexture.isEmpty()) {
mpus.set_session(session);
mpus.set_texture(blob(su->qbaTexture));
sendMessage(uSource, mpus);
}
}
if (ntextures)
mpus.clear_texture();
for (int i = 0; i < ncomments; ++i) {
unsigned int session = msg.session_comment(i);
ServerUser *su = qhUsers.value(session);
if (su && !su->qsComment.isEmpty()) {
mpus.set_session(session);
mpus.set_comment(u8(su->qsComment));
sendMessage(uSource, mpus);
}
}
}
}
void Server::msgServerConfig(ServerUser *, MumbleProto::ServerConfig &) {
}
void Server::msgSuggestConfig(ServerUser *, MumbleProto::SuggestConfig &) {
}
void Server::msgPluginDataTransmission(ServerUser *sender, MumbleProto::PluginDataTransmission &msg) {
ZoneScoped;
// A client's plugin has sent us a message that we shall delegate to its receivers
if (sender->m_pluginMessageBucket.ratelimit(1)) {
qWarning("Dropping plugin message sent from \"%s\" (%d)", qUtf8Printable(sender->qsName), sender->uiSession);
return;
}
if (!msg.has_data() || !msg.has_dataid()) {
// Messages without data and/or without a data ID can't be used by the clients. Thus we don't even have to send
// them
return;
}
if (msg.data().size() > Mumble::Plugins::PluginMessage::MAX_DATA_LENGTH) {
qWarning("Dropping plugin message sent from \"%s\" (%d) - data too large", qUtf8Printable(sender->qsName),
sender->uiSession);
return;
}
if (msg.dataid().size() > Mumble::Plugins::PluginMessage::MAX_DATA_ID_LENGTH) {
qWarning("Dropping plugin message sent from \"%s\" (%d) - data ID too long", qUtf8Printable(sender->qsName),
sender->uiSession);
return;
}
// Always set the sender's session and don't rely on it being set correctly (would
// allow spoofing the sender's session)
msg.set_sendersession(sender->uiSession);
// Copy needed data from message in order to be able to remove info about receivers from the message as this doesn't
// matter for the client
size_t receiverAmount = static_cast< std::size_t >(msg.receiversessions_size());
const ::google::protobuf::RepeatedField<::google::protobuf::uint32 > receiverSessions = msg.receiversessions();
msg.clear_receiversessions();
QSet< uint32_t > uniqueReceivers;
uniqueReceivers.reserve(receiverSessions.size());
for (int i = 0; static_cast< size_t >(i) < receiverAmount; i++) {
uint32_t userSession = receiverSessions.Get(i);
if (!uniqueReceivers.contains(userSession)) {
uniqueReceivers.insert(userSession);
} else {
// Duplicate entry -> ignore
continue;
}
ServerUser *receiver = qhUsers.value(receiverSessions.Get(i));
if (receiver) {
// We can simply redirect the message we have received to the clients
sendMessage(receiver, msg);
}
}
}
#undef RATELIMIT
#undef MSG_SETUP
#undef MSG_SETUP_NO_UNIDLE
#undef VICTIM_SETUP
#undef PERM_DENIED
#undef PERM_DENIED_TYPE
#undef PERM_DENIED_FALLBACK
#undef PERM_DENIED_HASH