#include #include #include #include "../../build.h" #include "../ConnectedClient.h" #include "../InternalClient.h" #include "../../server/file/FileServer.h" #include "../voice/VoiceClient.h" #include "PermissionManager.h" #include "../../InstanceHandler.h" #include "../../server/QueryServer.h" #include "../file/FileClient.h" #include "../music/MusicClient.h" #include "../query/QueryClient.h" #include "../../weblist/WebListManager.h" #include "../../manager/ConversationManager.h" #include "../../manager/PermissionNameMapper.h" #include #include "helpers.h" #include #include #include #include #include #include #include using namespace std::chrono; using namespace std; using namespace ts; using namespace ts::server; using namespace ts::token; #define QUERY_PASSWORD_LENGTH 12 command_result ConnectedClient::handleCommandClientGetVariables(Command &cmd) { CMD_REQ_SERVER; ConnectedLockedClient client{this->server->find_client_by_id(cmd["clid"].as())}; shared_lock tree_lock(this->channel_lock); if (!client || (client.client != this && !this->isClientVisible(client.client, false))) return command_result{error::client_invalid_id, ""}; deque> props; for (auto &prop : client->properties()->list_properties(property::FLAG_CLIENT_VARIABLE, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) { props.push_back(property::info((property::ClientProperties) prop.type().property_index)); } this->notifyClientUpdated(client.client, props, false); return command_result{error::ok}; } command_result ConnectedClient::handleCommandClientKick(Command &cmd) { CMD_REQ_SERVER; CMD_CHK_AND_INC_FLOOD_POINTS(25); ConnectedLockedClient client{this->server->find_client_by_id(cmd["clid"].as())}; if (!client) return command_result{error::client_invalid_id}; if (client->getType() == CLIENT_MUSIC) return command_result{error::client_invalid_type, "You cant kick a music bot!"}; std::shared_ptr targetChannel = nullptr; auto type = cmd["reasonid"].as(); if (type == ViewReasonId::VREASON_CHANNEL_KICK) { auto channel = client->getChannel(); ACTION_REQUIRES_PERMISSION(permission::i_client_kick_from_channel_power, client->calculate_permission(permission::i_client_needed_kick_from_channel_power, client->getChannelId()), client->getChannelId()); targetChannel = this->server->channelTree->getDefaultChannel(); } else if (type == ViewReasonId::VREASON_SERVER_KICK) { auto channel = client->getChannel(); ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_client_kick_from_server_power, client->calculate_permission(permission::i_client_needed_kick_from_server_power, client->getChannelId())); targetChannel = nullptr; } else return command_result{error::not_implemented}; if (targetChannel) { this->server->notify_client_kick(client.client, this->ref(), cmd["reasonmsg"].as(), targetChannel); } else { this->server->notify_client_kick(client.client, this->ref(), cmd["reasonmsg"].as(), nullptr); client->close_connection(system_clock::now() + seconds(1)); } return command_result{error::ok}; } command_result ConnectedClient::handleCommandClientGetIds(Command &cmd) { CMD_REQ_SERVER; bool error = false; bool found = false; auto client_list = this->server->getClients(); Command notify(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyclientids" : ""); int result_index = 0; for(int index = 0; index < cmd.bulkCount(); index++) { auto unique_id = cmd[index]["cluid"].as(); for(const auto& entry : client_list) { if(entry->getUid() == unique_id) { if(!config::server::show_invisible_clients_as_online && !this->channels->channel_visible(entry->currentChannel, nullptr)) continue; notify[result_index]["name"] = entry->getDisplayName(); notify[result_index]["clid"] = entry->getClientId(); notify[result_index]["cluid"] = entry->getUid(); result_index++; found = true; } } if(found) found = false; else error = false; } string uid = cmd["cluid"]; if(result_index > 0) { this->sendCommand(notify); } if(error) { return command_result{error::database_empty_result}; } return command_result{error::ok}; } command_result ConnectedClient::handleCommandClientMove(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(10); shared_lock server_channel_r_lock(this->server->channel_tree_lock); auto target_client_id = cmd["clid"].as(); ConnectedLockedClient target_client{target_client_id == 0 ? this->ref() : this->server->find_client_by_id(target_client_id)}; if(!target_client) { return command_result{error::client_invalid_id, "Invalid target clid"}; } if(!target_client->getChannel()) { if(target_client.client != this) return command_result{error::client_invalid_id, "Invalid target clid"}; } auto channel = this->server->channelTree->findChannel(cmd["cid"].as()); if (!channel) { return command_result{error::channel_invalid_id}; } auto permission_cache = make_shared(); if(!cmd[0].has("cpw")) cmd["cpw"] = ""; if (!channel->passwordMatch(cmd["cpw"], true)) if (!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_join_ignore_password, channel->channelId()))) return command_result{error::channel_invalid_password}; auto permission_error = this->calculate_and_get_join_state(channel); if(permission_error != permission::unknown) return command_result{permission_error}; if (!channel->properties()[property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED].as() || !channel->properties()[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_UNLIMITED].as()) { if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_join_ignore_maxclients, channel->channelId()))) { if(!channel->properties()[property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED].as()) { auto maxClients = channel->properties()[property::CHANNEL_MAXCLIENTS].as(); if (maxClients >= 0 && maxClients <= this->server->getClientsByChannel(channel).size()) return command_result{error::channel_maxclients_reached}; } if(!channel->properties()[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_UNLIMITED].as()) { shared_ptr family_root; if(channel->properties()[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED].as()) { family_root = channel; while(family_root && family_root->properties()[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED].as()) family_root = family_root->parent(); } if(family_root && !family_root->properties()[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_UNLIMITED]) { //Could not be CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED auto maxClients = family_root->properties()[property::CHANNEL_MAXFAMILYCLIENTS].as(); auto clients = 0; for(const auto& entry : this->server->getClientsByChannelRoot(channel, false)) if(entry.get() != this) clients++; //Dont count the client itself if (maxClients >= 0 && maxClients <= clients) return command_result{error::channel_maxfamily_reached}; } } } } if (target_client.client != this) ACTION_REQUIRES_PERMISSION(permission::i_client_move_power, target_client->calculate_permission(permission::i_client_needed_move_power, target_client->getChannelId()), target_client->getChannelId()); server_channel_r_lock.unlock(); unique_lock server_channel_w_lock(this->server->channel_tree_lock); auto oldChannel = target_client->getChannel(); this->server->client_move( target_client.client, channel, target_client.client == this ? nullptr : _this.lock(), "", target_client.client == this ? ViewReasonId::VREASON_USER_ACTION : ViewReasonId::VREASON_MOVED, true, server_channel_w_lock ); if(oldChannel) { if(!server_channel_w_lock.owns_lock()) server_channel_w_lock.lock(); if(oldChannel->channelType() == ChannelType::temporary && oldChannel->properties()[property::CHANNEL_DELETE_DELAY].as() == 0) if(this->server->getClientsByChannelRoot(oldChannel, false).empty()) this->server->delete_channel(dynamic_pointer_cast(oldChannel), this->ref(), "temporary auto delete", server_channel_w_lock); if(server_channel_w_lock.owns_lock()) server_channel_w_lock.unlock(); } return command_result{error::ok}; } command_result ConnectedClient::handleCommandClientPoke(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); ConnectedLockedClient client{ this->server->find_client_by_id(cmd["clid"].as())}; if (!client) return command_result{error::client_invalid_id}; if (client->getType() == CLIENT_MUSIC) return command_result{error::client_invalid_type}; ACTION_REQUIRES_PERMISSION(permission::i_client_poke_power, client->calculate_permission(permission::i_client_needed_poke_power, client->getChannelId()), client->getChannelId()); client->notifyClientPoke(_this.lock(), cmd["msg"]); return command_result{error::ok}; } command_result ConnectedClient::handleCommandClientChatComposing(Command &cmd) { CMD_REQ_SERVER; CMD_CHK_AND_INC_FLOOD_POINTS(0); ConnectedLockedClient client{this->server->find_client_by_id(cmd["clid"].as())}; if (!client) return command_result{error::client_invalid_id}; client->notifyClientChatComposing(_this.lock()); return command_result{error::ok}; } command_result ConnectedClient::handleCommandClientChatClosed(Command &cmd) { CMD_REQ_SERVER; CMD_CHK_AND_INC_FLOOD_POINTS(5); ConnectedLockedClient client{this->server->find_client_by_id(cmd["clid"].as())}; if (!client) return command_result{error::client_invalid_id}; { unique_lock channel_lock(this->channel_lock); this->openChats.erase(remove_if(this->openChats.begin(), this->openChats.end(), [&](const weak_ptr& weak) { return weak.lock() == client; }), this->openChats.end()); } { unique_lock channel_lock(client->get_channel_lock()); client->openChats.erase(remove_if(client->openChats.begin(), client->openChats.end(), [&](const weak_ptr& weak) { return weak.lock().get() == this; }), client->openChats.end()); } client->notifyClientChatClosed(_this.lock()); return command_result{error::ok}; } //ftgetfilelist cid=1 cpw path=\/ return_code=1:x //Answer: //1 .. n // notifyfilelist cid=1 path=\/ return_code=1:x name=testFile size=35256 datetime=1509459767 type=1|name=testDir size=0 datetime=1509459741 type=0|name=testDir_2 size=0 datetime=1509459763 type=0 //notifyfilelistfinished cid=1 path=\/ inline void cmd_filelist_append_files(ServerId sid, Command &command, vector> files) { int index = 0; logTrace(sid, "Sending file list for path {}", command["path"].string()); for (const auto& fileEntry : files) { logTrace(sid, " - {} ({})", fileEntry->name, fileEntry->type == file::FileType::FILE ? "file" : "directory"); command[index]["name"] = fileEntry->name; command[index]["datetime"] = std::chrono::duration_cast(fileEntry->lastChanged.time_since_epoch()).count(); command[index]["type"] = fileEntry->type; if (fileEntry->type == file::FileType::FILE) command[index]["size"] = static_pointer_cast(fileEntry)->fileSize; else command[index]["size"] = 0; index++; } } #define CMD_REQ_FSERVER if(!serverInstance->getFileServer()) return command_result{error::vs_critical, "file server not started yet!"} //start=0 duration=10 //pattern=%asd% struct ClientDbArgs { shared_ptr server; int index = 0; int offset = 0; int resultIndex = 0; bool showIp = false; bool largeInfo = false; Command *result = nullptr; }; command_result ConnectedClient::handleCommandClientDbList(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_client_dblist, 1); Command notify(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyclientdblist" : ""); if (!cmd[0].has("start")) cmd["start"] = 0; if (!cmd[0].has("duration")) cmd["duration"] = 20; if (cmd[0]["duration"].as() > 2000) cmd["duration"] = 2000; if (cmd[0]["duration"].as() < 1) cmd["duration"] = 1; auto maxIndex = cmd["start"].as() + cmd["duration"].as(); ClientDbArgs args; args.server = this->server; args.offset = cmd["start"].as(); args.result = ¬ify; args.resultIndex = 0; args.index = 0; args.showIp = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_remoteaddress_view, 0)); args.largeInfo = cmd.hasParm("details"); (LOG_SQL_CMD)(sql::command(this->server->getSql(), "SELECT * FROM `clients` WHERE `serverId` = :sid ORDER BY `cldbid` ASC" + (maxIndex > 0 ? " LIMIT " + to_string(maxIndex) : ""), variable{":sid", this->server->getServerId()}).query( [](ClientDbArgs *pArgs, int length, char **values, char **column) { pArgs->index++; if (pArgs->offset < pArgs->index) { ClientDbId id = 0; string uid, name, ip; string created = "0", lastConnected = "0", connections = "0"; for (int index = 0; index < length; index++) { string key = column[index]; if (key == "cldbid") id = stoll(values[index]); else if (key == "clientUid") uid = values[index]; else if (key == "firstConnect") created = values[index]; else if (key == "lastConnect") lastConnected = values[index]; else if (key == "connections") connections = values[index]; else if (key == "lastName") name = values[index]; } pArgs->result->operator[](pArgs->resultIndex)["cldbid"] = id; pArgs->result->operator[](pArgs->resultIndex)["client_unique_identifier"] = uid; pArgs->result->operator[](pArgs->resultIndex)["client_nickname"] = name; pArgs->result->operator[](pArgs->resultIndex)["client_created"] = created; pArgs->result->operator[](pArgs->resultIndex)["client_lastconnected"] = lastConnected; pArgs->result->operator[](pArgs->resultIndex)["client_totalconnections"] = connections; pArgs->result->operator[](pArgs->resultIndex)["client_description"] = ""; auto props = serverInstance->databaseHelper()->loadClientProperties(pArgs->server, id, ClientType::CLIENT_TEAMSPEAK); if (props) { pArgs->result->operator[](pArgs->resultIndex)["client_lastip"] = (*props)[property::CONNECTION_CLIENT_IP].as(); pArgs->result->operator[](pArgs->resultIndex)["client_description"] = (*props)[property::CLIENT_DESCRIPTION].as(); if (pArgs->largeInfo) { pArgs->result->operator[](pArgs->resultIndex)["client_badges"] = (*props)[property::CLIENT_BADGES].as(); pArgs->result->operator[](pArgs->resultIndex)["client_version"] = (*props)[property::CLIENT_VERSION].as(); pArgs->result->operator[](pArgs->resultIndex)["client_platform"] = (*props)[property::CLIENT_PLATFORM].as(); pArgs->result->operator[](pArgs->resultIndex)["client_hwid"] = (*props)[property::CLIENT_HARDWARE_ID].as(); } } if (!pArgs->showIp) pArgs->result->operator[](pArgs->resultIndex)["client_lastip"] = "hidden"; pArgs->resultIndex++; } return 0; }, &args)); if (args.resultIndex == 0) return command_result{error::database_empty_result}; if (cmd.hasParm("count")) { size_t result = 0; sql::command(this->server->getSql(), "SELECT COUNT(*) AS `count` FROM `clients` WHERE `serverId` = :sid", variable{":sid", this->server->getServerId()}).query([](size_t *ptr, int, char **v, char **) { *ptr = static_cast(stoll(v[0])); return 0; }, &result); notify[0]["count"] = result; } this->sendCommand(notify); return command_result{error::ok}; } command_result ConnectedClient::handleCommandClientDBEdit(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_modify_dbproperties, 1); if (!serverInstance->databaseHelper()->validClientDatabaseId(this->server, cmd["cldbid"])) return command_result{error::database_empty_result, "invalid cldbid"}; auto props = serverInstance->databaseHelper()->loadClientProperties(this->server, cmd["cldbid"], ClientType::CLIENT_TEAMSPEAK); for (auto &elm : cmd[0].keys()) { if (elm == "cldbid") continue; auto info = property::info(elm); if(*info == property::CLIENT_UNDEFINED) { logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change someone's db entry, but the entry in unknown: " + elm); continue; } if(!info->validate_input(cmd[elm].as())) { logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change a property to an invalid value. (Value: '" + cmd[elm].as() + "', Property: '" + info->name + "')"); continue; } (*props)[info] = cmd[elm].string(); } return command_result{error::ok}; } command_result ConnectedClient::handleCommandClientEdit(ts::Command &cmd) { CMD_REQ_SERVER; ConnectedLockedClient client{this->server->find_client_by_id(cmd["clid"].as())}; if (!client) return command_result{error::client_invalid_id}; return this->handleCommandClientEdit(cmd, client.client); } command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std::shared_ptr& client) { assert(client); auto self = client == this; CMD_CHK_AND_INC_FLOOD_POINTS(self ? 15 : 25); CMD_RESET_IDLE; bool update_talk_rights = false; unique_ptr> nickname_lock; deque> keys; for(const auto& key : cmd[0].keys()) { if(key == "return_code") continue; if(key == "clid") continue; const auto &info = property::info(key); if(*info == property::CLIENT_UNDEFINED) { logError(this->getServerId(), R"([{}] Tried to change a not existing client property for {}. (Key: "{}", Value: "{}"))", CLIENT_STR_LOG_PREFIX, CLIENT_STR_LOG_PREFIX_(client), key, cmd[key].string()); continue; } if((info->flags & property::FLAG_USER_EDITABLE) == 0) { logError(this->getServerId(), R"([{}] Tried to change a not user editable client property for {}. (Key: "{}", Value: "{}"))", CLIENT_STR_LOG_PREFIX, CLIENT_STR_LOG_PREFIX_(client), key, cmd[key].string()); continue; } if(!info->validate_input(cmd[key].as())) { logError(this->getServerId(), R"([{}] Tried to change a client property to an invalid value for {}. (Key: "{}", Value: "{}"))", CLIENT_STR_LOG_PREFIX, CLIENT_STR_LOG_PREFIX_(client), key, cmd[key].string()); continue; } if(client->properties()[info].as() == cmd[key].as()) continue; if (*info == property::CLIENT_DESCRIPTION) { if (self) { ACTION_REQUIRES_PERMISSION(permission::b_client_modify_own_description, 1, client->getChannelId()); } else if(client->getType() == ClientType::CLIENT_MUSIC) { if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) { ACTION_REQUIRES_PERMISSION(permission::i_client_music_modify_power, client->calculate_permission(permission::i_client_music_needed_modify_power, client->getChannelId()), client->getChannelId()); } } else { ACTION_REQUIRES_PERMISSION(permission::b_client_modify_description, 1, client->getChannelId()); } string value = cmd["client_description"].string(); if (count_characters(value) > 200) return command_result{error::parameter_invalid, "Invalid description length. A maximum of 200 characters is allowed!"}; } else if (*info == property::CLIENT_IS_TALKER) { ACTION_REQUIRES_PERMISSION(permission::b_client_set_flag_talker, 1, client->getChannelId()); cmd["client_is_talker"] = cmd["client_is_talker"].as(); cmd["client_talk_request"] = 0; update_talk_rights = true; keys.emplace_back(property::CLIENT_IS_TALKER, "client_is_talker"); keys.emplace_back(property::CLIENT_TALK_REQUEST, "client_talk_request"); continue; } else if(*info == property::CLIENT_NICKNAME) { if(!self) { if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type}; if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) { ACTION_REQUIRES_PERMISSION(permission::i_client_music_rename_power, client->calculate_permission(permission::i_client_music_needed_rename_power, client->getChannelId()), client->getChannelId()); } } string name = cmd["client_nickname"].string(); if (count_characters(name) < 3) return command_result{error::parameter_invalid, "Invalid name length. A minimum of 3 characters is required!"}; if (count_characters(name) > 30) return command_result{error::parameter_invalid, "Invalid name length. A maximum of 30 characters is allowed!"}; if (!permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_ignore_bans, client->getClientId()))) { auto banRecord = serverInstance->banManager()->findBanByName(this->getServerId(), name); if (banRecord) return command_result{error::client_nickname_inuse, string() + "This nickname is " + (banRecord->serverId == 0 ? "globally " : "") + "banned for the reason: " + banRecord->reason}; } if (this->server) { nickname_lock = std::make_unique>(this->server->client_nickname_lock); bool self = false; for (const auto &cl : this->server->getClients()) { if (cl->getDisplayName() == cmd["client_nickname"].string()) { if(cl == this) self = true; else return command_result{error::client_nickname_inuse, "This nickname is already in use"}; } } if(self) { nickname_lock.reset(); continue; } } } else if(*info == property::CLIENT_PLAYER_VOLUME) { if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type}; if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) { ACTION_REQUIRES_PERMISSION(permission::i_client_music_modify_power, client->calculate_permission(permission::i_client_music_needed_modify_power, client->getChannelId()), client->getChannelId()); } auto bot = dynamic_pointer_cast(client); assert(bot); auto volume = cmd["player_volume"].as(); auto max_volume = this->calculate_permission(permission::i_client_music_create_modify_max_volume, client->getClientId()); if(max_volume.has_value && !permission::v2::permission_granted(volume * 100, max_volume)) return command_result{permission::i_client_music_create_modify_max_volume}; bot->volume_modifier(cmd["player_volume"]); } else if(*info == property::CLIENT_IS_CHANNEL_COMMANDER) { if(!self) { if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type}; if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) { ACTION_REQUIRES_PERMISSION(permission::i_client_music_modify_power, client->calculate_permission(permission::i_client_music_needed_modify_power, client->getChannelId()), client->getChannelId()); } } if(cmd["client_is_channel_commander"].as()) ACTION_REQUIRES_PERMISSION(permission::b_client_use_channel_commander, 1, client->getChannelId()); } else if(*info == property::CLIENT_IS_PRIORITY_SPEAKER) { //FIXME allow other to remove this thing if(!self) { if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type}; if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) ACTION_REQUIRES_PERMISSION(permission::i_client_music_modify_power, client->calculate_permission(permission::i_client_music_needed_modify_power, client->getClientId()), client->getClientId()); } if(cmd["client_is_priority_speaker"].as()) ACTION_REQUIRES_PERMISSION(permission::b_client_use_priority_speaker, 1, client->getChannelId()); } else if (self && key == "client_talk_request") { CMD_CHK_AND_INC_FLOOD_POINTS(20); ACTION_REQUIRES_PERMISSION(permission::b_client_request_talker, 1, client->getChannelId()); if (cmd["client_talk_request"].as()) cmd["client_talk_request"] = duration_cast(system_clock::now().time_since_epoch()).count(); else cmd["client_talk_request"] = 0; keys.emplace_back(property::CLIENT_TALK_REQUEST, "client_talk_request"); continue; } else if (self && key == "client_badges") { std::string str = cmd[key]; size_t index = 0; int badgesTags = 0; do { index = str.find("badges", index); if (index < str.length()) badgesTags++; index++; } while (index < str.length() && index != 0); if (badgesTags >= 2) { if (!permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_allow_invalid_badges, client->getClientId()))) ((VoiceClient *) this)->disconnect(VREASON_SERVER_KICK, config::messages::kick_invalid_badges, this->server ? this->server->serverAdmin : dynamic_pointer_cast(serverInstance->getInitialServerAdmin()), true); return command_result{error::parameter_invalid, "Invalid badges"}; } //FIXME stuff here } else if(!self && key == "client_version") { if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type}; if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) { ACTION_REQUIRES_PERMISSION(permission::i_client_music_modify_power, client->calculate_permission(permission::i_client_music_needed_modify_power, client->getChannelId()), client->getChannelId()); } } else if(!self && key == "client_platform") { if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type}; if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) { ACTION_REQUIRES_PERMISSION(permission::i_client_music_modify_power, client->calculate_permission(permission::i_client_music_needed_modify_power, client->getChannelId()), client->getChannelId()); } } else if(!self && key == "client_country") { if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type}; if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) { ACTION_REQUIRES_PERMISSION(permission::i_client_music_modify_power, client->calculate_permission(permission::i_client_music_needed_modify_power, client->getChannelId()), client->getChannelId()); } } else if(!self && (*info == property::CLIENT_FLAG_NOTIFY_SONG_CHANGE/* || *info == property::CLIENT_NOTIFY_SONG_MESSAGE*/)) { if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type}; if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) { ACTION_REQUIRES_PERMISSION(permission::i_client_music_modify_power, client->calculate_permission(permission::i_client_music_needed_modify_power, client->getChannelId()), client->getChannelId()); } } else if(!self && key == "client_uptime_mode") { if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type}; if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) { ACTION_REQUIRES_PERMISSION(permission::i_client_music_modify_power, client->calculate_permission(permission::i_client_music_needed_modify_power, client->getChannelId()), client->getChannelId()); } if(cmd[key].as() == MusicClient::UptimeMode::TIME_SINCE_SERVER_START) { cmd["client_lastconnected"] = duration_cast(this->server->startTimestamp.time_since_epoch()).count(); } else { string value = client->properties()[property::CLIENT_CREATED]; if(value.empty()) value = "0"; cmd["client_lastconnected"] = value; } keys.emplace_back(property::CLIENT_LASTCONNECTED, "client_lastconnected"); } else if(!self && *info == property::CLIENT_BOT_TYPE) { ACTION_REQUIRES_PERMISSION(permission::i_client_music_modify_power, client->calculate_permission(permission::i_client_music_needed_modify_power, client->getChannelId()), client->getChannelId()); auto type = cmd["client_bot_type"].as(); if(type == MusicClient::Type::TEMPORARY) { ACTION_REQUIRES_PERMISSION(permission::b_client_music_modify_temporary, 1, client->getChannelId()); } else if(type == MusicClient::Type::SEMI_PERMANENT) { ACTION_REQUIRES_PERMISSION(permission::b_client_music_modify_semi_permanent, 1, client->getChannelId()); } else if(type == MusicClient::Type::PERMANENT) { ACTION_REQUIRES_PERMISSION(permission::b_client_music_modify_permanent, 1, client->getChannelId()); } else return command_result{error::parameter_invalid}; } else if(*info == property::CLIENT_AWAY_MESSAGE) { if(!self) continue; if(cmd["client_away_message"].string().length() > 256) return command_result{error::parameter_invalid}; } else if(!self) { /* dont edit random properties of other clients. For us self its allowed to edit the rest without permissions */ continue; } keys.emplace_back((property::ClientProperties) info->property_index, key); } deque updates; for(const auto& key : keys) { if(key.first == property::CLIENT_IS_PRIORITY_SPEAKER) { client->clientPermissions->set_permission(permission::b_client_is_priority_speaker, {1, 0}, cmd["client_is_priority_speaker"].as() ? permission::v2::PermissionUpdateType::set_value : permission::v2::PermissionUpdateType::delete_value, permission::v2::PermissionUpdateType::do_nothing); } client->properties()[key.first] = cmd[0][key.second].value(); updates.push_back(key.first); } if(update_talk_rights) client->updateTalkRights(client->properties()[property::CLIENT_TALK_POWER]); if(this->server) this->server->notifyClientPropertyUpdates(client, updates); nickname_lock.reset(); return command_result{error::ok}; } command_result ConnectedClient::handleCommandClientUpdate(Command &cmd) { return this->handleCommandClientEdit(cmd, _this.lock()); } command_result ConnectedClient::handleCommandClientMute(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; ConnectedLockedClient client{this->server->find_client_by_id(cmd["clid"].as())}; if (!client || client->getClientId() == this->getClientId()) return command_result{error::client_invalid_id}; { unique_lock channel_lock(this->channel_lock); for(const auto& weak : this->mutedClients) if(weak.lock() == client) return command_result{error::ok}; this->mutedClients.push_back(client.client); } if (config::voice::notifyMuted) client->notifyTextMessage(ChatMessageMode::TEXTMODE_PRIVATE, _this.lock(), client->getClientId(), 0, system_clock::now(), config::messages::mute_notify_message); return command_result{error::ok}; } command_result ConnectedClient::handleCommandClientUnmute(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; ConnectedLockedClient client{this->server->find_client_by_id(cmd["clid"].as())}; if (!client || client->getClientId() == this->getClientId()) return command_result{error::client_invalid_id}; { unique_lock channel_lock(this->channel_lock); this->mutedClients.erase(std::remove_if(this->mutedClients.begin(), this->mutedClients.end(), [&](const weak_ptr& weak) { auto c = weak.lock(); return !c || c == client; }), this->mutedClients.end()); } if (config::voice::notifyMuted) client->notifyTextMessage(ChatMessageMode::TEXTMODE_PRIVATE, _this.lock(), client->getClientId(), 0, system_clock::now(), config::messages::unmute_notify_message); return command_result{error::ok}; } command_result ConnectedClient::handleCommandClientList(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; bool allow_ip = false; if (cmd.hasParm("ip")) allow_ip = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_remoteaddress_view, 0)); Command result(""); int index = 0; this->server->forEachClient([&](shared_ptr client) { if (client->getType() == ClientType::CLIENT_INTERNAL) return; result[index]["clid"] = client->getClientId(); if (client->getChannel()) result[index]["cid"] = client->getChannel()->channelId(); else result[index]["cid"] = 0; result[index]["client_database_id"] = client->getClientDatabaseId(); result[index]["client_nickname"] = client->getDisplayName(); result[index]["client_type"] = client->getType(); if (cmd.hasParm("uid")) result[index]["client_unique_identifier"] = client->getUid(); if (cmd.hasParm("away")) { result[index]["client_away"] = client->properties()[property::CLIENT_AWAY].as(); result[index]["client_away_message"] = client->properties()[property::CLIENT_AWAY_MESSAGE].as(); } if (cmd.hasParm("groups")) { result[index]["client_channel_group_id"] = client->properties()[property::CLIENT_CHANNEL_GROUP_ID].as(); result[index]["client_servergroups"] = client->properties()[property::CLIENT_SERVERGROUPS].as(); result[index]["client_channel_group_inherited_channel_id"] = client->properties()[property::CLIENT_CHANNEL_GROUP_INHERITED_CHANNEL_ID].as(); } if (cmd.hasParm("times")) { result[index]["client_idle_time"] = duration_cast(system_clock::now() - client->idleTimestamp).count(); result[index]["client_total_online_time"] = client->properties()[property::CLIENT_TOTAL_ONLINE_TIME].as() + duration_cast(system_clock::now() - client->lastOnlineTimestamp).count(); result[index]["client_month_online_time"] = client->properties()[property::CLIENT_MONTH_ONLINE_TIME].as() + duration_cast(system_clock::now() - client->lastOnlineTimestamp).count(); result[index]["client_idle_time"] = duration_cast(system_clock::now() - client->idleTimestamp).count(); result[index]["client_created"] = client->properties()[property::CLIENT_CREATED].as(); result[index]["client_lastconnected"] = client->properties()[property::CLIENT_LASTCONNECTED].as(); } if (cmd.hasParm("info")) { result[index]["client_version"] = client->properties()[property::CLIENT_VERSION].as(); result[index]["client_platform"] = client->properties()[property::CLIENT_PLATFORM].as(); } if (cmd.hasParm("badges")) result[index]["client_badges"] = client->properties()[property::CLIENT_BADGES].as(); if (cmd.hasParm("country")) result[index]["client_country"] = client->properties()[property::CLIENT_COUNTRY].as(); if (cmd.hasParm("ip")) result[index]["connection_client_ip"] = allow_ip ? client->properties()[property::CONNECTION_CLIENT_IP].as() : "hidden"; if (cmd.hasParm("icon")) result[index]["client_icon_id"] = client->properties()[property::CLIENT_ICON_ID].as(); if (cmd.hasParm("voice")) { result[index]["client_talk_power"] = client->properties()[property::CLIENT_TALK_POWER].as(); result[index]["client_flag_talking"] = client->properties()[property::CLIENT_FLAG_TALKING].as(); result[index]["client_input_muted"] = client->properties()[property::CLIENT_INPUT_MUTED].as(); result[index]["client_output_muted"] = client->properties()[property::CLIENT_OUTPUT_MUTED].as(); result[index]["client_input_hardware"] = client->properties()[property::CLIENT_INPUT_HARDWARE].as(); result[index]["client_output_hardware"] = client->properties()[property::CLIENT_OUTPUT_HARDWARE].as(); result[index]["client_is_talker"] = client->properties()[property::CLIENT_IS_TALKER].as(); result[index]["client_is_priority_speaker"] = client->properties()[property::CLIENT_IS_PRIORITY_SPEAKER].as(); result[index]["client_is_recording"] = client->properties()[property::CLIENT_IS_RECORDING].as(); result[index]["client_is_channel_commander"] = client->properties()[property::CLIENT_IS_CHANNEL_COMMANDER].as(); } index++; }); this->sendCommand(result); return command_result{error::ok}; } command_result ConnectedClient::handleCommandClientGetDBIDfromUID(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; deque unique_ids; for(int index = 0; index < cmd.bulkCount(); index++) unique_ids.push_back(cmd[index]["cluid"].as()); auto res = serverInstance->databaseHelper()->queryDatabaseInfoByUid(this->server, unique_ids); if (res.empty()) return command_result{error::database_empty_result}; Command result(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyclientdbidfromuid" : ""); int result_index = 0; for(auto& info : res) { result[result_index]["cluid"] = info->uniqueId; result[result_index]["cldbid"] = info->cldbid; result_index++; } this->sendCommand(result); return command_result{error::ok}; } command_result ConnectedClient::handleCommandClientGetNameFromDBID(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; deque dbids; for(int index = 0; index < cmd.bulkCount(); index++) dbids.push_back(cmd[index]["cldbid"].as()); auto res = serverInstance->databaseHelper()->queryDatabaseInfo(this->server, dbids); if (res.empty()) return command_result{error::database_empty_result}; Command result(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyclientgetnamefromdbid" : ""); int result_index = 0; for(auto& info : res) { result[result_index]["cluid"] = info->uniqueId; result[result_index]["cldbid"] = info->cldbid; result[result_index]["name"] = info->lastName; result[result_index]["clname"] = info->lastName; result_index++; } this->sendCommand(result); return command_result{error::ok}; } command_result ConnectedClient::handleCommandClientGetNameFromUid(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; deque unique_ids; for(int index = 0; index < cmd.bulkCount(); index++) unique_ids.push_back(cmd[index]["cluid"].as()); auto res = serverInstance->databaseHelper()->queryDatabaseInfoByUid(this->server, unique_ids); if (res.empty()) return command_result{error::database_empty_result}; Command result(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyclientnamefromuid" : ""); int result_index = 0; for(auto& info : res) { result[result_index]["cluid"] = info->uniqueId; result[result_index]["cldbid"] = info->cldbid; result[result_index]["name"] = info->lastName; result[result_index]["clname"] = info->lastName; result_index++; } this->sendCommand(result); return command_result{error::ok}; } command_result ConnectedClient::handleCommandClientGetUidFromClid(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; bool error = false; bool found = false; auto client_list = this->server->getClients(); Command notify(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyclientgetuidfromclid" : ""); int result_index = 0; for(int index = 0; index < cmd.bulkCount(); index++) { auto client_id = cmd[index]["clid"].as(); for(const auto& entry : client_list) { if(entry->getClientId() == client_id) { notify[result_index]["clname"] = entry->getDisplayName(); notify[result_index]["clid"] = entry->getClientId(); notify[result_index]["cluid"] = entry->getUid(); notify[result_index]["cldbid"] = entry->getClientDatabaseId(); result_index++; found = true; } } if(found) found = false; else error = false; } if(result_index > 0) this->sendCommand(notify); if(error) return command_result{error::database_empty_result}; return command_result{error::ok}; } command_result ConnectedClient::handleCommandClientAddPerm(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); auto cldbid = cmd["cldbid"].as(); if(!serverInstance->databaseHelper()->validClientDatabaseId(this->server, cldbid)) return command_result{error::client_invalid_id}; auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cldbid); ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_client_permission_modify_power, this->server->calculate_permission(permission::i_client_needed_permission_modify_power, cldbid, ClientType::CLIENT_TEAMSPEAK, 0)); auto max_value = this->calculate_permission(permission::i_permission_modify_power, 0, true); if(!max_value.has_value) return command_result{permission::i_permission_modify_power}; auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, 0)); bool conOnError = cmd[0].has("continueonerror"); auto update_channels = false; for (int index = 0; index < cmd.bulkCount(); index++) { PARSE_PERMISSION(cmd); auto val = cmd[index]["permvalue"].as(); if(permission_require_granted_value(permType) && !permission::v2::permission_granted(val, max_value)) { if(conOnError) continue; return command_result{permission::i_permission_modify_power}; } if(!ignore_granted_values && !permission::v2::permission_granted(1, this->calculate_permission(permType, 0, true))) { if(conOnError) continue; return command_result{permission::i_permission_modify_power}; } if (grant) { mgr->set_permission(permType, {0, cmd[index]["permvalue"]}, permission::v2::do_nothing, permission::v2::set_value); } else { mgr->set_permission(permType, {cmd[index]["permvalue"], 0}, permission::v2::set_value, permission::v2::do_nothing, cmd[index]["permskip"] ? 1 : 0, cmd[index]["permnegated"] ? 1 : 0); update_channels |= permission_is_client_property(permType); } } serverInstance->databaseHelper()->saveClientPermissions(this->server, cldbid, mgr); auto onlineClients = this->server->findClientsByCldbId(cldbid); if (!onlineClients.empty()) for (const auto &elm : onlineClients) { if(elm->update_cached_permissions()) /* update cached calculated permissions */ elm->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ if(update_channels) elm->updateChannelClientProperties(true, true); elm->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */ } return command_result{error::ok}; } command_result ConnectedClient::handleCommandClientDelPerm(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); auto cldbid = cmd["cldbid"].as(); if(!serverInstance->databaseHelper()->validClientDatabaseId(this->server, cldbid)) return command_result{error::client_invalid_id}; auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cldbid); ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_client_permission_modify_power, this->server->calculate_permission(permission::i_client_needed_permission_modify_power, cldbid, ClientType::CLIENT_TEAMSPEAK, 0)); auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, 0)); bool conOnError = cmd[0].has("continueonerror"); auto onlineClients = this->server->findClientsByCldbId(cmd["cldbid"]); auto update_channel = false; for (int index = 0; index < cmd.bulkCount(); index++) { PARSE_PERMISSION(cmd) if(!ignore_granted_values && !permission::v2::permission_granted(0, this->calculate_permission(permType, 0, true))) { if(conOnError) continue; return command_result{permission::i_permission_modify_power}; } if (grant) { mgr->set_permission(permType, permission::v2::empty_permission_values, permission::v2::do_nothing, permission::v2::delete_value); } else { mgr->set_permission(permType, permission::v2::empty_permission_values, permission::v2::delete_value, permission::v2::do_nothing); update_channel |= permission_is_client_property(permType); } } serverInstance->databaseHelper()->saveClientPermissions(this->server, cldbid, mgr); if (!onlineClients.empty()) for (const auto &elm : onlineClients) { if(elm->update_cached_permissions()) /* update cached calculated permissions */ elm->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ if(update_channel) elm->updateChannelClientProperties(true, true); elm->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */ } return command_result{error::ok}; } command_result ConnectedClient::handleCommandClientPermList(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_client_permission_list, 1); if(!serverInstance->databaseHelper()->validClientDatabaseId(this->server, cmd["cldbid"])) return command_result{error::client_invalid_id}; auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cmd["cldbid"]); if (!this->notifyClientPermList(cmd["cldbid"], mgr, cmd.hasParm("permsid"))) return command_result{error::database_empty_result}; return command_result{error::ok}; } command_result ConnectedClient::handleCommandClientDbInfo(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_client_dbinfo, 1); deque cldbids; for(int index = 0; index < cmd.bulkCount(); index++) cldbids.push_back(cmd[index]["cldbid"]); auto basic = serverInstance->databaseHelper()->queryDatabaseInfo(this->server, cldbids); if (basic.empty()) return command_result{error::database_empty_result}; auto allow_ip = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_remoteaddress_view, 0)); Command res(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifyclientdbinfo" : ""); size_t index = 0; for(const auto& info : basic) { res[index]["client_base64HashClientUID"] = hex::hex(base64::validate(info->uniqueId) ? base64::decode(info->uniqueId) : info->uniqueId, 'a', 'q'); res[index]["client_unique_identifier"] = info->uniqueId; res[index]["client_nickname"] = info->lastName; res[index]["client_database_id"] = info->cldbid; res[index]["client_created"] = chrono::duration_cast(info->created.time_since_epoch()).count(); res[index]["client_lastconnected"] = chrono::duration_cast(info->lastjoin.time_since_epoch()).count(); res[index]["client_totalconnections"] = info->connections; res[index]["client_database_id"] = info->cldbid; auto props = serverInstance->databaseHelper()->loadClientProperties(this->server, info->cldbid, ClientType::CLIENT_TEAMSPEAK); if (allow_ip) res[index]["client_lastip"] = (*props)[property::CONNECTION_CLIENT_IP].as(); else res[index]["client_lastip"] = "hidden"; res[index]["client_icon_id"] = (*props)[property::CLIENT_ICON_ID].as(); res[index]["client_badges"] = (*props)[property::CLIENT_BADGES].as(); res[index]["client_version"] = (*props)[property::CLIENT_VERSION].as(); res[index]["client_platform"] = (*props)[property::CLIENT_PLATFORM].as(); res[index]["client_hwid"] = (*props)[property::CLIENT_HARDWARE_ID].as(); res[index]["client_total_bytes_downloaded"] = (*props)[property::CLIENT_TOTAL_BYTES_DOWNLOADED].as(); res[index]["client_total_bytes_uploaded"] = (*props)[property::CLIENT_TOTAL_BYTES_UPLOADED].as(); res[index]["client_month_bytes_downloaded"] = (*props)[property::CLIENT_MONTH_BYTES_DOWNLOADED].as(); res[index]["client_month_bytes_uploaded"] = (*props)[property::CLIENT_MONTH_BYTES_DOWNLOADED].as(); res[index]["client_description"] = (*props)[property::CLIENT_DESCRIPTION].as(); res[index]["client_flag_avatar"] = (*props)[property::CLIENT_FLAG_AVATAR].as(); res[index]["client_month_online_time"] = (*props)[property::CLIENT_MONTH_ONLINE_TIME].as(); res[index]["client_total_online_time"] = (*props)[property::CLIENT_TOTAL_ONLINE_TIME].as(); index++; } this->sendCommand(res); return command_result{error::ok}; } command_result ConnectedClient::handleCommandClientDBDelete(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_delete_dbproperties, 1); ClientDbId id = cmd["cldbid"]; if (!serverInstance->databaseHelper()->validClientDatabaseId(this->server, id)) return command_result{error::database_empty_result}; serverInstance->databaseHelper()->deleteClient(this->server, id); return command_result{error::ok}; } struct DBFindArgs { int index = 0; bool full = false; bool ip = false; Command cmd{""}; }; command_result ConnectedClient::handleCommandClientDBFind(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_client_dbsearch, 1); bool uid = cmd.hasParm("uid"); string pattern = cmd["pattern"]; DBFindArgs args{}; args.cmd = Command(this->getType() == CLIENT_QUERY ? "" : "notifyclientdbfind"); args.full = cmd.hasParm("details"); args.ip = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_remoteaddress_view, 0)); auto res = sql::command(this->sql, string() + "SELECT * FROM `clients` WHERE `serverId` = :sid AND `" + (uid ? "clientUid" : "lastName") + "` LIKE '" + pattern + "' LIMIT 50", variable{":sid", this->server->getServerId()}).query( [&](DBFindArgs *ptr, int len, char **values, char **names) { for (int index = 0; index < len; index++) if (strcmp(names[index], "cldbid") == 0) ptr->cmd[ptr->index]["cldbid"] = values[index]; else if (strcmp(names[index], "clientUid") == 0 && ptr->full) ptr->cmd[ptr->index]["client_unique_identifier"] = values[index]; else if (strcmp(names[index], "lastConnect") == 0 && ptr->full) ptr->cmd[ptr->index]["client_lastconnected"] = values[index]; else if (strcmp(names[index], "connections") == 0 && ptr->full) ptr->cmd[ptr->index]["client_totalconnections"] = values[index]; else if (strcmp(names[index], "lastName") == 0 && ptr->full) ptr->cmd[ptr->index]["client_nickname"] = values[index]; if (ptr->full) { auto props = serverInstance->databaseHelper()->loadClientProperties(this->server, ptr->cmd[ptr->index]["cldbid"], ClientType::CLIENT_TEAMSPEAK); if (props) { if (ptr->ip) { ptr->cmd[ptr->index]["client_lastip"] = (*props)[property::CONNECTION_CLIENT_IP].as(); } else { ptr->cmd[ptr->index]["client_lastip"] = "hidden"; } ptr->cmd[ptr->index]["client_badges"] = (*props)[property::CLIENT_BADGES].as(); ptr->cmd[ptr->index]["client_version"] = (*props)[property::CLIENT_VERSION].as(); ptr->cmd[ptr->index]["client_platform"] = (*props)[property::CLIENT_PLATFORM].as(); ptr->cmd[ptr->index]["client_hwid"] = (*props)[property::CLIENT_HARDWARE_ID].as(); } } ptr->index++; return 0; }, &args); auto pf = LOG_SQL_CMD; pf(res); if (args.index == 0) return command_result{error::database_empty_result}; this->sendCommand(args.cmd); return command_result{error::ok}; } command_result ConnectedClient::handleCommandClientInfo(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; Command res(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifyclientinfo" : ""); bool trigger_error = false; bool view_remote = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_remoteaddress_view, 0)); int result_index = 0; for(int index = 0; index < cmd.bulkCount(); index++) { auto client_id = cmd[index]["clid"].as(); if(client_id == 0) continue; ConnectedLockedClient client{this->server->find_client_by_id(client_id)}; if(!client) { trigger_error = true; continue; } for (const auto &key : client->properties()->list_properties(property::FLAG_CLIENT_VIEW | property::FLAG_CLIENT_VARIABLE | property::FLAG_CLIENT_INFO, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) res[result_index][key.type().name] = key.value(); if(view_remote) res[result_index]["connection_client_ip"] = client->properties()[property::CONNECTION_CLIENT_IP].as(); else res[result_index]["connection_client_ip"] = "hidden"; res[result_index]["client_idle_time"] = duration_cast(system_clock::now() - client->idleTimestamp).count(); res[result_index]["connection_connected_time"] = duration_cast(system_clock::now() - client->connectTimestamp).count(); { auto channel = client->currentChannel; if(channel) res[result_index]["cid"] = channel->channelId(); else res[result_index]["cid"] = 0; } result_index++; } if(result_index > 0) { this->sendCommand(res); } if(trigger_error || result_index == 0) return command_result{error::client_invalid_id}; else return command_result{error::ok}; } command_result ConnectedClient::handleCommandClientFind(Command &cmd) { CMD_REQ_SERVER; CMD_CHK_AND_INC_FLOOD_POINTS(5); string pattern = cmd["pattern"]; std::transform(pattern.begin(), pattern.end(), pattern.begin(), ::tolower); Command res(""); int index = 0; for (const auto &cl : this->server->getClients()) { string name = cl->getDisplayName(); std::transform(name.begin(), name.end(), name.begin(), ::tolower); if (name.find(pattern) != std::string::npos) { res[index]["clid"] = cl->getClientId(); res[index]["client_nickname"] = cl->getDisplayName(); index++; } } if (index == 0) return command_result{error::database_empty_result}; this->sendCommand(res); return command_result{error::ok}; } command_result ConnectedClient::handleCommandClientSetServerQueryLogin(Command &cmd) { ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_create_modify_serverquery_login, 1); if(!cmd[0].has("client_login_password")) cmd["client_login_password"] = ""; std::string password = cmd["client_login_password"]; if(password.empty()) password = rnd_string(QUERY_PASSWORD_LENGTH); auto old = serverInstance->getQueryServer()->find_query_account_by_name(cmd["client_login_name"]); if (old) { if(old->unique_id == this->getUid()) { serverInstance->getQueryServer()->change_query_password(old, password); } else { return command_result{error::client_not_logged_in}; } } else { serverInstance->getQueryServer()->create_query_account(cmd["client_login_name"], this->getServerId(), this->getUid(), password); } Command res(this->notify_response_command("notifyclientserverqueryloginpassword")); res["client_login_password"] = password; this->sendCommand(res); return command_result{error::ok}; }