989 lines
42 KiB
C++
989 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);
|
|
}
|
|
|
|
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, 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& value : permissions) {
|
|
cmd[index]["permid"] = value.first;
|
|
cmd[index++]["permvalue"] = value.second.has_value ? value.second.value : 0;
|
|
}
|
|
|
|
if(index == 0) {
|
|
cmd[index]["permid"] = permission::i_client_talk_power;
|
|
cmd[index++]["permvalue"] = 0;
|
|
}
|
|
|
|
this->sendCommand(cmd);
|
|
return true;
|
|
}
|
|
|
|
inline void write_command_result_error(ts::command_builder_bulk bulk, const command_result& result, const std::string& errorCodeKey) {
|
|
bulk.put_unchecked(errorCodeKey, (uint32_t) result.error_code());
|
|
bulk.put_unchecked("msg", findError(result.error_code()).message);
|
|
if(result.is_permission_error())
|
|
bulk.put_unchecked("failed_permid", (uint32_t) result.permission_id());
|
|
}
|
|
|
|
inline void write_command_result_detailed(ts::command_builder_bulk bulk, const command_result& result, const std::string& errorCodeKey) {
|
|
auto details = result.details();
|
|
bulk.put_unchecked(errorCodeKey, (uint32_t) details->error_id);
|
|
bulk.put_unchecked("msg", findError(details->error_id).message);
|
|
|
|
for(const auto& extra : details->extra_properties)
|
|
bulk.put(extra.first, extra.second);
|
|
}
|
|
|
|
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(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(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);
|
|
} |