#include #include #include #include "ConnectedClient.h" #include "voice/VoiceClient.h" #include "../server/file/FileServer.h" #include "../server/VoiceServer.h" #include "../InstanceHandler.h" #include "../server/QueryServer.h" #include "music/MusicClient.h" #include #include #include using namespace std::chrono; using namespace std; using namespace ts; using namespace ts::server; using namespace ts::token; extern ts::server::InstanceHandler* serverInstance; #define INVOKER(command, invoker) \ do { \ if(invoker) { \ command["invokerid"] = invoker->getClientId(); \ command["invokername"] = invoker->getDisplayName(); \ command["invokeruid"] = invoker->getUid(); \ } else { \ command["invokerid"] = 0; \ command["invokername"] = "undefined"; \ command["invokeruid"] = "undefined"; \ } \ } while(0) bool ConnectedClient::notifyServerGroupList() { Command cmd(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyservergrouplist" : ""); int index = 0; for (const auto& group : (this->server ? this->server->groups : serverInstance->getGroupManager().get())->availableServerGroups(true)) { if(group->target() == GroupTarget::GROUPTARGET_CHANNEL) { cmd[index]["cgid"] = group->groupId(); } else { cmd[index]["sgid"] = group->groupId(); } for (const auto &prop : group->properties().list_properties(property::FLAG_GROUP_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) cmd[index][prop.type().name] = prop.value(); auto modify_power = group->permissions()->permission_value_flagged(permission::i_displayed_group_needed_modify_power); auto add_power = group->permissions()->permission_value_flagged(permission::i_displayed_group_needed_member_add_power); auto remove_power = group->permissions()->permission_value_flagged(permission::i_displayed_group_needed_member_remove_power); cmd[index]["n_modifyp"] = modify_power.has_value ? modify_power.value : 0; cmd[index]["n_member_addp"] = add_power.has_value ? add_power.value : 0; cmd[index]["n_member_removep"] = remove_power.has_value ? remove_power.value : 0; index++; } this->sendCommand(cmd); return true; } bool ConnectedClient::notifyGroupPermList(const std::shared_ptr& group, bool as_sid) { Command cmd(this->getExternalType() == CLIENT_TEAMSPEAK ? group->target() == GROUPTARGET_SERVER ? "notifyservergrouppermlist" : "notifychannelgrouppermlist" : ""); if (group->target() == GROUPTARGET_SERVER) cmd["sgid"] = group->groupId(); else cmd["cgid"] = group->groupId(); int index = 0; auto permissions = group->permissions()->permissions(); for (const auto &permission_data : permissions) { auto& permission = get<1>(permission_data); if(!permission.flags.value_set) continue; auto type = permission::resolvePermissionData(get<0>(permission_data)); if(as_sid) { cmd[index]["permsid"] = type->name; } else { cmd[index]["permid"] = (uint16_t) type->type; } cmd[index]["permvalue"] = permission.values.value; cmd[index]["permnegated"] = permission.flags.negate; cmd[index]["permskip"] = permission.flags.skip; index++; } for (const auto &permission_data : permissions) { auto& permission = get<1>(permission_data); if(!permission.flags.grant_set) continue; auto type = permission::resolvePermissionData(get<0>(permission_data)); if(as_sid) { cmd[index]["permsid"] = type->grant_name; } else { cmd[index]["permid"] = (uint16_t) (type->type | PERM_ID_GRANT); } cmd[index]["permvalue"] = permission.values.grant; cmd[index]["permnegated"] = permission.flags.negate; cmd[index]["permskip"] = permission.flags.skip; index++; } if (index == 0) return false; this->sendCommand(cmd); return true; } bool ConnectedClient::notifyClientPermList(ClientDbId cldbid, const std::shared_ptr& mgr, bool perm_sids) { Command res(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyclientpermlist" : ""); auto permissions = mgr->permissions(); if(permissions.empty()) return false; int index = 0; res[index]["cldbid"] = cldbid; for (const auto &permission_data : permissions) { auto& permission = std::get<1>(permission_data); if(permission.flags.value_set) { if (perm_sids) res[index]["permsid"] = permission::resolvePermissionData(get<0>(permission_data))->name; else res[index]["permid"] = get<0>(permission_data); res[index]["permvalue"] = permission.values.value; res[index]["permnegated"] = permission.flags.negate; res[index]["permskip"] = permission.flags.skip; index++; } if(permission.flags.grant_set) { if (perm_sids) res[index]["permsid"] = permission::resolvePermissionData(get<0>(permission_data))->grant_name; else res[index]["permid"] = (get<0>(permission_data) | PERM_ID_GRANT); res[index]["permvalue"] = permission.values.grant; res[index]["permnegated"] = 0; res[index]["permskip"] = 0; index++; } } this->sendCommand(res); return true; } bool ConnectedClient::notifyChannelGroupList() { Command cmd(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifychannelgrouplist" : ""); int index = 0; for (const auto &group : (this->server ? this->server->groups : serverInstance->getGroupManager().get())->availableChannelGroups(false)) { if(group->target() == GroupTarget::GROUPTARGET_CHANNEL) { cmd[index]["cgid"] = group->groupId(); } else { cmd[index]["sgid"] = group->groupId(); } for (auto &prop : group->properties().list_properties(property::FLAG_GROUP_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) cmd[index][prop.type().name] = prop.value(); auto modify_power = group->permissions()->permission_value_flagged(permission::i_displayed_group_needed_modify_power); auto add_power = group->permissions()->permission_value_flagged(permission::i_displayed_group_needed_member_add_power); auto remove_power = group->permissions()->permission_value_flagged(permission::i_displayed_group_needed_member_remove_power); cmd[index]["n_modifyp"] = modify_power.has_value ? modify_power.value : 0; cmd[index]["n_member_addp"] = add_power.has_value ? add_power.value : 0; cmd[index]["n_member_removep"] = remove_power.has_value ? remove_power.value : 0; index++; } if(index == 0) return false; this->sendCommand(cmd); return true; } bool ConnectedClient::notifyTextMessage(ChatMessageMode mode, const shared_ptr &invoker, uint64_t targetId, const string &textMessage) { //notifytextmessage targetmode=1 msg=asdasd target=2 invokerid=1 invokername=WolverinDEV invokeruid=xxjnc14LmvTk+Lyrm8OOeo4tOqw= Command cmd("notifytextmessage"); INVOKER(cmd, invoker); cmd["targetmode"] = mode; cmd["target"] = targetId; cmd["msg"] = textMessage; this->sendCommand(cmd); return true; } bool ConnectedClient::notifyServerGroupClientAdd(const shared_ptr &invoker, const shared_ptr &client, const shared_ptr &group) { Command cmd("notifyservergroupclientadded"); INVOKER(cmd, invoker); cmd["sgid"] = group->groupId(); cmd["clid"] = client->getClientId(); cmd["name"] = client->getDisplayName(); cmd["cluid"] = client->getUid(); this->sendCommand(cmd); return true; } bool ConnectedClient::notifyServerGroupClientRemove(std::shared_ptr invoker, std::shared_ptr client, std::shared_ptr group) { Command cmd("notifyservergroupclientdeleted"); INVOKER(cmd, invoker); cmd["sgid"] = group->groupId(); cmd["clid"] = client->getClientId(); cmd["name"] = client->getDisplayName(); cmd["cluid"] = client->getUid(); this->sendCommand(cmd); return true; } bool ConnectedClient::notifyClientChannelGroupChanged( const std::shared_ptr &invoker, const std::shared_ptr &client, const std::shared_ptr& channel, const std::shared_ptr& inherited, const std::shared_ptr& group, bool lock_channel_tree) { assert(!lock_channel_tree); /* not supported */ if(!this->isClientVisible(client, false) && client != this) return false; if(client->getChannel() != channel) return false; assert(client); assert(channel); assert(inherited); assert(group); Command cmd("notifyclientchannelgroupchanged"); INVOKER(cmd, invoker); cmd["cgid"] = group->groupId(); cmd["clid"] = client->getClientId(); cmd["cid"] = channel->channelId(); cmd["cgi"] = inherited ? inherited->channelId() : channel->channelId(); //The inherited channel this->sendCommand(cmd); return true; } bool ConnectedClient::notifyConnectionInfo(std::shared_ptr target, std::shared_ptr info) { Command notify("notifyconnectioninfo"); notify["clid"] = target->getClientId(); if(target->getClientId() != this->getClientId()){ for(const auto& elm : info->properties) notify[elm.first] = elm.second; notify["connection_connected_time"] = chrono::duration_cast(chrono::system_clock::now() - target->connectTimestamp).count(); notify["connection_filetransfer_bandwidth_sent"] = 0; notify["connection_filetransfer_bandwidth_received"] = 0; } if(target->getClientId() == this->getClientId() || this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_remoteaddress_view, 1, target->currentChannel, true)) { notify["connection_client_ip"] = target->getLoggingPeerIp(); notify["connection_client_port"] = target->getPeerPort(); } notify["connection_client2server_packetloss_speech"] = 0.f; notify["connection_client2server_packetloss_keepalive"] = 0.f; notify["connection_client2server_packetloss_control"] = 0.f; notify["connection_client2server_packetloss_total"] = 0.f; notify["connection_idle_time"] = chrono::duration_cast(chrono::system_clock::now() - target->idleTimestamp).count(); this->sendCommand(notify); return true; } bool ConnectedClient::notifyClientMoved(const shared_ptr &client, const std::shared_ptr &target_channel, ViewReasonId reason, std::string msg, std::shared_ptr invoker, bool lock_channel_tree) { assert(!lock_channel_tree); assert(client->getClientId() > 0); assert(client->currentChannel); assert(target_channel); assert(this->isClientVisible(client, false) || &*client == this); Command mv("notifyclientmoved"); mv["clid"] = client->getClientId(); mv["cfid"] = client->currentChannel->channelId(); mv["ctid"] = target_channel->channelId(); mv["reasonid"] = reason; if (invoker) INVOKER(mv, invoker); if (!msg.empty()) mv["reasonmsg"] = msg; else mv["reasonmsg"] = ""; this->sendCommand(mv); return true; } bool ConnectedClient::notifyClientUpdated(const std::shared_ptr &client, const deque> &props, bool lock) { shared_lock channel_lock(this->channel_lock, defer_lock); if(lock) channel_lock.lock(); if(!this->isClientVisible(client, false) && client != this) return false; auto client_id = client->getClientId(); if(client_id == 0) { logError(this->getServerId(), "{} Attempted to send a clientupdate for client id 0. Updated client: {}", CLIENT_STR_LOG_PREFIX, CLIENT_STR_LOG_PREFIX_(client)); return false; } Command response(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifyclientupdated" : ""); response["clid"] = client_id; for (const auto &prop : props) { if(lastOnlineTimestamp.time_since_epoch().count() > 0 && (prop->property_index == property::CLIENT_TOTAL_ONLINE_TIME || prop->property_index == property::CLIENT_MONTH_ONLINE_TIME)) response[prop->name] = client->properties()[prop].as() + duration_cast(system_clock::now() - client->lastOnlineTimestamp).count(); else response[prop->name] = client->properties()[prop].value(); } this->sendCommand(response); return true; } bool ConnectedClient::notifyPluginCmd(std::string name, std::string msg, std::shared_ptr sender) { Command notify("notifyplugincmd"); notify["name"] = name; notify["data"] = msg; INVOKER(notify, sender); this->sendCommand(notify); return true; } bool ConnectedClient::notifyClientChatComposing(const shared_ptr &client) { Command notify("notifyclientchatcomposing"); notify["clid"] = client->getClientId(); notify["cluid"] = client->getUid(); this->sendCommand(notify); return true; } bool ConnectedClient::notifyClientChatClosed(const shared_ptr &client) { Command notify("notifyclientchatclosed"); notify["clid"] = client->getClientId(); notify["cluid"] = client->getUid(); this->sendCommand(notify); return true; } bool ConnectedClient::notifyChannelMoved(const std::shared_ptr &channel, ChannelId order, const std::shared_ptr &invoker) { if(!channel || !this->channels->channel_visible(channel)) return false; Command notify("notifychannelmoved"); INVOKER(notify, invoker); notify["reasonid"] = ViewReasonId::VREASON_MOVED; notify["cid"] = channel->channelId(); notify["cpid"] = channel->parent() ? channel->parent()->channelId() : 0; notify["order"] = order; this->sendCommand(notify); return true; } bool ConnectedClient::notifyChannelCreate(const std::shared_ptr &channel, ChannelId orderId, const std::shared_ptr &invoker) { Command notify("notifychannelcreated"); for (auto &prop : channel->properties().list_properties(property::FLAG_CHANNEL_VARIABLE | property::FLAG_CHANNEL_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) { if(prop.type() == property::CHANNEL_ORDER) notify[prop.type().name] = orderId; else notify[prop.type().name] = prop.value(); } INVOKER(notify, invoker); this->sendCommand(notify); return true; } bool ConnectedClient::notifyChannelHide(const std::deque &channel_ids, bool lock_channel_tree) { if(channel_ids.empty()) return true; if(this->getType() == ClientType::CLIENT_TEAMSPEAK) { //Voice hasnt that event 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(); } else { assert(mutex_locked(this->channel_lock)); } deque> clients_to_remove; { for(const auto& w_client : this->visibleClients) { auto client = w_client.lock(); if(!client) continue; if(client->currentChannel) { auto id = client->currentChannel->channelId(); for(const auto cid : channel_ids) { if(cid == id) { clients_to_remove.push_back(client); break; } } } } } //TODO: May send a unsubscribe and remove the clients like that? this->notifyClientLeftView(clients_to_remove, "Channel gone out of view", false, ViewReasonServerLeft); return this->notifyChannelDeleted(channel_ids, this->server->serverRoot); } else { Command notify("notifychannelhide"); int index = 0; for(const auto& channel : channel_ids) notify[index++]["cid"] = channel; this->sendCommand(notify); } return true; } bool ConnectedClient::notifyChannelShow(const std::shared_ptr &channel, ts::ChannelId orderId) { if(!channel) return false; auto result = false; if(this->getType() == ClientType::CLIENT_TEAMSPEAK) { //Voice hasn't that event result = this->notifyChannelCreate(channel, orderId, this->server->serverRoot); } else { Command notify("notifychannelshow"); for (auto &prop : channel->properties().list_properties(property::FLAG_CHANNEL_VARIABLE | property::FLAG_CHANNEL_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) { if(prop.type() == property::CHANNEL_ORDER) notify[prop.type().name] = orderId; else notify[prop.type().name] = prop.value(); } this->sendCommand(notify); } if(result && this->subscribeToAll) this->subscribeChannel({channel}, false, true); return true; } bool ConnectedClient::notifyChannelDescriptionChanged(std::shared_ptr channel) { if(!this->channels->channel_visible(channel)) return false; Command notifyDesChanges("notifychanneldescriptionchanged"); notifyDesChanges["cid"] = channel->channelId(); this->sendCommand(notifyDesChanges); return true; } bool ConnectedClient::notifyChannelPasswordChanged(std::shared_ptr channel) { if(!this->channels->channel_visible(channel)) return false; Command notifyDesChanges("notifychannelpasswordchanged"); notifyDesChanges["cid"] = channel->channelId(); this->sendCommand(notifyDesChanges); return true; } bool ConnectedClient::notifyClientEnterView(const std::shared_ptr &client, const std::shared_ptr &invoker, const std::string& reason, const std::shared_ptr &to, ViewReasonId reasonId, const std::shared_ptr &from, bool lock_channel_tree) { sassert(client && client->getClientId() > 0); sassert(to); sassert(!lock_channel_tree); /* we don't support locking */ sassert(!this->isClientVisible(client, false) || &*client == this); switch (reasonId) { case ViewReasonId::VREASON_MOVED: case ViewReasonId::VREASON_BAN: case ViewReasonId::VREASON_CHANNEL_KICK: case ViewReasonId::VREASON_SERVER_KICK: if(!invoker) { logCritical(this->getServerId(), "{} ConnectedClient::notifyClientEnterView() => missing invoker for reason id {}", CLIENT_STR_LOG_PREFIX, reasonId); if(this->server) ;//invoker = this->server->serverRoot.get(); } break; default: break; } Command cmd("notifycliententerview"); cmd["cfid"] = from ? from->channelId() : 0; cmd["ctid"] = to ? to->channelId() : 0; cmd["reasonid"] = reasonId; INVOKER(cmd, invoker); switch (reasonId) { case ViewReasonId::VREASON_MOVED: case ViewReasonId::VREASON_BAN: case ViewReasonId::VREASON_CHANNEL_KICK: case ViewReasonId::VREASON_SERVER_KICK: cmd["reasonmsg"] = reason; break; default: break; } for (const auto &elm : client->properties()->list_properties(property::FLAG_CLIENT_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) { cmd[elm.type().name] = elm.value(); } visibleClients.emplace_back(client); this->sendCommand(cmd); return true; } bool ConnectedClient::notifyClientEnterView(const std::deque> &clients, const ts::ViewReasonSystemT &_vrs) { if(clients.empty()) return true; assert(mutex_locked(this->channel_lock)); Command cmd("notifycliententerview"); cmd["cfid"] = 0; cmd["reasonid"] = ViewReasonId::VREASON_SYSTEM; ChannelId current_channel = 0; size_t index = 0; auto it = clients.begin(); while(it != clients.end()) { auto client = *(it++); if(this->isClientVisible(client, false)) continue; auto channel = client->getChannel(); sassert(!channel || channel->channelId() != 0); if(!channel) /* hmm suspecious */ continue; if(current_channel != channel->channelId()) { if(current_channel == 0) cmd[index]["ctid"] = (current_channel = channel->channelId()); else { it--; /* we still have to send him */ break; } } this->visibleClients.push_back(client); for (const auto &elm : client->properties()->list_properties(property::FLAG_CLIENT_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) { cmd[index][elm.type().name] = elm.value(); } index++; if(index > 16) /* max 16 clients per packet */ break; } if(index > 0) this->sendCommand(cmd); if(it != clients.end()) return this->notifyClientEnterView({it, clients.end()}, _vrs); return true; } bool ConnectedClient::notifyChannelEdited(std::shared_ptr channel, std::vector keys, ConnectedClient *invoker) { auto v_channel = this->channels->find_channel(channel->channelId()); if(!v_channel) return false; //Not visible? Important do not remove! Command notify("notifychanneledited"); auto counter = 0; for (const auto& key : keys) { auto info = property::impl::info(key); if(*info == property::CHANNEL_UNDEFINED) { logError(this->getServerId(), "Tried to edit a non existing channel property: " + key); continue; } counter++; if(*info == property::CHANNEL_ORDER) { notify[key] = v_channel->previous_channel; } else { notify[key] = channel->properties()[info].as(); } } if(counter == 0) return true; notify["cid"] = channel->channelId(); INVOKER(notify, invoker); notify["reasonid"] = ViewReasonId::VREASON_EDITED; this->sendCommand(notify); return true; } bool ConnectedClient::notifyChannelDeleted(const deque& channel_ids, const std::shared_ptr& invoker) { if(channel_ids.empty()) return true; Command notify("notifychanneldeleted"); int index = 0; for (const auto& channel_id : channel_ids) notify[index++]["cid"] = channel_id; INVOKER(notify, invoker); notify["reasonid"] = ViewReasonId::VREASON_EDITED; this->sendCommand(notify); return true; } bool ConnectedClient::notifyServerUpdated(std::shared_ptr invoker) { Command response("notifyserverupdated"); for (const auto& elm : this->server->properties().list_properties(property::FLAG_SERVER_VARIABLE, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) { if(elm.type() == property::VIRTUALSERVER_MIN_WINPHONE_VERSION) continue; //if(elm->type() == property::VIRTUALSERVER_RESERVED_SLOTS) response[elm.type().name] = elm.value(); } if(getType() == CLIENT_QUERY) INVOKER(response, invoker); this->sendCommand(response); return true; } bool ConnectedClient::notifyClientPoke(std::shared_ptr invoker, std::string msg) { Command cmd("notifyclientpoke"); INVOKER(cmd, invoker); cmd["msg"] = msg; this->sendCommand(cmd); return true; } bool ConnectedClient::notifyChannelSubscribed(const deque> &channels) { Command notify("notifychannelsubscribed"); int index = 0; for (const auto& ch : channels) { notify[index]["es"] = this->server->getClientsByChannel(ch).empty() ? ch->emptySince() : 0; notify[index++]["cid"] = ch->channelId(); } this->sendCommand(notify); return true; } bool ConnectedClient::notifyChannelUnsubscribed(const deque> &channels) { Command notify("notifychannelunsubscribed"); int index = 0; for (const auto& ch : channels) { notify[index]["es"] = this->server->getClientsByChannel(ch).empty() ? ch->emptySince() : 0; notify[index++]["cid"] = ch->channelId(); } this->sendCommand(notify); return true; } bool ConnectedClient::notifyMusicQueueAdd(const shared_ptr& bot, const shared_ptr& entry, int index, const std::shared_ptr& invoker) { Command notify("notifymusicqueueadd"); notify["bot_id"] = bot->getClientDatabaseId(); notify["song_id"] = entry->getSongId(); notify["url"] = entry->getUrl(); notify["index"] = index; INVOKER(notify, invoker); this->sendCommand(notify); return true; } bool ConnectedClient::notifyMusicQueueRemove(const std::shared_ptr &bot, const std::deque> &entry, const std::shared_ptr& invoker) { Command notify("notifymusicqueueremove"); notify["bot_id"] = bot->getClientDatabaseId(); int index = 0; for(const auto& song : entry) notify[index++]["song_id"] = song->getSongId(); INVOKER(notify, invoker); this->sendCommand(notify); return true; } bool ConnectedClient::notifyMusicQueueOrderChange(const std::shared_ptr &bot, const std::shared_ptr &entry, int order, const std::shared_ptr& invoker) { Command notify("notifymusicqueueorderchange"); notify["bot_id"] = bot->getClientDatabaseId(); notify["song_id"] = entry->getSongId(); notify["index"] = order; INVOKER(notify, invoker); this->sendCommand(notify); return true; } bool ConnectedClient::notifyMusicPlayerStatusUpdate(const std::shared_ptr &bot) { Command notify("notifymusicstatusupdate"); notify["bot_id"] = bot->getClientDatabaseId(); auto player = bot->current_player(); if(player) { notify["player_buffered_index"] = player->bufferedUntil().count(); notify["player_replay_index"] = player->currentIndex().count(); } else { notify["player_buffered_index"] = 0; notify["player_replay_index"] = 0; } this->sendCommand(notify); return true; } extern void apply_song(Command& command, const std::shared_ptr& element, int index = 0); bool ConnectedClient::notifyMusicPlayerSongChange(const std::shared_ptr &bot, const shared_ptr &newEntry) { Command notify("notifymusicplayersongchange"); notify["bot_id"] = bot->getClientDatabaseId(); if(newEntry) { apply_song(notify, newEntry); } else { notify["song_id"] = 0; } this->sendCommand(notify); return true; }