mumble/src/murmur/Messages.cpp

1721 lines
47 KiB
C++

/* Copyright (C) 2005-2011, Thorvald Natvig <thorvald@natvig.com>
Copyright (C) 2009-2011, Stefan Hacker <dd0t@users.sourceforge.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
- Neither the name of the Mumble Developers nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "murmur_pch.h"
#include "User.h"
#include "Channel.h"
#include "ACL.h"
#include "Group.h"
#include "Message.h"
#include "ServerDB.h"
#include "Connection.h"
#include "Server.h"
#include "ServerUser.h"
#include "Version.h"
#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<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->uiVersion < 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); \
}
void Server::msgAuthenticate(ServerUser *uSource, MumbleProto::Authenticate &msg) {
if ((msg.tokens_size() > 0) || (uSource->sState == ServerUser::Authenticated)) {
QStringList qsl;
for (int i=0;i<msg.tokens_size();++i)
qsl << u8(msg.tokens(i));
{
QMutexLocker qml(&qmCache);
uSource->qslAccessTokens = qsl;
}
clearACLCache(uSource);
}
MSG_SETUP(ServerUser::Connected);
Channel *root = qhChannels.value(0);
Channel *c;
uSource->qsName = u8(msg.username());
bool ok = false;
bool nameok = validateUserName(uSource->qsName);
QString pw = u8(msg.password());
// Fetch ID and stored username.
// Since this may call DBus, which may recall our dbus messages, this function needs
// to support re-entrancy, and also to support the fact that sessions may go away.
int id = authenticate(uSource->qsName, pw, 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 = NULL;
foreach(ServerUser *u, qhUsers) {
if (u == uSource)
continue;
if (((u->iId>=0) && (u->iId == uSource->iId)) ||
(u->qsName == uSource->qsName)) {
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) && (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) {
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
uSource->csCrypt.genKey();
MumbleProto::CryptSetup mpcrypt;
mpcrypt.set_key(std::string(reinterpret_cast<const char *>(uSource->csCrypt.raw_key), AES_BLOCK_SIZE));
mpcrypt.set_server_nonce(std::string(reinterpret_cast<const char *>(uSource->csCrypt.encrypt_iv), AES_BLOCK_SIZE));
mpcrypt.set_client_nonce(std::string(reinterpret_cast<const char *>(uSource->csCrypt.decrypt_iv), AES_BLOCK_SIZE));
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(NULL, 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->uiVersion >= 0x010202) && ! c->qbaDescHash.isEmpty())
mpcs.set_description_hash(blob(c->qbaDescHash));
else if (! c->qsDesc.isEmpty())
mpcs.set_description(u8(c->qsDesc));
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);
}
}
// Transmit user profile
MumbleProto::UserState mpus;
Channel *lc;
if (bRememberChan)
lc = qhChannels.value(readLastChannel(uSource->iId));
else
lc = qhChannels.value(iDefaultChan);
if (! lc || ! hasPermission(uSource, lc, ChanACL::Enter)) {
lc = qhChannels.value(iDefaultChan);
if (! lc || ! hasPermission(uSource, lc, ChanACL::Enter)) {
lc = root;
}
}
userEnterChannel(uSource, lc, mpus);
uSource->sState = ServerUser::Authenticated;
mpus.set_session(uSource->uiSession);
mpus.set_name(u8(uSource->qsName));
if (uSource->iId >= 0) {
mpus.set_user_id(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));
if (uSource->cChannel->iId != 0)
mpus.set_channel_id(uSource->cChannel->iId);
sendAll(mpus, 0x010202);
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, ~ 0x010202);
// 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(u->iId);
if (uSource->uiVersion >= 0x010202) {
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->uiVersion >= 0x010202) && ! 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));
sendMessage(uSource, mpus);
}
// Send syncronisation packet
MumbleProto::ServerSync mpss;
mpss.set_session(uSource->uiSession);
if (! qsWelcomeText.isEmpty())
mpss.set_welcome_text(u8(qsWelcomeText));
mpss.set_max_bandwidth(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);
MumbleProto::ServerConfig mpsc;
mpsc.set_allow_html(bAllowHTML);
mpsc.set_message_length(iMaxTextMessageLength);
mpsc.set_image_message_length(iMaxImageMessageLength);
mpsc.set_max_users(iMaxUsers);
sendMessage(uSource, mpsc);
MumbleProto::SuggestConfig mpsug;
if (! qvSuggestVersion.isNull())
mpsug.set_version(qvSuggestVersion.toUInt());
if (! qvSuggestPositional.isNull())
mpsug.set_positional(qvSuggestPositional.toBool());
if (! qvSuggestPushToTalk.isNull())
mpsug.set_push_to_talk(qvSuggestPushToTalk.toBool());
if (mpsug.ByteSize() > 0) {
sendMessage(uSource, mpsug);
}
log(uSource, "Authenticated");
emit userConnected(uSource);
}
void Server::msgBanList(ServerUser *uSource, MumbleProto::BanList &msg) {
MSG_SETUP(ServerUser::Authenticated);
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(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 {
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 = 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);
b.qdtStart.setTimeSpec(Qt::UTC);
} else {
b.qdtStart = QDateTime::currentDateTime().toUTC();
}
b.iDuration = be.duration();
if (b.isValid())
qlBans << b;
}
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 *uSource, MumbleProto::UDPTunnel &msg) {
MSG_SETUP_NO_UNIDLE(ServerUser::Authenticated);
const std::string &str = msg.packet();
int len = static_cast<int>(str.length());
if (len < 1)
return;
QReadLocker rl(&qrwlUsers);
processMsg(uSource, str.data(), len);
}
void Server::msgUserState(ServerUser *uSource, MumbleProto::UserState &msg) {
MSG_SETUP(ServerUser::Authenticated);
VICTIM_SETUP;
Channel *root = qhChannels.value(0);
/*
First check all permissions involved
*/
if ((pDstServerUser->iId == 0) && (uSource->iId != 0)) {
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 (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 (iMaxUsersPerChannel && (c->qlUsers.count() >= iMaxUsersPerChannel)) {
PERM_DENIED_FALLBACK(ChannelFull, 0x010201, QLatin1String("Channel is full"));
return;
}
}
if (msg.has_mute() || msg.has_deaf() || msg.has_suppress() || msg.has_priority_speaker()) {
if (pDstServerUser->iId == 0) {
PERM_DENIED_TYPE(SuperUser);
return;
}
if (uSource->cChannel->bTemporary) {
PERM_DENIED_TYPE(TemporaryChannel);
return;
}
if (! hasPermission(uSource, pDstServerUser->cChannel, ChanACL::MuteDeafen) || msg.suppress()) {
PERM_DENIED(uSource, pDstServerUser->cChannel, ChanACL::MuteDeafen);
return;
}
}
QString comment;
if (msg.has_comment()) {
bool changed = false;
comment = u8(msg.comment());
if (uSource != pDstServerUser) {
if (! hasPermission(uSource, root, ChanACL::Move)) {
PERM_DENIED(uSource, root, ChanACL::Move);
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::Move)) {
PERM_DENIED(uSource, root, ChanACL::Move);
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())) {
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;
}
if (msg.has_self_deaf()) {
uSource->bSelfDeaf = msg.self_deaf();
if (uSource->bSelfDeaf)
msg.set_self_mute(true);
bBroadcast = true;
}
if (msg.has_self_mute()) {
uSource->bSelfMute = msg.self_mute();
if (! uSource->bSelfMute) {
msg.set_self_deaf(false);
uSource->bSelfDeaf = false;
}
bBroadcast = true;
}
if (msg.has_plugin_context()) {
uSource->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()) {
uSource->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()) {
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())) {
pDstServerUser->bRecording = msg.recording();
MumbleProto::TextMessage mptm;
mptm.add_tree_id(0);
if (pDstServerUser->bRecording)
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, ~ 0x010203);
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;
}
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(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, ~ 0x010202);
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, ~ 0x010202);
}
// 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));
}
sendAll(msg, 0x010202);
if (bDstAclChanged)
clearACLCache(pDstServerUser);
}
emit userStateChanged(pDstServerUser);
}
void Server::msgUserRemove(ServerUser *uSource, MumbleProto::UserRemove &msg) {
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) {
MSG_SETUP(ServerUser::Authenticated);
Channel *c = NULL;
Channel *p = NULL;
// 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;
}
// 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;
}
QRegExp re2("\\w");
if (re2.indexIn(qsName) == -1) {
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, 0x010204, 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;
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());
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, ~ 0x010202);
if (! c->qbaDescHash.isEmpty()) {
msg.clear_description();
msg.set_description_hash(blob(c->qbaDescHash));
}
sendAll(msg, 0x010202);
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;
}
if (! hasPermission(uSource, p, ChanACL::MakeChannel)) {
PERM_DENIED(uSource, p, ChanACL::MakeChannel);
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;
}
}
}
// 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)));
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);
}
updateChannel(c);
emit channelStateChanged(c);
sendAll(msg, ~ 0x010202);
if (msg.has_description() && ! c->qbaDescHash.isEmpty()) {
msg.clear_description();
msg.set_description_hash(blob(c->qbaDescHash));
}
sendAll(msg, 0x010202);
}
}
void Server::msgChannelRemove(ServerUser *uSource, MumbleProto::ChannelRemove &msg) {
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) {
MSG_SETUP(ServerUser::Authenticated);
QMutexLocker qml(&qmCache);
TextMessage tm; // for signal userTextMessage
QSet<ServerUser *> users;
QQueue<Channel *> q;
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);
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;
}
foreach(User *p, c->qlUsers)
users.insert(static_cast<ServerUser *>(p));
tm.qlChannels.append(id);
}
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);
}
while (! q.isEmpty()) {
Channel *c = q.dequeue();
if (ChanACL::hasPermission(uSource, c, ChanACL::TextMessage, &acCache)) {
foreach(Channel *sub, c->qlChannels)
q.enqueue(sub);
foreach(User *p, c->qlUsers)
users.insert(static_cast<ServerUser *>(p));
}
}
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);
}
users.remove(uSource);
foreach(ServerUser *u, users)
sendMessage(u, msg);
emit userTextMessage(uSource, tm);
}
void Server::msgACL(ServerUser *uSource, MumbleProto::ACL &msg) {
MSG_SETUP(ServerUser::Authenticated);
Channel *c = qhChannels.value(msg.channel_id());
if (!c)
return;
if (! hasPermission(uSource, c, ChanACL::Write) && !(c->cParent && hasPermission(uSource, c->cParent, ChanACL::Write))) {
PERM_DENIED(uSource, c, ChanACL::Write);
return;
}
if (msg.has_query() && msg.query()) {
QStack<Channel *> chans;
Channel *p;
ChanACL *acl;
QSet<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 = NULL;
}
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(acl->iUserId);
qsId.insert(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) : NULL;
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 != NULL) && pg->bInheritable);
if (g) {
foreach(int id, g->qsAdd) {
qsId.insert(id);
group->add_add(id);
}
foreach(int id, g->qsRemove) {
qsId.insert(id);
group->add_remove(id);
}
}
if (pg)
foreach(int id, pg->members()) {
qsId.insert(id);
group->add_inherited_members(id);
}
}
sendMessage(uSource, msg);
MumbleProto::QueryUsers mpqu;
foreach(int id, qsId) {
QString uname=getUserName(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;
QHash<QString, QSet<int> > hOldTemp;
foreach(g, c->qhGroups) {
hOldTemp.insert(g->qsName, g->qsTemporary);
delete g;
}
foreach(a, c->qlACL)
delete a;
c->qhGroups.clear();
c->qlACL.clear();
c->bInheritACL = msg.inherit_acls();
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(group.add(j)).isEmpty())
g->qsAdd << group.add(j);
for (int j=0;j<group.remove_size();++j)
if (!getUserName(group.remove(j)).isEmpty())
g->qsRemove << group.remove(j);
g->qsTemporary = hOldTemp.value(g->qsName);
}
for (int i=0;i<msg.acls_size(); ++i) {
const MumbleProto::ACL_ChanACL &mpacl = msg.acls(i);
if (mpacl.has_user_id() && getUserName(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=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;
}
clearACLCache();
if (! hasPermission(uSource, c, ChanACL::Write) && ((uSource->iId >= 0) || !uSource->qsHash.isEmpty())) {
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));
}
}
void Server::msgQueryUsers(ServerUser *uSource, MumbleProto::QueryUsers &msg) {
MSG_SETUP(ServerUser::Authenticated);
MumbleProto::QueryUsers reply;
for (int i=0;i<msg.ids_size();++i) {
int id = msg.ids(i);
if (id >= 0) {
const QString &name = getUserName(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(id);
reply.add_names(u8(name));
}
}
sendMessage(uSource, reply);
}
void Server::msgPing(ServerUser *uSource, MumbleProto::Ping &msg) {
MSG_SETUP_NO_UNIDLE(ServerUser::Authenticated);
CryptState &cs=uSource->csCrypt;
cs.uiRemoteGood = msg.good();
cs.uiRemoteLate = msg.late();
cs.uiRemoteLost = msg.lost();
cs.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(cs.uiGood);
msg.set_late(cs.uiLate);
msg.set_lost(cs.uiLost);
msg.set_resync(cs.uiResync);
sendMessage(uSource, msg);
}
void Server::msgCryptSetup(ServerUser *uSource, MumbleProto::CryptSetup &msg) {
MSG_SETUP_NO_UNIDLE(ServerUser::Authenticated);
if (! msg.has_client_nonce()) {
log(uSource, "Requested crypt-nonce resync");
msg.set_server_nonce(std::string(reinterpret_cast<const char *>(uSource->csCrypt.encrypt_iv), AES_BLOCK_SIZE));
sendMessage(uSource, msg);
} else {
const std::string &str = msg.client_nonce();
if (str.size() == AES_BLOCK_SIZE) {
uSource->csCrypt.uiResync++;
memcpy(uSource->csCrypt.decrypt_iv, str.data(), AES_BLOCK_SIZE);
}
}
}
void Server::msgContextActionModify(ServerUser *, MumbleProto::ContextActionModify &) {
}
void Server::msgContextAction(ServerUser *uSource, MumbleProto::ContextAction &msg) {
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(id))
return;
emit contextAction(uSource, u8(msg.action()), session, id);
}
void Server::msgVersion(ServerUser *uSource, MumbleProto::Version &msg) {
if (msg.has_version())
uSource->uiVersion=msg.version();
if (msg.has_release())
uSource->qsRelease = u8(msg.release());
if (msg.has_os()) {
uSource->qsOS = u8(msg.os());
if (msg.has_os_version())
uSource->qsOSVersion = u8(msg.os_version());
}
log(uSource, QString("Client version %1 (%2: %3)").arg(MumbleVersion::toString(uSource->uiVersion)).arg(uSource->qsOS).arg(uSource->qsRelease));
}
void Server::msgUserList(ServerUser *uSource, MumbleProto::UserList &msg) {
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(it->user_id);
user->set_name(u8(it->name));
if (it->last_channel) {
user->set_last_channel(*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);
int id = user.user_id();
if (id == 0)
continue;
if (! user.has_name()) {
log(uSource, QString::fromLatin1("Unregistered user %1").arg(id));
unregisterUser(id);
} else {
const QString &name = u8(user.name());
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(id, info);
MumbleProto::UserState mpus;
foreach(ServerUser *serverUser, qhUsers) {
if (serverUser->iId == id) {
serverUser->qsName = name;
mpus.set_session(serverUser->uiSession);
break;
}
}
if (mpus.has_session()) {
mpus.set_name(u8(name));
sendAll(mpus);
}
} else {
MumbleProto::PermissionDenied mppd;
mppd.set_type(MumbleProto::PermissionDenied_DenyType_UserName);
if (uSource->uiVersion < 0x010201)
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) {
MSG_SETUP_NO_UNIDLE(ServerUser::Authenticated);
int target = msg.id();
if ((target < 1) || (target >= 0x1f))
return;
QWriteLocker lock(&qrwlUsers);
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.qlSessions << s;
}
if (t.has_channel_id()) {
unsigned int id = t.channel_id();
if (qhChannels.contains(id)) {
WhisperTarget::Channel wtc;
wtc.iId = id;
wtc.bChildren = t.children();
wtc.bLinks = t.links();
if (t.has_group())
wtc.qsGroup = u8(t.group());
wt.qlChannels << wtc;
}
}
}
if (wt.qlSessions.isEmpty() && wt.qlChannels.isEmpty())
uSource->qmTargets.remove(target);
else
uSource->qmTargets.insert(target, wt);
}
}
void Server::msgPermissionQuery(ServerUser *uSource, MumbleProto::PermissionQuery &msg) {
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) {
MSG_SETUP_NO_UNIDLE(ServerUser::Authenticated);
VICTIM_SETUP;
const CryptState &cs = pDstServerUser->csCrypt;
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;
mpusss = msg.mutable_from_client();
mpusss->set_good(cs.uiGood);
mpusss->set_late(cs.uiLate);
mpusss->set_lost(cs.uiLost);
mpusss->set_resync(cs.uiResync);
mpusss = msg.mutable_from_server();
mpusss->set_good(cs.uiRemoteGood);
mpusss->set_late(cs.uiRemoteLate);
mpusss->set_lost(cs.uiRemoteLost);
mpusss->set_resync(cs.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->uiVersion)
mpv->set_version(pDstServerUser->uiVersion);
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(bwr.bandwidth());
msg.set_onlinesecs(bwr.onlineSeconds());
if (local)
msg.set_idlesecs(bwr.idleSeconds());
sendMessage(uSource, msg);
}
void Server::msgRequestBlob(ServerUser *uSource, MumbleProto::RequestBlob &msg) {
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) {
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) {
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) {
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 &) {
}