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

1015 lines
42 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 "../server/VoiceServer.h"
#include "../InstanceHandler.h"
#include "ConnectedClient.h"
#include <event.h>
using namespace std;
using namespace std::chrono;
using namespace ts;
using namespace ts::server;
using namespace ts::token;
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);
}
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;
}
//Attention the client should be only read only locked!
void ConnectedClient::updateChannelClientProperties(bool lock_channel_tree, bool notify_self) {
/* this->server may be null! */
shared_ptr<VirtualServer> server_ref = this->server;
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
}, this->currentChannel ? this->currentChannel->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);
}
deque<property::ClientProperties> notifyList;
debugMessage(this->getServerId(), "{} Got a channel talk power of {} Talk power set is {}", CLIENT_STR_LOG_PREFIX, permission_talk_power.has_value ? permission_talk_power.value : 0, this->properties()[property::CLIENT_TALK_POWER].as<uint64_t>());
if((permission_talk_power.has_value ? permission_talk_power.value : 0) != this->properties()[property::CLIENT_TALK_POWER].as<uint64_t>()) { //We do not have to update tp if there's no channel
this->properties()[property::CLIENT_TALK_POWER] = (permission_talk_power.has_value ? permission_talk_power.value : 0);
notifyList.emplace_back(property::CLIENT_TALK_POWER);
auto update = this->properties()[property::CLIENT_IS_TALKER].as<bool>() || this->properties()[property::CLIENT_TALK_REQUEST].as<int64_t>() > 0;
if(update && this->currentChannel) {
if(this->currentChannel->talk_power_granted(permission_talk_power)) {
this->properties()[property::CLIENT_IS_TALKER] = 0;
this->properties()[property::CLIENT_TALK_REQUEST] = 0;
this->properties()[property::CLIENT_TALK_REQUEST_MSG] = "";
notifyList.emplace_back(property::CLIENT_IS_TALKER);
notifyList.emplace_back(property::CLIENT_TALK_REQUEST);
notifyList.emplace_back(property::CLIENT_TALK_REQUEST_MSG);
}
}
}
IconId iconId = 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)
iconId = value.value;
}
logTrace(this->getServerId(), "[CLIENT] Updating client icon from " + to_string(this->properties()[property::CLIENT_ICON_ID].as<IconId>()) + " to " + to_string(iconId));
if(this->properties()[property::CLIENT_ICON_ID].as<IconId>() != iconId) {
#if 0
if(server_ref && iconId != 0) {
auto dir = serverInstance->getFileServer()->iconDirectory(server_ref);
if(!serverInstance->getFileServer()->iconExists(server_ref, iconId)) {
logMessage(this->getServerId(), "[FILE] Missing client icon (" + to_string(iconId) + ").");
iconId = 0;
}
}
#endif
if(this->properties()[property::CLIENT_ICON_ID].as<IconId>() != iconId) {
this->properties()[property::CLIENT_ICON_ID] = (IconId) iconId;
notifyList.emplace_back(property::CLIENT_ICON_ID);
}
}
auto pSpeaker = this->clientPermissions ? this->clientPermissions->channel_permission(permission::b_client_is_priority_speaker, this->getChannelId()) : permission::v2::empty_channel_permission;
auto pSpeakerGranted = permission::v2::permission_granted(1, {pSpeaker.values.value, pSpeaker.flags.value_set});
if(properties()[property::CLIENT_IS_PRIORITY_SPEAKER].as<bool>() != pSpeakerGranted){
properties()[property::CLIENT_IS_PRIORITY_SPEAKER] = pSpeakerGranted;
notifyList.emplace_back(property::CLIENT_IS_PRIORITY_SPEAKER);
}
block_flood = !permission::v2::permission_granted(1, permission_ignore_antiflood);
if(server_ref)
server_ref->notifyClientPropertyUpdates(_this.lock(), notifyList, 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 && this->currentChannel && 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(this->currentChannel) {
deque<ChannelId> deleted;
for(const auto& update_entry : this->channels->update_channel_path(server_ref->channelTree->tree_head(), server_ref->channelTree->find_linked_entry(this->currentChannel->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<bool>();
if(!flag && this->currentChannel) {
flag = this->currentChannel->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<uint16_t>();
}
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;
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->cached_permissions_lock);
auto permissions = this->cached_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.as<string>());
}
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.lock(), 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].as<std::string>();
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] += duration_cast<seconds>(time - lastOnlineTimestamp).count();
this->properties()[property::CLIENT_TOTAL_ONLINE_TIME] += 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] += update.first;
this->properties()[property::CLIENT_TOTAL_BYTES_DOWNLOADED] += update.first;
}
if(update.second > 0) {
this->properties()[property::CLIENT_MONTH_BYTES_UPLOADED] += update.second;
this->properties()[property::CLIENT_TOTAL_BYTES_UPLOADED] += 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].as<string>();
command["client_needed_serverquery_view_power"] = this->properties()[property::CLIENT_NEEDED_SERVERQUERY_VIEW_POWER].as<string>();
command["client_myteamspeak_id"] = this->properties()[property::CLIENT_MYTEAMSPEAK_ID].as<string>();
command["client_integrations"] = this->properties()[property::CLIENT_INTEGRATIONS].as<string>();
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<size_t>() <= 32)
command["lt"] = LicenseType::LICENSE_NONE;
else if(this->server->properties()[property::VIRTUALSERVER_MAXCLIENTS].as<size_t>() <= 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_cached_permissions() {
auto values = this->calculate_permissions(permission::neededPermissions, this->currentChannel? this->currentChannel->channelId() : 0); /* copy the channel here so it does not change */
auto updated = false;
{
lock_guard cached_lock(this->cached_permissions_lock);
auto old_cached_permissions{this->cached_permissions};
this->cached_permissions = values;
std::sort(this->cached_permissions.begin(), this->cached_permissions.end(), [](const auto& a, const auto& b) { return a.first < b.first; });
if(this->cached_permissions.size() != old_cached_permissions.size())
updated = true;
else {
for(auto oit = old_cached_permissions.begin(), nit = this->cached_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::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) {
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);
}