#include #include #include #include #include #include #include #include #include #include "../TSServer.h" #include "voice/VoiceClient.h" #include "../server/VoiceServer.h" #include "../server/file/FileServer.h" #include "../InstanceHandler.h" #include "ConnectedClient.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&server) : DataClient(db, server) { memtrack::allocated(this); memset(&this->remote_address, 0, sizeof(this->remote_address)); connectionStatistics = make_shared(server ? server->getServerStatistics() : nullptr, false); this->connectionStatistics->measure_bandwidths(false); /* done by the client and we trust this */ channels = make_shared(this); } ConnectedClient::~ConnectedClient() { memtrack::freed(this); } std::shared_ptr ConnectedClient::request_connection_info(const std::shared_ptr &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& 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 server_ref = this->server; auto permissions = this->permissionValues(permission::PERMTEST_ORDERED, { permission::i_client_talk_power, permission::b_client_is_priority_speaker, permission::b_client_ignore_antiflood, permission::i_channel_view_power, permission::b_channel_ignore_view_power }, this->currentChannel); permission::PermissionValue permission_talk_power = permNotGranted, permission_priority_speaker = permNotGranted, permission_ignore_antiflood = permNotGranted, permission_channel_view_power = permNotGranted, permission_channel_ignore_view_power = permNotGranted; 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_is_priority_speaker) permission_priority_speaker = 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); } if(permission_talk_power < -2) permission_talk_power = -2; else if(permission_talk_power == -2) permission_talk_power = 0; deque 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()); if(permission_talk_power != this->properties()[property::CLIENT_TALK_POWER].as()) { //We do not have to update tp if there's no channel this->properties()[property::CLIENT_TALK_POWER] = permission_talk_power; notifyList.emplace_back(property::CLIENT_TALK_POWER); auto update = this->properties()[property::CLIENT_IS_TALKER].as() || this->properties()[property::CLIENT_TALK_REQUEST].as() > 0; if(update && this->currentChannel) { if(this->currentChannel->talk_power_granted({permission_talk_power, permission_talk_power != permNotGranted})) { 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()) + " to " + to_string(iconId)); if(this->properties()[property::CLIENT_ICON_ID].as() != iconId){ 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; } } if(this->properties()[property::CLIENT_ICON_ID].as() != iconId) { this->properties()[property::CLIENT_ICON_ID] = (IconId) iconId; notifyList.emplace_back(property::CLIENT_ICON_ID); } } auto pSpeaker = permission_priority_speaker > 0; if(properties()[property::CLIENT_IS_PRIORITY_SPEAKER].as() != pSpeaker){ properties()[property::CLIENT_IS_PRIORITY_SPEAKER] = pSpeaker; notifyList.emplace_back(property::CLIENT_IS_PRIORITY_SPEAKER); } block_flood = permission_ignore_antiflood <= 0 || permission_ignore_antiflood == permNotGranted; 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(); } deque 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::PermissionValue talk_power) { bool flag = false; flag |= this->properties()[property::CLIENT_IS_TALKER].as(); if(!flag && this->currentChannel) { flag = this->currentChannel->talk_power_granted({talk_power, talk_power != permNotGranted}); } 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(); } std::deque> ConnectedClient::subscribeChannel(const std::deque>& targets, bool lock_channel, bool enforce) { deque> subscribed_channels; auto ref_server = this->server; if(!ref_server) return {}; auto general_granted = enforce || this->permission_granted(this->permissionValue(permission::b_channel_ignore_subscribe_power, nullptr), 1, true); { 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(); 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_value(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_value(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> 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> ConnectedClient::unsubscribeChannel(const std::deque>& targets, bool lock_channel) { auto ref_server = this->server; if(!ref_server) return {}; deque > 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& 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& client, bool lock) { for(const auto& entry : this->getVisibleClients(lock)) if(entry.lock() == client) return true; return false; } bool ConnectedClient::notifyClientLeftView(const std::deque> &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& 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 &client, const std::shared_ptr &target_channel, ViewReasonId reason_id, const std::string& reason_message, std::shared_ptr invoker, bool lock_channel_tree) { assert(!lock_channel_tree); /* not supported yet! */ assert(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 = ""; } 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& 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 &client, const std::shared_ptr &target_channel, const std::string& message, std::shared_ptr 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& 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 &client, const std::string& message, std::shared_ptr 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& 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) { if(value.second != permNotGranted || value.first == permission::b_client_force_push_to_talk) { cmd[index]["permid"] = value.first; cmd[index++]["permvalue"] = value.second == permNotGranted ? 0 : value.second; } } if(permissions.empty()) { cmd[0]["permid"] = permission::b_client_force_push_to_talk; cmd[0]["permvalue"] = false; } this->sendCommand(cmd); return true; } bool ConnectedClient::notifyError(const CommandResult& result, const std::string& retCode) { Command cmd("error"); cmd["id"] = result.error.errorId; cmd["msg"] = result.error.message; if(retCode.length() > 0) cmd["return_code"] = retCode; for(const auto& extra : result.extraProperties) cmd[extra.first] = extra.second; this->sendCommand(cmd); return true; } inline std::shared_ptr pop_view_entry(std::deque>& 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>::iterator; inline void send_channels(ConnectedClient* client, ChannelIT begin, const ChannelIT& end, bool override_orderid){ if(begin == end) return; Command channellist("channellist"); size_t index = 0; while(begin != end) { auto channel = (*begin)->channel(); if(!channel) 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) channellist[index][elm.type().name] = override_orderid ? 0 : (*begin)->previous_channel; else channellist[index][elm.type().name] = elm.as(); } begin++; if(++index > 6) break; } if(dynamic_cast(client)) { auto vc = dynamic_cast(client); vc->sendCommand0(channellist, false, true); /* we need to process this command directly so it will be processed before the channellistfinished stuff */ } else { client->sendCommand(channellist); } 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> 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(), true); 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->sendCommand(Command("channellistfinished")); } void ConnectedClient::sendChannelDescription(const std::shared_ptr& channel, bool lock) { shared_lock tree_lock(this->channel_lock, defer_lock); if(lock) tree_lock.lock(); if(!this->channels->channel_visible(channel)) return; Command cmd("notifychanneledited"); cmd["cid"] = channel->channelId(); cmd["reasonid"] = 9; cmd["channel_description"] = channel->properties()[property::CHANNEL_DESCRIPTION].as(); 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(time - lastOnlineTimestamp).count(); this->properties()[property::CLIENT_TOTAL_ONLINE_TIME] += duration_cast(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; } } } if(this->last_statistics_tick + seconds(5) < time) { this->last_statistics_tick = time; 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[prop.type().name] = prop.value(); } command["virtualserver_maxclients"] = 32; //Server stuff command["client_talk_power"] = this->properties()[property::CLIENT_TALK_POWER].as(); command["client_needed_serverquery_view_power"] = this->properties()[property::CLIENT_NEEDED_SERVERQUERY_VIEW_POWER].as(); command["client_myteamspeak_id"] = this->properties()[property::CLIENT_MYTEAMSPEAK_ID].as(); command["client_integrations"] = this->properties()[property::CLIENT_INTEGRATIONS].as(); 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() <= 32) command["lt"] = LicenseType::LICENSE_NONE; else if(this->server->properties()[property::VIRTUALSERVER_MAXCLIENTS].as() <= 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(); if(dynamic_cast(this)) { dynamic_cast(this)->sendCommand0(command, false, true); /* process it directly so the order for the channellist entries is ensured. (First serverinit then everything else) */ } else { 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 CommandResult result; try { result = this->handleCommand(cmd); } catch(invalid_argument& ex){ debugMessage(this->getServerId(), "{}[Command] Execution throws invalid_argument exception ({}).", CLIENT_STR_LOG_PREFIX, ex.what()); if(disconnectOnFail) { this->disconnect("Invalid argument (" + string(ex.what()) + ")"); return false; } else { result = {findError("parameter_convert"), "Invalid argument (" + string(ex.what()) + ")"}; } } catch (exception& ex) { if(disconnectOnFail) { this->disconnect("Error while command handling (" + string(ex.what()) + ")!"); return false; } else { result = {findError("vs_critical"), "error while command handling (" + string(ex.what()) + ")"}; } } catch (...) { this->disconnect("Error while command handling! (unknown)"); return false; } bool generateReturnStatus = false; if(result.type() == PERM_ERROR){ generateReturnStatus = true; } else if(cmd["return_code"].size() > 0) { generateReturnStatus = !cmd["return_code"].string().empty(); } if(this->getType() == ClientType::CLIENT_QUERY) generateReturnStatus = true; if (!result) { generateReturnStatus = true; stringstream ss; ss << "{"; for(auto it = result.extraProperties.begin(); it != result.extraProperties.end();){ ss << it->first << " = " << it->second; if(++it != result.extraProperties.end()) ss << ", "; } ss << "}" << endl; logTrace(this->getServerId(), "{}[Command] Command {} with return code {} fails and returns error code {:#06x}. Properties: {}", CLIENT_STR_LOG_PREFIX, cmd.command(), cmd["return_code"].size() ? "\"" + cmd["return_code"].first().as() + "\"" : "", result.error.errorId, ss.str()); } if(generateReturnStatus) this->notifyError(result, cmd["return_code"].size() > 0 ? cmd["return_code"].first().as() : ""); if(!result && this->state == ConnectionState::INIT_HIGH) { this->closeConnection(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(end - start).count()); else logWarning(this->getServerId(), "Command handling of command {} needs {}ms.", cmd.command(), duration_cast(end - start).count()); } return true; } std::shared_ptr ConnectedClient::resolveActiveBan(const std::string& ip_address) { if(this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_ignore_bans, 1)) return nullptr; //Check if manager banned auto banManager = serverInstance->banManager(); shared_ptr banEntry = nullptr; deque> 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(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->permissionValues(permission::PERMTEST_ORDERED, permission::neededPermissions, shared_ptr(this->currentChannel)); /* copy the channel here so it does not change */ auto updated = false; { lock_guard cached_lock(this->cached_permissions_lock); vector old_permissions; old_permissions.reserve(this->cached_permissions.size()); for(const auto& value : this->cached_permissions) old_permissions.push_back(value.first); for(const auto& value : values) { auto value_it = cached_permissions.find(value.first); if(value_it == cached_permissions.end()) { /* new entry */ updated = true; this->cached_permissions[value.first] = value.second; continue; /* no need to remove that from old_permissions because it isn't there */ } else if(value_it->second != value.second) { /* entry changed */ updated = true; value_it->second = value.second; } { /* we've updated the value or verified it */ auto old_it = find(old_permissions.begin(), old_permissions.end(), value.first); if(old_it != old_permissions.end()) old_permissions.erase(old_it); } } for(const auto& left : old_permissions) { auto value_it = cached_permissions.find(left); if(value_it != cached_permissions.end()) { cached_permissions.erase(value_it); updated = true; } } } 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); } } permission::v2::PermissionFlaggedValue ConnectedClient::calculate_permission_value(const ts::permission::PermissionType &permission, ts::ChannelId channel_id) { if(channel_id == (this->currentChannel ? this->currentChannel->channelId() : 0) || channel_id == -1) { std::lock_guard lock(this->cached_permissions_lock); auto index = this->cached_permissions.find(permission); if(index != this->cached_permissions.end()) return {index->second, index->second != permNotGranted}; } auto ref_server = this->server; if(ref_server) { auto result = this->server->calculatePermissions2(this->getClientDatabaseId(), {permission}, this->getType(), channel_id, false); if(!result.empty()) /* it should never be empty! */ return result.back().second; } auto value = this->permissionValue(permission::PERMTEST_ORDERED, permission, nullptr); return {value, value != permNotGranted}; } #define RESULT(flag) \ do { \ ventry->join_state_id = this->join_state_id; \ ventry->joinable = (flag); \ return flag; \ } while(0) bool ConnectedClient::calculate_and_get_join_state(const std::shared_ptr& channel) { shared_ptr ventry; { shared_lock view_lock(this->channel_lock); ventry = this->channel_view()->find_channel(channel); if(!ventry) return false; } if(ventry->join_state_id == this->join_state_id) return ventry->joinable; auto permission_cache = make_shared(); switch(channel->channelType()) { case ChannelType::permanent: if(!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_channel_join_permanent, 1, channel, true, permission_cache)) RESULT(false); break; case ChannelType::semipermanent: if(!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_channel_join_semi_permanent, 1, channel, true, permission_cache)) RESULT(false); break; case ChannelType::temporary: if(!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_channel_join_temporary, 1, channel, true, permission_cache)) RESULT(false); break; } if(!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_channel_ignore_join_power, 1, channel, true, permission_cache)) { auto result = this->server->calculatePermissions2(this->getClientDatabaseId(), {permission::i_channel_join_power}, this->getType(), channel->channelId(), false, permission_cache); if(result.empty()) RESULT(false); if(!channel->permission_granted(permission::i_channel_needed_join_power, result.back().second, false)) RESULT(false); } auto val = this->permissionValue(permission::PERMTEST_ORDERED, permission::b_client_is_sticky, this->currentChannel, permission_cache); if (val != permNotGranted && val > 0) { auto st = this->permissionValue(permission::PERMTEST_ORDERED, permission::b_client_ignore_sticky, this->currentChannel, permission_cache); if (st != 1) RESULT(false); } RESULT(true); }