Teaspeak-Server/server/src/client/ConnectedClient.cpp

1295 lines
54 KiB
C++

#include <iostream>
#include <algorithm>
#include <bitset>
#include <memory>
#include <Definitions.h>
#include <misc/sassert.h>
#include <misc/memtracker.h>
#include <log/LogUtils.h>
#include <ThreadPool/Timer.h>
#include "src/VirtualServer.h"
#include "voice/VoiceClient.h"
#include "../InstanceHandler.h"
#include "../PermissionCalculator.h"
#include "../groups/GroupManager.h"
#include "../groups/Group.h"
#include <event.h>
using namespace std;
using namespace std::chrono;
using namespace ts;
using namespace ts::server;
extern ts::server::InstanceHandler* serverInstance;
ConnectedClient::ConnectedClient(sql::SqlManager* db, const std::shared_ptr<VirtualServer>&server) : DataClient(db, server) {
memtrack::allocated<ConnectedClient>(this);
memset(&this->remote_address, 0, sizeof(this->remote_address));
connectionStatistics = make_shared<stats::ConnectionStatistics>(server ? server->getServerStatistics() : nullptr);
channels = make_shared<ClientChannelView>(this);
}
ConnectedClient::~ConnectedClient() {
memtrack::freed<ConnectedClient>(this);
}
void ConnectedClient::initialize_weak_reference(const std::shared_ptr<ConnectedClient> &self) {
assert(this == &*self);
this->_this = self;
auto weak_self = std::weak_ptr{self};
this->task_update_needed_permissions = multi_shot_task{serverInstance->general_task_executor(), "update permissions for " + this->getLoggingPeerIp(), [weak_self]{
auto self = weak_self.lock();
if(self) {
auto permissions_changed = self->update_client_needed_permissions();
logTrace(self->getServerId(), "{} Updated client permissions. Permissions changed: {}", CLIENT_STR_LOG_PREFIX_(self), permissions_changed);
if(permissions_changed) {
self->sendNeededPermissions(true);
}
}
}};
this->task_update_channel_client_properties = multi_shot_task{serverInstance->general_task_executor(), "update channel properties for " + this->getLoggingPeerIp(), [weak_self]{
auto self = weak_self.lock();
if(self) {
self->updateChannelClientProperties(true, true);
}
}};
this->task_update_displayed_groups = multi_shot_task{serverInstance->general_task_executor(), "update displayed groups for " + this->getLoggingPeerIp(), [weak_self]{
auto self = weak_self.lock();
if(self) {
bool changed{false};
self->update_displayed_client_groups(changed, changed);
}
}};
}
bool ConnectedClient::loadDataForCurrentServer() {
auto result = DataClient::loadDataForCurrentServer();
if(!result) {
return false;
}
this->properties()[property::CONNECTION_CLIENT_IP] = this->getLoggingPeerIp();
return true;
}
std::shared_ptr<ConnectionInfoData> ConnectedClient::request_connection_info(const std::shared_ptr<ConnectedClient> &receiver, bool& send_temp) {
auto& info = this->connection_info;
lock_guard info_lock(info.lock);
if(info.data){
if(chrono::system_clock::now() - info.data_age < chrono::seconds(1))
return info.data;
if(chrono::system_clock::now() - info.data_age > chrono::seconds(5)) //Data timeout
info.data = nullptr;
}
if(receiver) {
info.receiver.erase(std::remove_if(info.receiver.begin(), info.receiver.end(), [&](const weak_ptr<ConnectedClient>& weak) {
auto locked = weak.lock();
if(locked == receiver) {
send_temp = true;
return true;
}
return !locked;
}), info.receiver.end());
info.receiver.push_back(receiver);
}
if(chrono::system_clock::now() - info.last_requested >= chrono::seconds(1)) {
info.last_requested = chrono::system_clock::now();
Command cmd("notifyconnectioninforequest");
string invoker;
for(const auto& weak_request : info.receiver) {
auto request = weak_request.lock();
if(!request) continue;
invoker += (invoker.empty() ? "" : ",") + to_string(request->getClientId());
}
cmd["invokerids"] = invoker;
this->sendCommand(cmd);
}
return info.data;
}
void ConnectedClient::updateChannelClientProperties(bool lock_channel_tree, bool notify_self) {
/* The server and the current channel might change while executing this method! */
auto server_ref = this->server;
auto channel = this->currentChannel;
auto permissions = this->calculate_permissions({
permission::i_client_talk_power,
permission::b_client_ignore_antiflood,
permission::i_channel_view_power,
permission::b_channel_ignore_view_power,
}, channel ? channel->channelId() : 0);
permission::v2::PermissionFlaggedValue
permission_talk_power{0, false},
permission_ignore_antiflood{0, false},
permission_channel_view_power{0, false},
permission_channel_ignore_view_power{0, false};
for(const auto& perm : permissions) {
if(perm.first == permission::i_client_talk_power) {
permission_talk_power = perm.second;
} else if(perm.first == permission::b_client_ignore_antiflood) {
permission_ignore_antiflood = perm.second;
} else if(perm.first == permission::i_channel_view_power) {
permission_channel_view_power = perm.second;
} else if(perm.first == permission::b_channel_ignore_view_power) {
permission_channel_ignore_view_power = perm.second;
} else {
sassert(false);
}
}
std::deque<property::ClientProperties> updated_client_properties;
{
auto old_talk_power = this->properties()[property::CLIENT_TALK_POWER].as_or<int64_t>(0);
auto new_talk_power = permission_talk_power.has_value ? permission_talk_power.value : 0;
debugMessage(this->getServerId(), "{} Recalculated talk power. New value: {} Old value: {}", CLIENT_STR_LOG_PREFIX, new_talk_power, old_talk_power);
if(old_talk_power != new_talk_power) {
this->properties()[property::CLIENT_TALK_POWER] = new_talk_power;
updated_client_properties.emplace_back(property::CLIENT_TALK_POWER);
auto retract_request = this->properties()[property::CLIENT_IS_TALKER].as_or<bool>(false);
if(!retract_request && channel) {
retract_request = channel->talk_power_granted(permission_talk_power);
}
if(retract_request) {
if(this->properties()[property::CLIENT_IS_TALKER].update_value(0)) {
updated_client_properties.emplace_back(property::CLIENT_IS_TALKER);
}
if(this->properties()[property::CLIENT_TALK_REQUEST].update_value(0)) {
updated_client_properties.emplace_back(property::CLIENT_TALK_REQUEST);
}
if(this->properties()[property::CLIENT_TALK_REQUEST_MSG].update_value("")) {
updated_client_properties.emplace_back(property::CLIENT_TALK_REQUEST_MSG);
}
}
}
}
{
IconId current_icon_id = this->properties()[property::CLIENT_ICON_ID].as_or<IconId>(0);
IconId new_icon_id{ 0};
auto local_permissions = this->clientPermissions;
if(local_permissions) {
permission::v2::PermissionFlaggedValue value{0, false};
auto permission_flags = local_permissions->permission_flags(permission::i_icon_id);
if(permission_flags.channel_specific && this->currentChannel) {
auto val = local_permissions->channel_permission(permission::i_icon_id, this->currentChannel->channelId());
value = { val.values.value, val.flags.value_set };
}
if(!value.has_value) {
value = local_permissions->permission_value_flagged(permission::i_icon_id);
}
if(value.has_value) {
new_icon_id = value.value;
}
}
if(this->properties()[property::CLIENT_ICON_ID].update_value(new_icon_id)) {
logTrace(this->getServerId(), "{} Updating client icon from {} to {}", CLIENT_STR_LOG_PREFIX, current_icon_id, new_icon_id);
updated_client_properties.emplace_back(property::CLIENT_ICON_ID);
}
}
{
auto local_permissions = this->clientPermissions;
auto permission_speaker = local_permissions ?
local_permissions->channel_permission(permission::b_client_is_priority_speaker, channel ? channel->channelId() : 0) :
permission::v2::empty_channel_permission;
auto speaker_granted = permission::v2::permission_granted(1, { permission_speaker.values.value, permission_speaker.flags.value_set });
if(properties()[property::CLIENT_IS_PRIORITY_SPEAKER].update_value(speaker_granted)){
updated_client_properties.emplace_back(property::CLIENT_IS_PRIORITY_SPEAKER);
}
}
block_flood = !permission::v2::permission_granted(1, permission_ignore_antiflood);
if(server_ref) {
server_ref->notifyClientPropertyUpdates(this->ref(), updated_client_properties, notify_self);
}
this->updateTalkRights(permission_talk_power);
if((this->channels_view_power != permission_channel_view_power || this->channels_ignore_view != permission_channel_ignore_view_power) && notify_self && channel && server_ref) {
this->channels_view_power = permission_channel_view_power;
this->channels_ignore_view = permission_channel_ignore_view_power;
shared_lock server_channel_lock(server_ref->channel_tree_lock, defer_lock);
unique_lock client_channel_lock(this->channel_lock, defer_lock);
if(lock_channel_tree) {
/* first read lock server channel tree */
server_channel_lock.lock();
client_channel_lock.lock();
}
/* might have been changed since we locked the tree */
if(channel) {
deque<ChannelId> deleted;
for(const auto& update_entry : this->channels->update_channel_path(server_ref->channelTree->tree_head(), server_ref->channelTree->find_linked_entry(channel->channelId()))) {
if(update_entry.first) {
this->notifyChannelShow(update_entry.second->channel(), update_entry.second->previous_channel);
} else {
deleted.push_back(update_entry.second->channelId());
}
}
if(!deleted.empty()) {
this->notifyChannelHide(deleted, false); /* we've locked the tree before */
}
}
}
}
void ConnectedClient::updateTalkRights(permission::v2::PermissionFlaggedValue talk_power) {
bool flag = false;
flag |= this->properties()[property::CLIENT_IS_TALKER].as_or<bool>(false);
auto current_channel = this->currentChannel;
if(!flag && current_channel) {
flag = current_channel->talk_power_granted(talk_power);
}
this->allowedToTalk = flag;
}
void ConnectedClient::resetIdleTime() {
this->idleTimestamp = chrono::system_clock::now();
}
void ConnectedClient::increaseFloodPoints(uint16_t num) {
this->floodPoints += num;
}
bool ConnectedClient::shouldFloodBlock() {
if(!this->server) return false;
if(!this->block_flood) return false;
return this->floodPoints >
this->server->properties()[property::VIRTUALSERVER_ANTIFLOOD_POINTS_NEEDED_COMMAND_BLOCK].as_or<uint16_t>(150);
}
std::deque<std::shared_ptr<BasicChannel>> ConnectedClient::subscribeChannel(const std::deque<std::shared_ptr<BasicChannel>>& targets, bool lock_channel, bool enforce) {
deque<std::shared_ptr<BasicChannel>> subscribed_channels;
auto ref_server = this->server;
if(!ref_server)
return {};
auto general_granted = enforce || permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_ignore_subscribe_power, 0));
{
shared_lock server_channel_lock(ref_server->channel_tree_lock, defer_lock);
unique_lock client_channel_lock(this->channel_lock, defer_lock);
if(lock_channel) {
server_channel_lock.lock();
client_channel_lock.lock();
}
auto cache = make_shared<CalculateCache>();
for (const auto& channel : targets) {
auto local_channel = this->channels->find_channel(channel);
if(!local_channel) continue; //Not visible
if(local_channel->subscribed) continue; //Already subscribed
if(!general_granted && channel != this->currentChannel) {
auto granted_permission = this->calculate_permission(permission::i_channel_subscribe_power, channel->channelId());
if(!channel->permission_granted(permission::i_channel_needed_subscribe_power, granted_permission, false)) {
auto ignore_power = this->calculate_permission(permission::b_channel_ignore_subscribe_power, channel->channelId());
if(!ignore_power.has_value || ignore_power.value < 1)
continue;
}
}
local_channel->subscribed = true;
subscribed_channels.push_back(channel);
}
deque<shared_ptr<ConnectedClient>> visible_clients;
for(const auto& target_channel : subscribed_channels) {
/* ref_server->getClientsByChannel only acquires channel client lock */
for(const auto& client : ref_server->getClientsByChannel(target_channel)) {
visible_clients.push_back(client);
}
}
this->notifyClientEnterView(visible_clients, ViewReasonSystem);
if (!subscribed_channels.empty())
this->notifyChannelSubscribed(subscribed_channels);
}
return subscribed_channels;
}
std::deque<std::shared_ptr<BasicChannel>> ConnectedClient::unsubscribeChannel(const std::deque<std::shared_ptr<BasicChannel>>& targets, bool lock_channel) {
auto ref_server = this->server;
if(!ref_server)
return {};
deque<std::shared_ptr<BasicChannel> > unsubscribed_channels;
{
shared_lock server_channel_lock(ref_server->channel_tree_lock, defer_lock);
unique_lock client_channel_lock(this->channel_lock, defer_lock);
if(lock_channel) {
server_channel_lock.lock();
client_channel_lock.lock();
}
for (const auto& channel : targets) {
if(this->currentChannel == channel) continue;
auto chan = this->channels->find_channel(channel);
if(!chan || !chan->subscribed) continue;
chan->subscribed = false;
/* ref_server->getClientsByChannel only acquires channel client lock */
auto clients = this->server->getClientsByChannel(channel);
this->visibleClients.erase(std::remove_if(this->visibleClients.begin(), this->visibleClients.end(), [&, clients](const weak_ptr<ConnectedClient>& weak) {
auto c = weak.lock();
if(!c) {
logError(this->getServerId(), "{} Got \"dead\" client in visible client list! This can cause a remote client disconnect within the future!", CLIENT_STR_LOG_PREFIX);
return true;
}
return std::find(clients.begin(), clients.end(), c) != clients.end();
}), this->visibleClients.end());
unsubscribed_channels.push_back(channel);
}
if (!unsubscribed_channels.empty())
this->notifyChannelUnsubscribed(unsubscribed_channels);
}
return unsubscribed_channels;
}
bool ConnectedClient::isClientVisible(const std::shared_ptr<ts::server::ConnectedClient>& client, bool lock) {
for(const auto& entry : this->getVisibleClients(lock))
if(entry.lock() == client)
return true;
return false;
}
bool ConnectedClient::notifyClientLeftView(const std::deque<std::shared_ptr<ts::server::ConnectedClient>> &clients, const std::string &reason, bool lock, const ts::ViewReasonServerLeftT &_vrsl) {
if(clients.empty())
return true;
if(lock) {
/* TODO: add locking of server channel tree in read mode and client tree in write mode! */
assert(false);
}
Command cmd("notifyclientleftview");
cmd["reasonmsg"] = reason;
cmd["reasonid"] = ViewReasonId::VREASON_SERVER_LEFT;
cmd["ctid"] = 0;
ChannelId current_channel_id = 0;
size_t index = 0;
auto it = clients.begin();
while(it != clients.end()) {
auto client = *it;
assert(client->getClientId() > 0);
assert(client->currentChannel || &*client == this);
if(!client->currentChannel)
continue;
if(current_channel_id != (client->currentChannel ? client->currentChannel->channelId() : 0)) {
if(current_channel_id != 0)
break;
else
cmd[index]["cfid"] = (current_channel_id = client->currentChannel->channelId());
}
cmd[index]["clid"] = client->getClientId();
it++;
index++;
}
this->visibleClients.erase(std::remove_if(this->visibleClients.begin(), this->visibleClients.end(), [&](const weak_ptr<ConnectedClient>& weak) {
auto c = weak.lock();
if(!c) {
logError(this->getServerId(), "{} Got \"dead\" client in visible client list! This can cause a remote client disconnect within the future!", CLIENT_STR_LOG_PREFIX);
return true;
}
return std::find(clients.begin(), it, c) != it;
}), this->visibleClients.end());
this->sendCommand(cmd);
if(it != clients.end())
return this->notifyClientLeftView({it, clients.end()}, reason, false, _vrsl);
return true;
}
bool ConnectedClient::notifyClientLeftView(
const shared_ptr<ConnectedClient> &client,
const std::shared_ptr<BasicChannel> &target_channel,
ViewReasonId reason_id,
const std::string& reason_message,
std::shared_ptr<ConnectedClient> invoker,
bool lock_channel_tree) {
assert(!lock_channel_tree); /* not supported yet! */
assert(client == this || (client && client->getClientId() != 0));
assert(client->currentChannel || &*client == this);
if(client != this) {
if(reason_id == VREASON_SERVER_STOPPED || reason_id == VREASON_SERVER_SHUTDOWN) {
debugMessage(this->getServerId(), "Replacing notify left reason " + to_string(reason_id) + " with " + to_string(VREASON_SERVER_LEFT));
reason_id = VREASON_SERVER_LEFT;
}
}
/*
switch (reasonId) {
case ViewReasonId::VREASON_MOVED:
case ViewReasonId::VREASON_BAN:
case ViewReasonId::VREASON_CHANNEL_KICK:
case ViewReasonId::VREASON_SERVER_KICK:
case ViewReasonId::VREASON_SERVER_SHUTDOWN:
case ViewReasonId::VREASON_SERVER_STOPPED:
if(reasonMessage.empty()) {
logCritical(this->getServerId(), "{} ConnectedClient::notifyClientLeftView() => missing reason message for reason id {}", CLIENT_STR_LOG_PREFIX, reasonId);
reasonMessage = "<undefined>";
}
break;
default:
break;
}
*/
switch (reason_id) {
case ViewReasonId::VREASON_MOVED:
case ViewReasonId::VREASON_BAN:
case ViewReasonId::VREASON_CHANNEL_KICK:
case ViewReasonId::VREASON_SERVER_KICK:
if(!invoker) {
logCritical(this->getServerId(), "{} ConnectedClient::notifyClientLeftView() => missing invoker for reason id {}", CLIENT_STR_LOG_PREFIX, reason_id);
if(this->server)
invoker = this->server->serverRoot;
}
break;
case ViewReasonId::VREASON_USER_ACTION:
case ViewReasonId::VREASON_SYSTEM:
case ViewReasonId::VREASON_TIMEOUT:
case ViewReasonId::VREASON_SERVER_STOPPED:
case ViewReasonId::VREASON_SERVER_LEFT:
case ViewReasonId::VREASON_CHANNEL_UPDATED:
case ViewReasonId::VREASON_EDITED:
case ViewReasonId::VREASON_SERVER_SHUTDOWN:
default:
break;
}
Command cmd("notifyclientleftview");
cmd["reasonmsg"] = reason_message;
cmd["reasonid"] = reason_id;
cmd["clid"] = client->getClientId();
cmd["cfid"] = client->currentChannel ? client->currentChannel->channelId() : 0;
cmd["ctid"] = target_channel ? target_channel->channelId() : 0;
if (invoker) {
cmd["invokerid"] = invoker->getClientId();
cmd["invokername"] = invoker->getDisplayName();
cmd["invokeruid"] = invoker->getUid();
}
/* TODO: Critical warn if the client hasn't been found? */
this->visibleClients.erase(std::remove_if(this->visibleClients.begin(), this->visibleClients.end(), [&, client](const weak_ptr<ConnectedClient>& weak) {
auto c = weak.lock();
if(!c) {
logError(this->getServerId(), "{} Got \"dead\" client in visible client list! This can cause a remote client disconnect within the future!", CLIENT_STR_LOG_PREFIX);
return true;
}
return c == client;
}), this->visibleClients.end());
this->sendCommand(cmd);
return true;
}
bool ConnectedClient::notifyClientLeftViewKicked(const std::shared_ptr<ConnectedClient> &client,
const std::shared_ptr<BasicChannel> &target_channel,
const std::string& message,
std::shared_ptr<ConnectedClient> invoker,
bool lock_channel_tree) {
assert(!lock_channel_tree); /* not supported yet! */
assert(client && client->getClientId() != 0);
assert(client->currentChannel || &*client == this);
/* TODO: Critical warn if the client hasn't been found? */
this->visibleClients.erase(std::remove_if(this->visibleClients.begin(), this->visibleClients.end(), [&, client](const weak_ptr<ConnectedClient>& weak) {
auto c = weak.lock();
if(!c) {
logError(this->getServerId(), "{} Got \"dead\" client in visible client list! This can cause a remote client disconnect within the future!", CLIENT_STR_LOG_PREFIX);
return true;
}
return c == client;
}), this->visibleClients.end());
if(!invoker) {
logCritical(this->getServerId(), "{} ConnectedClient::notifyClientLeftViewKicked() => missing invoker for reason id {}", CLIENT_STR_LOG_PREFIX, target_channel ? ViewReasonId::VREASON_CHANNEL_KICK : ViewReasonId::VREASON_SERVER_KICK);
if(this->server)
invoker = this->server->serverRoot;
}
Command cmd("notifyclientleftview");
cmd["clid"] = client->getClientId();
cmd["cfid"] = client->currentChannel ? client->currentChannel->channelId() : 0;
cmd["ctid"] = target_channel ? target_channel->channelId() : 0;
cmd["reasonid"] = (uint8_t) (target_channel ? ViewReasonId::VREASON_CHANNEL_KICK : ViewReasonId::VREASON_SERVER_KICK);
cmd["reasonmsg"] = message;
if (invoker) {
cmd["invokerid"] = invoker->getClientId();
cmd["invokername"] = invoker->getDisplayName();
cmd["invokeruid"] = invoker->getUid();
}
this->sendCommand(cmd);
return true;
}
bool ConnectedClient::notifyClientLeftViewBanned(
const shared_ptr<ConnectedClient> &client,
const std::string& message,
std::shared_ptr<ConnectedClient> invoker,
size_t length,
bool lock_channel_tree) {
assert(!lock_channel_tree); /* not supported yet! */
assert(client && client->getClientId() != 0);
assert(client->currentChannel || &*client == this);
Command cmd("notifyclientleftview");
cmd["clid"] = client->getClientId();
cmd["cfid"] = client->currentChannel ? client->currentChannel->channelId() : 0;
cmd["ctid"] = 0;
cmd["reasonid"] = ViewReasonId::VREASON_BAN;
cmd["reasonmsg"] = message;
if (invoker) {
cmd["invokerid"] = invoker->getClientId();
cmd["invokername"] = invoker->getDisplayName();
cmd["invokeruid"] = invoker->getUid();
}
if (length > 0) {
cmd["bantime"] = length;
}
/* TODO: Critical warn if the client hasn't been found? */
this->visibleClients.erase(std::remove_if(this->visibleClients.begin(), this->visibleClients.end(), [&, client](const weak_ptr<ConnectedClient>& weak) {
auto c = weak.lock();
if(!c) {
logError(this->getServerId(), "{} Got \"dead\" client in visible client list! This can cause a remote client disconnect within the future!", CLIENT_STR_LOG_PREFIX);
return true;
}
return c == client;
}), this->visibleClients.end());
this->sendCommand(cmd);
return true;
}
bool ConnectedClient::sendNeededPermissions(bool enforce) {
if(!enforce && this->state != ConnectionState::CONNECTED) return false;
if(!enforce && chrono::system_clock::now() - this->lastNeededNotify < chrono::seconds(5) && this->lastNeededPermissionNotifyChannel == this->currentChannel) { //Dont spam these (hang up ui)
this->requireNeededPermissionResend = true;
return true;
}
this->lastNeededNotify = chrono::system_clock::now();
this->lastNeededPermissionNotifyChannel = this->currentChannel;
this->requireNeededPermissionResend = false;
return this->notifyClientNeededPermissions();
}
bool ConnectedClient::notifyClientNeededPermissions() {
Command cmd("notifyclientneededpermissions");
int index = 0;
unique_lock cache_lock(this->client_needed_permissions_lock);
auto permissions = this->client_needed_permissions;
cache_lock.unlock();
for(const auto& [ key, value ] : permissions) {
if(!value.has_value) {
continue;
}
cmd[index]["permid"] = key;
cmd[index++]["permvalue"] = value.value;
}
if(index == 0) {
cmd[index]["permid"] = permission::i_client_talk_power;
cmd[index++]["permvalue"] = 0;
}
this->sendCommand(cmd);
return true;
}
bool ConnectedClient::notifyError(const command_result& result, const std::string& retCode) {
ts::command_builder command{"error"};
this->writeCommandResult(command, result);
if(!retCode.empty())
command.put_unchecked(0, "return_code", retCode);
this->sendCommand(command);
return true;
}
void ConnectedClient::writeCommandResult(ts::command_builder &cmd_builder, const command_result &result, const std::string& errorCodeKey) {
result.build_error_response(cmd_builder, errorCodeKey);
}
inline std::shared_ptr<ViewEntry> pop_view_entry(std::deque<std::shared_ptr<ViewEntry>>& pool, ChannelId id) {
for(auto it = pool.begin(); it != pool.end(); it++) {
if((*it)->channelId() == id) {
auto handle = *it;
pool.erase(it);
return handle;
}
}
return nullptr;
}
using ChannelIT = std::deque<std::shared_ptr<ViewEntry>>::iterator;
inline void send_channels(ConnectedClient* client, ChannelIT begin, const ChannelIT& end, bool override_orderid){
if(begin == end)
return;
ts::command_builder builder{"channellist", 512, 6};
size_t index = 0;
while(begin != end) {
auto channel = (*begin)->channel();
if(!channel) {
begin++;
continue;
}
for (const auto &elm : channel->properties()->list_properties(property::FLAG_CHANNEL_VIEW, client->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) {
if(elm.type() == property::CHANNEL_ORDER)
builder.put_unchecked(index, elm.type().name, override_orderid ? 0 : (*begin)->previous_channel);
else
builder.put_unchecked(index, elm.type().name, elm.value());
}
begin++;
if(++index > 3)
break;
}
client->sendCommand(builder);
if(begin != end)
send_channels(client, begin, end, override_orderid);
}
void ConnectedClient::sendChannelList(bool lock_channel_tree) {
shared_lock server_channel_lock(this->server->channel_tree_lock, defer_lock);
unique_lock client_channel_lock(this->channel_lock, defer_lock);
if(lock_channel_tree) {
server_channel_lock.lock();
client_channel_lock.lock();
}
auto channels = this->channels->insert_channels(this->server->channelTree->tree_head(), true, false);
if(this->currentChannel) {
bool send_success;
for(const auto& channel : this->channels->show_channel(this->server->channelTree->find_linked_entry(this->currentChannel->channelId()), send_success))
channels.push_back(channel);
if(!send_success)
logCritical(this->getServerId(), "ConnectedClient::sendChannelList => failed to insert default channel!");
}
/*
this->channels->print();
auto channels_left = channels;
for(const auto& channel : channels) {
if(channel->previous_channel == 0) continue;
bool erased = false;
bool own = true;
for(const auto& entry : channels_left) {
if(entry->channelId() == channel->channelId())
own = true;
if(entry->channelId() == channel->previous_channel) {
channels_left.erase(find(channels_left.begin(), channels_left.end(), entry));
erased = true;
break;
}
}
if(!erased || !own) {
logCritical(this->getServerId(), "Client {} would get an invalid channel order disconnect! Channel {} is not send before his channel! (Flags: erased := {} | own := {})", CLIENT_STR_LOG_PREFIX_(this), channel->previous_channel, erased, own);
}
}
*/
std::deque<std::shared_ptr<ViewEntry>> entry_channels{pop_view_entry(channels, this->currentChannel->channelId())};
while(entry_channels.front()) entry_channels.push_front(pop_view_entry(channels, entry_channels.front()->parentId()));
entry_channels.pop_front();
if(entry_channels.empty())
logCritical(this->getServerId(), "ConnectedClient::sendChannelList => invalid (empty) own channel path!");
send_channels(this, entry_channels.begin(), entry_channels.end(), false);
this->notifyClientEnterView(this->ref(), nullptr, "", this->currentChannel, ViewReasonId::VREASON_SYSTEM, nullptr, false); //Notify self after path is send
send_channels(this, channels.begin(), channels.end(), false);
//this->notifyClientEnterView(_this.lock(), nullptr, "", this->currentChannel, ViewReasonId::VREASON_SYSTEM, nullptr, false); //Notify self after path is send
this->sendCommand(Command("channellistfinished"));
}
void ConnectedClient::sendChannelDescription(const std::shared_ptr<BasicChannel>& channel, bool lock) {
shared_lock tree_lock(this->channel_lock, defer_lock);
if(lock)
tree_lock.lock();
if(!this->channels->channel_visible(channel)) return;
auto limit = this->getType() == CLIENT_TEAMSPEAK ? 8192 : 131130;
auto description = channel->properties()[property::CHANNEL_DESCRIPTION].value();
Command cmd("notifychanneledited");
cmd["cid"] = channel->channelId();
cmd["reasonid"] = 9;
cmd["channel_description"] = description.size() > limit ? description.substr(0, limit) : description;
this->sendCommand(cmd, true);
}
void ConnectedClient::tick_server(const std::chrono::system_clock::time_point &time) {
ALARM_TIMER(A1, "ConnectedClient::tick", milliseconds(2));
if(this->state == ConnectionState::CONNECTED) {
if(this->requireNeededPermissionResend)
this->sendNeededPermissions(false);
if(this->lastOnlineTimestamp.time_since_epoch().count() == 0) {
this->lastOnlineTimestamp = time;
} else if(time - this->lastOnlineTimestamp > seconds(120)) {
this->properties()[property::CLIENT_MONTH_ONLINE_TIME].increment_by<uint64_t>(duration_cast<seconds>(time - lastOnlineTimestamp).count());
this->properties()[property::CLIENT_TOTAL_ONLINE_TIME].increment_by<uint64_t>(duration_cast<seconds>(time - lastOnlineTimestamp).count());
lastOnlineTimestamp = time;
}
if(time - this->lastTransfareTimestamp > seconds(5)) {
lastTransfareTimestamp = time;
auto update = this->connectionStatistics->mark_file_bytes();
if(update.first > 0) {
this->properties()[property::CLIENT_MONTH_BYTES_DOWNLOADED].increment_by<uint64_t>(update.first);
this->properties()[property::CLIENT_TOTAL_BYTES_DOWNLOADED].increment_by<uint64_t>(update.first);
}
if(update.second > 0) {
this->properties()[property::CLIENT_MONTH_BYTES_UPLOADED].increment_by<uint64_t>(update.second);
this->properties()[property::CLIENT_TOTAL_BYTES_UPLOADED].increment_by<uint64_t>(update.second);
}
}
}
this->connectionStatistics->tick();
}
void ConnectedClient::sendServerInit() {
Command command("initserver");
for(const auto& prop : this->server->properties()->list_properties(property::FLAG_SERVER_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) {
command[std::string{prop.type().name}] = prop.value();
}
command["virtualserver_maxclients"] = 32;
//Server stuff
command["client_talk_power"] = this->properties()[property::CLIENT_TALK_POWER].value();
command["client_needed_serverquery_view_power"] = this->properties()[property::CLIENT_NEEDED_SERVERQUERY_VIEW_POWER].value();
command["client_myteamspeak_id"] = this->properties()[property::CLIENT_MYTEAMSPEAK_ID].value();
command["client_integrations"] = this->properties()[property::CLIENT_INTEGRATIONS].value();
if(ts::config::server::DefaultServerLicense == LicenseType::LICENSE_AUTOMATIC_INSTANCE) {
if(serverInstance->getVoiceServerManager()->usedSlots() <= 32)
command["lt"] = LicenseType::LICENSE_NONE;
else if(serverInstance->getVoiceServerManager()->usedSlots() <= 512)
command["lt"] = LicenseType::LICENSE_NPL;
else
command["lt"] = LicenseType::LICENSE_HOSTING;
} else if(ts::config::server::DefaultServerLicense == LicenseType::LICENSE_AUTOMATIC_SERVER){
if(this->server->properties()[property::VIRTUALSERVER_MAXCLIENTS].as_or<size_t>(0) <= 32)
command["lt"] = LicenseType::LICENSE_NONE;
else if(this->server->properties()[property::VIRTUALSERVER_MAXCLIENTS].as_or<size_t>(0) <= 512)
command["lt"] = LicenseType::LICENSE_NPL;
else
command["lt"] = LicenseType::LICENSE_HOSTING;
} else {
command["lt"] = ts::config::server::DefaultServerLicense;
}
command["pv"] = 6; //Protocol version
command["acn"] = this->getDisplayName();
command["aclid"] = this->getClientId();
this->sendCommand(command);
}
bool ConnectedClient::handleCommandFull(Command& cmd, bool disconnectOnFail) {
system_clock::time_point start, end;
start = system_clock::now();
#ifdef PKT_LOG_CMD
logTrace(this->getServerId() == 0 ? LOG_QUERY : this->getServerId(), "{}[Command][Client -> Server] Processing command: {}", CLIENT_STR_LOG_PREFIX, cmd.build(false));
#endif
command_result result;
try {
result.reset(this->handleCommand(cmd));
} catch(command_value_cast_failed& ex){
auto message = ex.key() + " at " + std::to_string(ex.index()) + " could not be casted to " + ex.target_type().name();
if(disconnectOnFail) {
this->disconnect(message);
return false;
} else {
result.reset(command_result{error::parameter_convert, message});
}
} catch(command_bulk_index_out_of_bounds_exception& ex){
auto message = "missing bulk for index " + std::to_string(ex.index());
if(disconnectOnFail) {
this->disconnect(message);
return false;
} else {
result.reset(command_result{error::parameter_invalid_count, message});
}
} catch(command_value_missing_exception& ex){
auto message = "missing value for " + ex.key() + (ex.bulk_index() == std::string::npos ? "" : " at " + std::to_string(ex.bulk_index()));
if(disconnectOnFail) {
this->disconnect(message);
return false;
} else {
result.reset(command_result{error::parameter_missing, message});
}
} catch(invalid_argument& ex){
logWarning(this->getServerId(), "{}[Command] Failed to handle command. Received invalid argument exception: {}", CLIENT_STR_LOG_PREFIX, ex.what());
if(disconnectOnFail) {
this->disconnect("Invalid argument (" + string(ex.what()) + ")");
return false;
} else {
result.reset(command_result{error::parameter_convert, ex.what()});
}
} catch (exception& ex) {
logWarning(this->getServerId(), "{}[Command] Failed to handle command. Received exception with message: {}", CLIENT_STR_LOG_PREFIX, ex.what());
if(disconnectOnFail) {
this->disconnect("Error while command handling (" + string(ex.what()) + ")!");
return false;
} else {
result.reset(command_result{error::vs_critical});
}
} catch (...) {
this->disconnect("Error while command handling! (unknown)");
return false;
}
bool generateReturnStatus = false;
if(result.has_error() || this->getType() == ClientType::CLIENT_QUERY){
generateReturnStatus = true;
} else if(cmd["return_code"].size() > 0) {
generateReturnStatus = !cmd["return_code"].string().empty();
}
if(generateReturnStatus)
this->notifyError(result, cmd["return_code"].size() > 0 ? cmd["return_code"].first().as<std::string>() : "");
if(result.has_error() && this->state == ConnectionState::INIT_HIGH) {
this->close_connection(system_clock::now()); //Disconnect now
}
for (const auto& handler : postCommandHandler)
handler();
postCommandHandler.clear();
end = system_clock::now();
if(end - start > milliseconds(10)) {
if(end - start > milliseconds(100))
logError(this->getServerId(), "Command handling of command {} needs {}ms. This could be an issue!", cmd.command(), duration_cast<milliseconds>(end - start).count());
else
logWarning(this->getServerId(), "Command handling of command {} needs {}ms.", cmd.command(), duration_cast<milliseconds>(end - start).count());
}
result.release_data();
return true;
}
std::shared_ptr<BanRecord> ConnectedClient::resolveActiveBan(const std::string& ip_address) {
if(permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_ignore_bans, 0))) return nullptr;
//Check if manager banned
auto banManager = serverInstance->banManager();
shared_ptr<BanRecord> banEntry = nullptr;
deque<shared_ptr<BanRecord>> entries;
if (!banEntry) {
banEntry = banManager->findBanByName(this->server->getServerId(), this->getDisplayName());
if(banEntry)
debugMessage(this->getServerId(), "{} Resolved name ban ({}). Record id {}, server id {}",
CLIENT_STR_LOG_PREFIX,
this->getDisplayName(),
banEntry->banId,
banEntry->serverId);
}
if (!banEntry) {
banEntry = banManager->findBanByUid(this->server->getServerId(), this->getUid());
if(banEntry)
debugMessage(this->getServerId(), "{} Resolved uuid ban ({}). Record id {}, server id {}",
CLIENT_STR_LOG_PREFIX,
this->getUid(),
banEntry->banId,
banEntry->serverId);
}
if (!banEntry && !ip_address.empty()) {
banEntry = banManager->findBanByIp(this->server->getServerId(), ip_address);
if(banEntry)
debugMessage(this->getServerId(), "{} Resolved ip ban ({}). Record id {}, server id {}",
CLIENT_STR_LOG_PREFIX,
ip_address,
banEntry->banId,
banEntry->serverId);
}
auto vclient = dynamic_cast<VoiceClient*>(this);
if(vclient)
if (!banEntry) {
banEntry = banManager->findBanByHwid(this->server->getServerId(), vclient->getHardwareId());
if(banEntry)
debugMessage(this->getServerId(), "{} Resolved hwid ban ({}). Record id {}, server id {}",
CLIENT_STR_LOG_PREFIX,
vclient->getHardwareId(),
banEntry->banId,
banEntry->serverId);
}
return banEntry;
}
bool ConnectedClient::update_client_needed_permissions() {
if(this->getType() == ClientType::CLIENT_QUERY) {
/* Query clients are not interested in their permissions */
return true;
}
/* The server and/or the channel might change while we're executing this method */
auto currentChannel = this->currentChannel;
ClientPermissionCalculator permission_helper{this, currentChannel};
auto values = permission_helper.calculate_permissions(permission::neededPermissions);
auto updated = false;
{
lock_guard cached_lock(this->client_needed_permissions_lock);
auto old_cached_permissions{this->client_needed_permissions};
this->client_needed_permissions = values;
std::sort(this->client_needed_permissions.begin(), this->client_needed_permissions.end(), [](const auto& a, const auto& b) { return a.first < b.first; });
if(this->client_needed_permissions.size() != old_cached_permissions.size())
updated = true;
else {
for(auto oit = old_cached_permissions.begin(), nit = this->client_needed_permissions.begin(); oit != old_cached_permissions.end(); oit++, nit++) {
if(oit->first != nit->first || oit->second != nit->second) {
updated = true;
break;
}
}
}
}
this->cpmerission_whisper_power = {0, false};
this->cpmerission_needed_whisper_power = {0, false};
for(const auto& entry : values) {
if(entry.first == permission::i_client_whisper_power)
this->cpmerission_whisper_power = entry.second;
else if(entry.first == permission::i_client_needed_whisper_power)
this->cpmerission_needed_whisper_power = entry.second;
}
return updated;
}
void ConnectedClient::update_displayed_client_groups(bool& server_groups_changed, bool& channel_group_changed) {
auto ref_server = this->server;
auto group_manager = ref_server ? ref_server->group_manager() : serverInstance->group_manager();
GroupId channel_group_id{0};
ChannelId channel_inherit_id{0};
std::string server_group_assignments{};
{
auto server_groups = group_manager->assignments().server_groups_of_client(groups::GroupAssignmentCalculateMode::GLOBAL, this->getClientDatabaseId());
for(const auto& group_id : server_groups) {
server_group_assignments += ",";
server_group_assignments += std::to_string(group_id);
}
if(!server_group_assignments.empty()) {
server_group_assignments = server_group_assignments.substr(1);
}
std::unique_lock view_lock{this->channel_lock};
this->cached_server_groups = server_groups;
}
{
std::shared_ptr<BasicChannel> inherited_channel{this->currentChannel};
auto channel_group = group_manager->assignments().calculate_channel_group_of_client(groups::GroupAssignmentCalculateMode::GLOBAL, this->getClientDatabaseId(), inherited_channel);
if(channel_group.has_value()) {
assert(inherited_channel);
channel_group_id = *channel_group;
channel_inherit_id = inherited_channel->channelId();
} else if(ref_server) {
channel_group_id = ref_server->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP].as_or<GroupId>(0);
channel_inherit_id = 0;
} else {
channel_group_id = 0;
channel_inherit_id = 0;
}
this->cached_channel_group = channel_group_id;
}
server_groups_changed = false;
channel_group_changed = false;
std::deque<property::ClientProperties> updated_properties{};
if(this->properties()[property::CLIENT_SERVERGROUPS].update_value(server_group_assignments)) {
updated_properties.push_back(property::CLIENT_SERVERGROUPS);
server_groups_changed = true;
}
if(this->properties()[property::CLIENT_CHANNEL_GROUP_ID].update_value(channel_group_id)) {
updated_properties.push_back(property::CLIENT_CHANNEL_GROUP_ID);
channel_group_changed = true;
}
if(this->properties()[property::CLIENT_CHANNEL_GROUP_INHERITED_CHANNEL_ID].update_value(channel_inherit_id)) {
updated_properties.push_back(property::CLIENT_CHANNEL_GROUP_ID);
channel_group_changed = true;
}
if(!updated_properties.empty() && ref_server) {
ref_server->notifyClientPropertyUpdates(this->ref(), updated_properties);
}
}
void ConnectedClient::sendTSPermEditorWarning() {
if(config::voice::warn_on_permission_editor) {
if(system_clock::now() - this->command_times.servergrouplist > milliseconds(1000)) return;
if(system_clock::now() - this->command_times.channelgrouplist > milliseconds(1000)) return;
this->command_times.last_notify = system_clock::now();
this->notifyClientPoke(this->server->serverRoot, config::messages::teamspeak_permission_editor);
}
}
#define RESULT(perm_) \
do { \
ventry->join_state_id = this->join_state_id; \
ventry->join_permission_error = (perm_); \
return perm_; \
} while(0)
permission::PermissionType ConnectedClient::calculate_and_get_join_state(const std::shared_ptr<BasicChannel>& channel) {
std::shared_ptr<ViewEntry> ventry;
{
shared_lock view_lock(this->channel_lock);
ventry = this->channel_view()->find_channel(channel);
if(!ventry) {
return permission::i_channel_view_power;
}
}
if(ventry->join_state_id == this->join_state_id) {
return ventry->join_permission_error;
}
auto channel_id = channel->channelId();
auto permission_cache = make_shared<CalculateCache>();
switch(channel->channelType()) {
case ChannelType::permanent:
if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_join_permanent, channel_id))) {
RESULT(permission::b_channel_join_permanent);
}
break;
case ChannelType::semipermanent:
if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_join_semi_permanent, channel_id))) {
RESULT(permission::b_channel_join_semi_permanent);
}
break;
case ChannelType::temporary:
if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_join_temporary, channel_id))) {
RESULT(permission::b_channel_join_temporary);
}
break;
}
if(!channel->permission_granted(permission::i_channel_needed_join_power, this->calculate_permission(permission::i_channel_join_power, channel_id), false)) {
if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_ignore_join_power, channel_id))) {
RESULT(permission::i_channel_join_power);
}
}
if(permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_is_sticky, this->currentChannel ? this->currentChannel->channelId() : 0))) {
if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_ignore_sticky, channel_id))) {
RESULT(permission::b_client_is_sticky);
}
}
RESULT(permission::ok);
}
void ConnectedClient::useToken(token::TokenId token_id) {
using groups::GroupCalculateMode;
using groups::GroupAssignmentResult;
auto server_ref = this->server;
if(!server_ref) {
return;
}
std::deque<token::TokenAction> actions{};
if(!server_ref->getTokenManager().query_token_actions(token_id, actions)) {
return;
}
if(actions.empty()) {
return;
}
bool tree_registered = !!this->currentChannel;
bool server_groups_changed{false}, channel_group_changed{false};
std::deque<std::shared_ptr<groups::ServerGroup>> added_server_groups{};
std::deque<std::shared_ptr<groups::ServerGroup>> removed_server_groups{};
for(const auto& action : actions) {
switch(action.type) {
case token::ActionType::AddServerGroup:
case token::ActionType::RemoveServerGroup: {
auto group = server->group_manager()->server_groups()->find_group(GroupCalculateMode::GLOBAL, action.id1);
if(!group) {
debugMessage(this->getServerId(), "{} Skipping token action add/remove server group for group {} because the group does not exists anymore.", CLIENT_STR_LOG_PREFIX, action.id1);
break;
}
if(action.type == token::ActionType::AddServerGroup) {
auto result = this->server->group_manager()->assignments().add_server_group(this->getClientDatabaseId(), group->group_id(), !group->is_permanent());
switch(result) {
case GroupAssignmentResult::SUCCESS:
debugMessage(this->getServerId(), "{} Executing token action add server group for group {}.", CLIENT_STR_LOG_PREFIX, action.id1);
added_server_groups.push_back(group);
server_groups_changed = true;
break;
case GroupAssignmentResult::ADD_ALREADY_MEMBER_OF_GROUP:
debugMessage(this->getServerId(), "{} Skipping token action add server group for group {} because client is already member of that group.", CLIENT_STR_LOG_PREFIX, action.id1);
break;
case GroupAssignmentResult::REMOVE_NOT_MEMBER_OF_GROUP:
case GroupAssignmentResult::SET_ALREADY_MEMBER_OF_GROUP:
default:
assert(false);
break;
}
} else {
auto result = this->server->group_manager()->assignments().remove_server_group(this->getClientDatabaseId(), group->group_id());
switch(result) {
case GroupAssignmentResult::SUCCESS:
debugMessage(this->getServerId(), "{} Executing token action remove server group for group {}.", CLIENT_STR_LOG_PREFIX, action.id1);
removed_server_groups.push_back(group);
server_groups_changed = true;
break;
case GroupAssignmentResult::REMOVE_NOT_MEMBER_OF_GROUP:
debugMessage(this->getServerId(), "{} Skipping token action remove server group for group {} because client is not a member of that group.", CLIENT_STR_LOG_PREFIX, action.id1);
break;
case GroupAssignmentResult::ADD_ALREADY_MEMBER_OF_GROUP:
case GroupAssignmentResult::SET_ALREADY_MEMBER_OF_GROUP:
default:
assert(false);
break;
}
}
break;
}
case token::ActionType::SetChannelGroup: {
auto group = server->group_manager()->channel_groups()->find_group(GroupCalculateMode::GLOBAL, action.id1);
if(!group) {
debugMessage(this->getServerId(), "{} Skipping token action set channel group for group {} at channel {} because the group does not exists anymore.", CLIENT_STR_LOG_PREFIX, action.id1, action.id2);
break;
}
auto channel = this->server->channelTree->findChannel(action.id2);
if (!channel) {
debugMessage(this->getServerId(), "{} Skipping token action set channel group for group {} at channel {} because the channel does not exists anymore.", CLIENT_STR_LOG_PREFIX, action.id1, action.id2);
break;
}
channel_group_changed = true;
this->server->group_manager()->assignments().set_channel_group(this->getClientDatabaseId(), group->group_id(), channel->channelId(), !group->is_permanent());
break;
}
case token::ActionType::AllowChannelJoin: {
auto speaking_client = dynamic_cast<SpeakingClient*>(this);
if(speaking_client) {
speaking_client->join_whitelisted_channel.emplace_back(action.id2, action.text);
}
break;
}
case token::ActionType::ActionSqlFailed:
case token::ActionType::ActionDeleted:
case token::ActionType::ActionIgnore:
default:
break;
}
}
if(this->state > ConnectionState::INIT_HIGH) {
this->task_update_channel_client_properties.enqueue();
this->task_update_needed_permissions.enqueue();
}
if(tree_registered && (server_groups_changed || channel_group_changed)) {
this->task_update_displayed_groups.enqueue();
}
if(tree_registered && server_groups_changed) {
for(const auto& group : added_server_groups) {
std::optional<ts::command_builder> notify{};
for(const auto &viewer : this->server->getClients()) {
viewer->notifyServerGroupClientAdd(notify, this->server->serverRoot, this->ref(), group->group_id());
}
}
for(const auto& group : added_server_groups) {
std::optional<ts::command_builder> notify{};
for(const auto &viewer : this->server->getClients()) {
viewer->notifyServerGroupClientRemove(notify, this->server->serverRoot, this->ref(), group->group_id());
}
}
}
}