mirror of
https://github.com/mumble-voip/mumble.git
synced 2025-10-26 11:19:16 +00:00
This allows server admins to explicitly trigger the broadcast of the
currently set welcome text to a given set of users.
By first setting a new welcome text and then broadcasting it to all
currently connected users, the old behavior of setting the welcome
message (changed in da26386827) can be
restored.
Fixes #4805
590 lines
16 KiB
C++
590 lines
16 KiB
C++
// Copyright 2008-2021 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 <QtCore/QtGlobal>
|
|
|
|
#ifdef Q_OS_WIN
|
|
# include "win.h"
|
|
#endif
|
|
|
|
#include "Channel.h"
|
|
#include "ChannelListener.h"
|
|
#include "Group.h"
|
|
#include "Meta.h"
|
|
#include "Server.h"
|
|
#include "ServerDB.h"
|
|
#include "ServerUser.h"
|
|
#include "Version.h"
|
|
|
|
#ifdef Q_OS_WIN
|
|
# include <winsock2.h>
|
|
#endif
|
|
|
|
void Server::setUserState(User *pUser, Channel *cChannel, bool mute, bool deaf, bool suppressed, bool prioritySpeaker,
|
|
const QString &name, const QString &comment) {
|
|
bool changed = false;
|
|
|
|
if (deaf)
|
|
mute = true;
|
|
if (!mute)
|
|
deaf = false;
|
|
|
|
MumbleProto::UserState mpus;
|
|
mpus.set_session(pUser->uiSession);
|
|
if (mute != pUser->bMute) {
|
|
changed = true;
|
|
mpus.set_mute(mute);
|
|
}
|
|
if (deaf != pUser->bDeaf) {
|
|
changed = true;
|
|
mpus.set_deaf(deaf);
|
|
}
|
|
if (suppressed != pUser->bSuppress) {
|
|
changed = true;
|
|
mpus.set_suppress(suppressed);
|
|
}
|
|
if (prioritySpeaker != pUser->bPrioritySpeaker) {
|
|
changed = true;
|
|
mpus.set_priority_speaker(prioritySpeaker);
|
|
}
|
|
if (comment != pUser->qsComment) {
|
|
changed = true;
|
|
mpus.set_comment(u8(comment));
|
|
if (pUser->iId >= 0) {
|
|
QMap< int, QString > info;
|
|
info.insert(ServerDB::User_Comment, comment);
|
|
setInfo(pUser->iId, info);
|
|
}
|
|
}
|
|
if (name != pUser->qsName) {
|
|
changed = true;
|
|
mpus.set_name(u8(name));
|
|
}
|
|
|
|
{
|
|
QWriteLocker wl(&qrwlVoiceThread);
|
|
pUser->bDeaf = deaf;
|
|
pUser->bMute = mute;
|
|
pUser->bSuppress = suppressed;
|
|
}
|
|
|
|
pUser->bPrioritySpeaker = prioritySpeaker;
|
|
pUser->qsName = name;
|
|
hashAssign(pUser->qsComment, pUser->qbaCommentHash, comment);
|
|
|
|
if (cChannel != pUser->cChannel) {
|
|
changed = true;
|
|
mpus.set_channel_id(cChannel->iId);
|
|
userEnterChannel(pUser, cChannel, mpus);
|
|
}
|
|
|
|
if (changed) {
|
|
sendAll(mpus, ~0x010202);
|
|
if (mpus.has_comment() && !pUser->qbaCommentHash.isEmpty()) {
|
|
mpus.clear_comment();
|
|
mpus.set_comment_hash(blob(pUser->qbaCommentHash));
|
|
}
|
|
sendAll(mpus, 0x010202);
|
|
|
|
emit userStateChanged(pUser);
|
|
}
|
|
}
|
|
|
|
// Sets err to error message on failure.
|
|
bool Server::setChannelStateGRPC(const MumbleProto::ChannelState &cs, QString &err) {
|
|
::MumbleProto::ChannelState mpcs;
|
|
bool changed = false;
|
|
bool updated = false;
|
|
|
|
if (!cs.has_channel_id()) {
|
|
err = QLatin1String("missing channel ID");
|
|
return false;
|
|
}
|
|
Channel *channel = qhChannels.value(cs.channel_id());
|
|
if (!channel) {
|
|
err = QLatin1String("invalid channel");
|
|
return false;
|
|
}
|
|
mpcs.set_channel_id(cs.channel_id());
|
|
|
|
// Links and parent channel are processed first, because they can return
|
|
// errors. Without doing this, the server state can be changed without
|
|
// notifying users.
|
|
QSet<::Channel * > newLinksSet;
|
|
for (int i = 0; i < cs.links_size(); i++) {
|
|
Channel *link = qhChannels.value(cs.links(i));
|
|
if (!link) {
|
|
err = QLatin1String("invalid channel link");
|
|
return false;
|
|
}
|
|
newLinksSet.insert(link);
|
|
}
|
|
|
|
if (cs.has_parent()) {
|
|
Channel *parent = qhChannels.value(cs.parent());
|
|
if (!parent) {
|
|
err = QLatin1String("invalid parent channel");
|
|
return false;
|
|
}
|
|
if (parent != channel->cParent) {
|
|
Channel *p = parent;
|
|
while (p) {
|
|
if (p == channel) {
|
|
err = QLatin1String("parent channel cannot be a descendant of channel");
|
|
return false;
|
|
}
|
|
p = p->cParent;
|
|
}
|
|
if (!canNest(parent, channel)) {
|
|
err = QLatin1String("channel cannot be nested in the given parent");
|
|
return false;
|
|
}
|
|
|
|
{
|
|
QWriteLocker wl(&qrwlVoiceThread);
|
|
channel->cParent->removeChannel(channel);
|
|
parent->addChannel(channel);
|
|
}
|
|
|
|
mpcs.set_parent(parent->iId);
|
|
|
|
changed = true;
|
|
updated = true;
|
|
}
|
|
}
|
|
|
|
if (cs.has_name()) {
|
|
QString qsName = u8(cs.name());
|
|
if (channel->qsName != qsName) {
|
|
channel->qsName = qsName;
|
|
mpcs.set_name(cs.name());
|
|
|
|
changed = true;
|
|
updated = true;
|
|
}
|
|
}
|
|
|
|
const QSet< Channel * > &oldLinksSet = channel->qsPermLinks;
|
|
|
|
if (newLinksSet != oldLinksSet) {
|
|
// Remove
|
|
foreach (Channel *l, oldLinksSet) {
|
|
if (!newLinksSet.contains(l)) {
|
|
removeLink(channel, l);
|
|
mpcs.add_links_remove(l->iId);
|
|
}
|
|
}
|
|
// Add
|
|
foreach (Channel *l, newLinksSet) {
|
|
if (!oldLinksSet.contains(l)) {
|
|
addLink(channel, l);
|
|
mpcs.add_links_add(l->iId);
|
|
}
|
|
}
|
|
|
|
changed = true;
|
|
}
|
|
|
|
if (cs.has_position() && cs.position() != channel->iPosition) {
|
|
channel->iPosition = cs.position();
|
|
mpcs.set_position(cs.position());
|
|
|
|
changed = true;
|
|
updated = true;
|
|
}
|
|
|
|
if (cs.has_description()) {
|
|
QString qsDescription = u8(cs.description());
|
|
if (qsDescription != channel->qsDesc) {
|
|
hashAssign(channel->qsDesc, channel->qbaDescHash, qsDescription);
|
|
mpcs.set_description(cs.description());
|
|
|
|
changed = true;
|
|
updated = true;
|
|
}
|
|
}
|
|
|
|
if (updated) {
|
|
updateChannel(channel);
|
|
}
|
|
if (changed) {
|
|
sendAll(mpcs, ~0x010202);
|
|
if (mpcs.has_description() && !channel->qbaDescHash.isEmpty()) {
|
|
mpcs.clear_description();
|
|
mpcs.set_description_hash(blob(channel->qbaDescHash));
|
|
}
|
|
sendAll(mpcs, 0x010202);
|
|
emit channelStateChanged(channel);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Server::setChannelState(Channel *cChannel, Channel *cParent, const QString &qsName, const QSet< Channel * > &links,
|
|
const QString &desc, const int position) {
|
|
bool changed = false;
|
|
bool updated = false;
|
|
|
|
MumbleProto::ChannelState mpcs;
|
|
mpcs.set_channel_id(cChannel->iId);
|
|
|
|
if (cChannel->qsName != qsName) {
|
|
cChannel->qsName = qsName;
|
|
mpcs.set_name(u8(qsName));
|
|
updated = true;
|
|
changed = true;
|
|
}
|
|
|
|
if ((cParent != cChannel) && (cParent != cChannel->cParent)) {
|
|
Channel *p = cParent;
|
|
while (p) {
|
|
if (p == cChannel)
|
|
return false;
|
|
p = p->cParent;
|
|
}
|
|
|
|
if (!canNest(cParent, cChannel)) {
|
|
return false;
|
|
}
|
|
|
|
{
|
|
QWriteLocker wl(&qrwlVoiceThread);
|
|
cChannel->cParent->removeChannel(cChannel);
|
|
cParent->addChannel(cChannel);
|
|
}
|
|
|
|
mpcs.set_parent(cParent->iId);
|
|
|
|
updated = true;
|
|
changed = true;
|
|
}
|
|
|
|
const QSet< Channel * > &oldset = cChannel->qsPermLinks;
|
|
|
|
if (links != oldset) {
|
|
// Remove
|
|
foreach (Channel *l, oldset) {
|
|
if (!links.contains(l)) {
|
|
removeLink(cChannel, l);
|
|
mpcs.add_links_remove(l->iId);
|
|
}
|
|
}
|
|
|
|
// Add
|
|
foreach (Channel *l, links) {
|
|
if (!oldset.contains(l)) {
|
|
addLink(cChannel, l);
|
|
mpcs.add_links_add(l->iId);
|
|
}
|
|
}
|
|
|
|
changed = true;
|
|
}
|
|
|
|
if (position != cChannel->iPosition) {
|
|
changed = true;
|
|
updated = true;
|
|
cChannel->iPosition = position;
|
|
mpcs.set_position(position);
|
|
}
|
|
|
|
if (!desc.isNull() && desc != cChannel->qsDesc) {
|
|
updated = true;
|
|
changed = true;
|
|
hashAssign(cChannel->qsDesc, cChannel->qbaDescHash, desc);
|
|
mpcs.set_description(u8(desc));
|
|
}
|
|
|
|
if (updated)
|
|
updateChannel(cChannel);
|
|
if (changed) {
|
|
sendAll(mpcs, ~0x010202);
|
|
if (mpcs.has_description() && !cChannel->qbaDescHash.isEmpty()) {
|
|
mpcs.clear_description();
|
|
mpcs.set_description_hash(blob(cChannel->qbaDescHash));
|
|
}
|
|
sendAll(mpcs, 0x010202);
|
|
emit channelStateChanged(cChannel);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Server::sendTextMessageGRPC(const ::MumbleProto::TextMessage &tm) {
|
|
MumbleProto::TextMessage mptm;
|
|
mptm.set_message(tm.message());
|
|
|
|
if (tm.has_actor()) {
|
|
mptm.set_actor(tm.actor());
|
|
}
|
|
|
|
// Broadcast
|
|
if (!tm.session_size() && !tm.channel_id_size() && !tm.tree_id_size()) {
|
|
sendAll(mptm);
|
|
return;
|
|
}
|
|
|
|
// Single targets
|
|
for (int i = 0; i < tm.session_size(); i++) {
|
|
ServerUser *user = qhUsers.value(tm.session(i));
|
|
if (!user) {
|
|
continue;
|
|
}
|
|
mptm.add_session(user->uiSession);
|
|
sendMessage(user, mptm);
|
|
mptm.clear_session();
|
|
}
|
|
|
|
// Channel targets
|
|
QSet< Channel * > chans;
|
|
|
|
for (int i = 0; i < tm.channel_id_size(); i++) {
|
|
Channel *channel = qhChannels.value(tm.channel_id(i));
|
|
if (!channel) {
|
|
continue;
|
|
}
|
|
chans.insert(channel);
|
|
mptm.add_channel_id(channel->iId);
|
|
}
|
|
|
|
QQueue< Channel * > chansQ;
|
|
for (int i = 0; i < tm.tree_id_size(); i++) {
|
|
Channel *channel = qhChannels.value(tm.tree_id(i));
|
|
if (!channel) {
|
|
continue;
|
|
}
|
|
chansQ.enqueue(channel);
|
|
mptm.add_tree_id(channel->iId);
|
|
}
|
|
while (!chansQ.isEmpty()) {
|
|
Channel *c = chansQ.dequeue();
|
|
chans.insert(c);
|
|
foreach (c, c->qlChannels) { chansQ.enqueue(c); }
|
|
}
|
|
|
|
foreach (Channel *c, chans) {
|
|
foreach (::User *p, c->qlUsers) { sendMessage(static_cast<::ServerUser * >(p), mptm); }
|
|
}
|
|
}
|
|
|
|
void Server::sendTextMessage(Channel *cChannel, ServerUser *pUser, bool tree, const QString &text) {
|
|
MumbleProto::TextMessage mptm;
|
|
mptm.set_message(u8(text));
|
|
|
|
if (pUser) {
|
|
mptm.add_session(pUser->uiSession);
|
|
sendMessage(pUser, mptm);
|
|
} else {
|
|
if (tree)
|
|
mptm.add_tree_id(cChannel->iId);
|
|
else
|
|
mptm.add_channel_id(cChannel->iId);
|
|
|
|
QSet< Channel * > chans;
|
|
QQueue< Channel * > q;
|
|
q << cChannel;
|
|
chans.insert(cChannel);
|
|
Channel *c;
|
|
|
|
if (tree) {
|
|
while (!q.isEmpty()) {
|
|
c = q.dequeue();
|
|
chans.insert(c);
|
|
foreach (c, c->qlChannels)
|
|
q.enqueue(c);
|
|
}
|
|
}
|
|
foreach (c, chans) {
|
|
foreach (User *p, c->qlUsers)
|
|
sendMessage(static_cast< ServerUser * >(p), mptm);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the list of temporary groups the given userid is a member of. If no channel is given root will
|
|
* be targeted.
|
|
*
|
|
* If userid is negative the absolute value is a session id. If it is positive it is a registration id.
|
|
*/
|
|
void Server::setTempGroups(int userid, int sessionId, Channel *cChannel, const QStringList &groups) {
|
|
if (!cChannel)
|
|
cChannel = qhChannels.value(0);
|
|
|
|
{
|
|
QWriteLocker wl(&qrwlVoiceThread);
|
|
|
|
Group *g;
|
|
foreach (g, cChannel->qhGroups) {
|
|
g->qsTemporary.remove(userid);
|
|
if (sessionId != 0)
|
|
g->qsTemporary.remove(-sessionId);
|
|
}
|
|
|
|
QString gname;
|
|
foreach (gname, groups) {
|
|
g = cChannel->qhGroups.value(gname);
|
|
if (!g) {
|
|
g = new Group(cChannel, gname);
|
|
}
|
|
g->qsTemporary.insert(userid);
|
|
if (sessionId != 0)
|
|
g->qsTemporary.insert(-sessionId);
|
|
}
|
|
}
|
|
|
|
User *p = qhUsers.value(userid);
|
|
if (p)
|
|
clearACLCache(p);
|
|
}
|
|
|
|
/**
|
|
* Clears temporary group memberships for the given User. If no channel is given root will be targeted.
|
|
* If recursion is activated all temporary memberships in related channels will also be cleared.
|
|
*/
|
|
void Server::clearTempGroups(User *user, Channel *cChannel, bool recurse) {
|
|
QList< Channel * > qlChans;
|
|
if (!cChannel)
|
|
cChannel = qhChannels.value(0);
|
|
|
|
qlChans.append(cChannel);
|
|
|
|
{
|
|
QWriteLocker wl(&qrwlVoiceThread);
|
|
|
|
while (!qlChans.isEmpty()) {
|
|
Channel *chan = qlChans.takeLast();
|
|
Group *g;
|
|
foreach (g, chan->qhGroups) {
|
|
g->qsTemporary.remove(user->iId);
|
|
g->qsTemporary.remove(-static_cast< int >(user->uiSession));
|
|
}
|
|
|
|
if (recurse)
|
|
qlChans << chan->qlChannels;
|
|
}
|
|
}
|
|
|
|
clearACLCache(user);
|
|
}
|
|
|
|
void Server::connectAuthenticator(QObject *obj) {
|
|
connect(this, SIGNAL(registerUserSig(int &, const QMap< int, QString > &)), obj,
|
|
SLOT(registerUserSlot(int &, const QMap< int, QString > &)));
|
|
connect(this, SIGNAL(unregisterUserSig(int &, int)), obj, SLOT(unregisterUserSlot(int &, int)));
|
|
connect(this, SIGNAL(getRegisteredUsersSig(const QString &, QMap< int, QString > &)), obj,
|
|
SLOT(getRegisteredUsersSlot(const QString &, QMap< int, QString > &)));
|
|
connect(this, SIGNAL(getRegistrationSig(int &, int, QMap< int, QString > &)), obj,
|
|
SLOT(getRegistrationSlot(int &, int, QMap< int, QString > &)));
|
|
connect(this,
|
|
SIGNAL(authenticateSig(int &, QString &, int, const QList< QSslCertificate > &, const QString &, bool,
|
|
const QString &)),
|
|
obj,
|
|
SLOT(authenticateSlot(int &, QString &, int, const QList< QSslCertificate > &, const QString &, bool,
|
|
const QString &)));
|
|
connect(this, SIGNAL(setInfoSig(int &, int, const QMap< int, QString > &)), obj,
|
|
SLOT(setInfoSlot(int &, int, const QMap< int, QString > &)));
|
|
connect(this, SIGNAL(setTextureSig(int &, int, const QByteArray &)), obj,
|
|
SLOT(setTextureSlot(int &, int, const QByteArray &)));
|
|
connect(this, SIGNAL(idToNameSig(QString &, int)), obj, SLOT(idToNameSlot(QString &, int)));
|
|
connect(this, SIGNAL(nameToIdSig(int &, const QString &)), obj, SLOT(nameToIdSlot(int &, const QString &)));
|
|
connect(this, SIGNAL(idToTextureSig(QByteArray &, int)), obj, SLOT(idToTextureSlot(QByteArray &, int)));
|
|
}
|
|
|
|
void Server::disconnectAuthenticator(QObject *obj) {
|
|
disconnect(this, SIGNAL(registerUserSig(int &, const QMap< int, QString > &)), obj,
|
|
SLOT(registerUserSlot(int &, const QMap< int, QString > &)));
|
|
disconnect(this, SIGNAL(unregisterUserSig(int &, int)), obj, SLOT(unregisterUserSlot(int &, int)));
|
|
disconnect(this, SIGNAL(getRegisteredUsersSig(const QString &, QMap< int, QString > &)), obj,
|
|
SLOT(getRegisteredUsersSlot(const QString &, QMap< int, QString > &)));
|
|
disconnect(this, SIGNAL(getRegistrationSig(int &, int, QMap< int, QString > &)), obj,
|
|
SLOT(getRegistrationSlot(int &, int, QMap< int, QString > &)));
|
|
disconnect(this,
|
|
SIGNAL(authenticateSig(int &, QString &, int, const QList< QSslCertificate > &, const QString &, bool,
|
|
const QString &)),
|
|
obj,
|
|
SLOT(authenticateSlot(int &, QString &, int, const QList< QSslCertificate > &, const QString &, bool,
|
|
const QString &)));
|
|
disconnect(this, SIGNAL(setInfoSig(int &, int, const QMap< int, QString > &)), obj,
|
|
SLOT(setInfoSlot(int &, int, const QMap< int, QString > &)));
|
|
disconnect(this, SIGNAL(setTextureSig(int &, int, const QByteArray &)), obj,
|
|
SLOT(setTextureSlot(int &, int, const QByteArray &)));
|
|
disconnect(this, SIGNAL(idToNameSig(QString &, int)), obj, SLOT(idToNameSlot(QString &, int)));
|
|
disconnect(this, SIGNAL(nameToIdSig(int &, const QString &)), obj, SLOT(nameToIdSlot(int &, const QString &)));
|
|
disconnect(this, SIGNAL(idToTextureSig(QByteArray &, int)), obj, SLOT(idToTextureSlot(QByteArray &, int)));
|
|
}
|
|
|
|
void Server::connectListener(QObject *obj) {
|
|
connect(this, SIGNAL(userStateChanged(const User *)), obj, SLOT(userStateChanged(const User *)));
|
|
connect(this, SIGNAL(userTextMessage(const User *, const TextMessage &)), obj,
|
|
SLOT(userTextMessage(const User *, const TextMessage &)));
|
|
connect(this, SIGNAL(userConnected(const User *)), obj, SLOT(userConnected(const User *)));
|
|
connect(this, SIGNAL(userDisconnected(const User *)), obj, SLOT(userDisconnected(const User *)));
|
|
connect(this, SIGNAL(channelStateChanged(const Channel *)), obj, SLOT(channelStateChanged(const Channel *)));
|
|
connect(this, SIGNAL(channelCreated(const Channel *)), obj, SLOT(channelCreated(const Channel *)));
|
|
connect(this, SIGNAL(channelRemoved(const Channel *)), obj, SLOT(channelRemoved(const Channel *)));
|
|
}
|
|
|
|
void Server::disconnectListener(QObject *obj) {
|
|
disconnect(this, SIGNAL(userStateChanged(const User *)), obj, SLOT(userStateChanged(const User *)));
|
|
disconnect(this, SIGNAL(userTextMessage(const User *, const TextMessage &)), obj,
|
|
SLOT(userTextMessage(const User *, const TextMessage &)));
|
|
disconnect(this, SIGNAL(userConnected(const User *)), obj, SLOT(userConnected(const User *)));
|
|
disconnect(this, SIGNAL(userDisconnected(const User *)), obj, SLOT(userDisconnected(const User *)));
|
|
disconnect(this, SIGNAL(channelStateChanged(const Channel *)), obj, SLOT(channelStateChanged(const Channel *)));
|
|
disconnect(this, SIGNAL(channelCreated(const Channel *)), obj, SLOT(channelCreated(const Channel *)));
|
|
disconnect(this, SIGNAL(channelRemoved(const Channel *)), obj, SLOT(channelRemoved(const Channel *)));
|
|
}
|
|
|
|
void Server::startListeningToChannel(ServerUser *user, Channel *cChannel) {
|
|
if (ChannelListener::isListening(user, cChannel)) {
|
|
// The user is already listening to this channel
|
|
return;
|
|
}
|
|
|
|
ChannelListener::addListener(user, cChannel);
|
|
|
|
MumbleProto::UserState mpus;
|
|
mpus.set_session(user->uiSession);
|
|
|
|
mpus.add_listening_channel_add(cChannel->iId);
|
|
|
|
sendAll(mpus);
|
|
}
|
|
|
|
void Server::stopListeningToChannel(ServerUser *user, Channel *cChannel) {
|
|
if (!ChannelListener::isListening(user, cChannel)) {
|
|
// The user is not listening to this channel
|
|
return;
|
|
}
|
|
|
|
ChannelListener::removeListener(user, cChannel);
|
|
|
|
MumbleProto::UserState mpus;
|
|
mpus.set_session(user->uiSession);
|
|
|
|
mpus.add_listening_channel_remove(cChannel->iId);
|
|
|
|
sendAll(mpus);
|
|
}
|
|
|
|
void Server::sendWelcomeMessageTo(ServerUser *user) {
|
|
MumbleProto::ServerConfig mpsc;
|
|
mpsc.set_welcome_text(qsWelcomeText.toUtf8().data());
|
|
|
|
sendMessage(user, mpsc);
|
|
}
|
|
|
|
void Meta::connectListener(QObject *obj) {
|
|
connect(this, SIGNAL(started(Server *)), obj, SLOT(started(Server *)));
|
|
connect(this, SIGNAL(stopped(Server *)), obj, SLOT(stopped(Server *)));
|
|
}
|
|
|
|
void Meta::getVersion(int &major, int &minor, int &patch, QString &string) {
|
|
string = QLatin1String(MUMBLE_RELEASE);
|
|
major = minor = patch = 0;
|
|
MumbleVersion::get(&major, &minor, &patch);
|
|
}
|