diff --git a/git-teaspeak b/git-teaspeak index 4dfe9fb..7a52b2d 160000 --- a/git-teaspeak +++ b/git-teaspeak @@ -1 +1 @@ -Subproject commit 4dfe9fbf023777cbc6dbb278cd1e161fe464edb4 +Subproject commit 7a52b2d2c469ece2b13ea49d017ce2d6989fea00 diff --git a/server/src/TS3ServerClientManager.cpp b/server/src/TS3ServerClientManager.cpp index 8094e1d..ca207d2 100644 --- a/server/src/TS3ServerClientManager.cpp +++ b/server/src/TS3ServerClientManager.cpp @@ -90,12 +90,14 @@ bool VirtualServer::registerClient(shared_ptr client) { } bool VirtualServer::unregisterClient(shared_ptr cl, std::string reason, std::unique_lock& chan_tree_lock) { - if(cl->getType() == ClientType::CLIENT_TEAMSPEAK && cl->getType() == ClientType::CLIENT_WEB) { + if(cl->getType() == ClientType::CLIENT_TEAMSPEAK || cl->getType() == ClientType::CLIENT_TEASPEAK || cl->getType() == ClientType::CLIENT_WEB) { sassert(cl->state == ConnectionState::DISCONNECTED); } auto client_id = cl->getClientId(); - if(client_id == 0) return false; /* not registered */ + if(client_id == 0) { + return false; /* not registered */ + } { lock_guard lock(this->clients.lock); @@ -252,6 +254,10 @@ bool VirtualServer::assignDefaultChannel(const shared_ptr& clie skip_permissions:; } + /* Clear these parameters. We don't need them any more after we initially payed attention. */ + client->properties()[property::CLIENT_DEFAULT_CHANNEL] = ""; + client->properties()[property::CLIENT_DEFAULT_CHANNEL_PASSWORD] = ""; + if(!channel) { /* Client did not propose a channel or the proposed channel got rejected */ channel = this->channelTree->getDefaultChannel(); diff --git a/server/src/TS3ServerHeartbeat.cpp b/server/src/TS3ServerHeartbeat.cpp index 84fc726..55f6149 100644 --- a/server/src/TS3ServerHeartbeat.cpp +++ b/server/src/TS3ServerHeartbeat.cpp @@ -65,7 +65,23 @@ void VirtualServer::executeServerTick() { size_t clientOnline{0}; size_t queryOnline{0}; for(const auto& conn : client_list){ - switch (conn->getType()){ + switch(conn->connectionState()) { + case ConnectionState::CONNECTED: + case ConnectionState::INIT_HIGH: + break; + + case ConnectionState::INIT_LOW: + case ConnectionState::DISCONNECTING: + case ConnectionState::DISCONNECTED: + continue; + + case ConnectionState::UNKNWON: + default: + assert(false); + continue; + } + + switch (conn->getType()) { case ClientType::CLIENT_TEAMSPEAK: case ClientType::CLIENT_TEASPEAK: case ClientType::CLIENT_WEB: diff --git a/server/src/VirtualServer.h b/server/src/VirtualServer.h index 314da57..47ca34a 100644 --- a/server/src/VirtualServer.h +++ b/server/src/VirtualServer.h @@ -237,7 +237,7 @@ namespace ts { bool granted = false ); - bool verifyServerPassword(std::string, bool hashed = false); + [[nodiscard]] bool verifyServerPassword(std::string /* password */, bool /* hashed */); void testBanStateChange(const std::shared_ptr& invoker); diff --git a/server/src/client/ConnectedClient.cpp b/server/src/client/ConnectedClient.cpp index 9e1ef6c..0d5de39 100644 --- a/server/src/client/ConnectedClient.cpp +++ b/server/src/client/ConnectedClient.cpp @@ -847,19 +847,21 @@ void ConnectedClient::sendServerInit() { command["client_integrations"] = this->properties()[property::CLIENT_INTEGRATIONS].value(); if(ts::config::server::DefaultServerLicense == LicenseType::LICENSE_AUTOMATIC_INSTANCE) { - if(serverInstance->getVoiceServerManager()->usedSlots() <= 32) + if(serverInstance->getVoiceServerManager()->usedSlots() <= 32) { command["lt"] = LicenseType::LICENSE_NONE; - else if(serverInstance->getVoiceServerManager()->usedSlots() <= 512) + } else if(serverInstance->getVoiceServerManager()->usedSlots() <= 512) { command["lt"] = LicenseType::LICENSE_NPL; - else + } else { command["lt"] = LicenseType::LICENSE_HOSTING; + } } else if(ts::config::server::DefaultServerLicense == LicenseType::LICENSE_AUTOMATIC_SERVER){ - if(this->server->properties()[property::VIRTUALSERVER_MAXCLIENTS].as_or(0) <= 32) + if(this->server->properties()[property::VIRTUALSERVER_MAXCLIENTS].as_or(0) <= 32) { command["lt"] = LicenseType::LICENSE_NONE; - else if(this->server->properties()[property::VIRTUALSERVER_MAXCLIENTS].as_or(0) <= 512) + } else if(this->server->properties()[property::VIRTUALSERVER_MAXCLIENTS].as_or(0) <= 512) { command["lt"] = LicenseType::LICENSE_NPL; - else + } else { command["lt"] = LicenseType::LICENSE_HOSTING; + } } else { command["lt"] = ts::config::server::DefaultServerLicense; } @@ -954,7 +956,9 @@ bool ConnectedClient::handleCommandFull(Command& cmd, bool disconnectOnFail) { } std::shared_ptr ConnectedClient::resolveActiveBan(const std::string& ip_address) { - if(permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_ignore_bans, 0))) return nullptr; + if(permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_ignore_bans, 0))) { + return nullptr; + } //Check if manager banned auto banManager = serverInstance->banManager(); diff --git a/server/src/client/ConnectedClient.h b/server/src/client/ConnectedClient.h index 5e42d92..f41b4e6 100644 --- a/server/src/client/ConnectedClient.h +++ b/server/src/client/ConnectedClient.h @@ -368,7 +368,8 @@ namespace ts { sockaddr_storage remote_address; //General states - std::mutex state_lock; + /* TODO: Make this a shared lock and lock the client state via holding the lock when executing commands etc */ + std::mutex state_lock{}; ConnectionState state{ConnectionState::UNKNWON}; bool allowedToTalk{false}; diff --git a/server/src/client/SpeakingClient.cpp b/server/src/client/SpeakingClient.cpp index 1c95cc2..39a0a2d 100644 --- a/server/src/client/SpeakingClient.cpp +++ b/server/src/client/SpeakingClient.cpp @@ -17,15 +17,13 @@ #include "./voice/VoiceClient.h" #include "../rtc/imports.h" #include "../groups/GroupManager.h" +#include "../PermissionCalculator.h" using namespace std::chrono; using namespace ts; using namespace ts::server; using namespace ts::protocol; -//#define PKT_LOG_VOICE -//#define PKT_LOG_WHISPER - SpeakingClient::SpeakingClient(sql::SqlManager *a, const std::shared_ptr &b) : ConnectedClient{a, b}, whisper_handler_{this} { speak_begin = std::chrono::system_clock::now(); speak_last_packet = std::chrono::system_clock::now(); @@ -96,11 +94,211 @@ HWID_REGEX(unix, "^[a-z0-9]{32}$"); HWID_REGEX(android, "^[a-z0-9]{16}$"); HWID_REGEX(ios, "^[A-Z0-9]{8}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{12}$"); +command_result SpeakingClient::applyClientInitParameters(Command &cmd) { + for(const auto& key : cmd[0].keys()) { + if(key == "return_code") { + /* That's ok */ + continue; + } else if(key == "client_nickname") { + auto name = cmd[key].string(); + if (count_characters(name) < 3) { + return command_result{error::parameter_invalid, "client_nickname"}; + } + + if (count_characters(name) > 30) { + return command_result{error::parameter_invalid, "client_nickname"}; + } + + /* The unique of the name will be checked when registering the client at the target server */ + this->properties()[property::CLIENT_NICKNAME] = name; + } else if(key == "client_nickname_phonetic") { + auto name = cmd["client_nickname_phonetic"].string(); + if (count_characters(name) > 30) { + return command_result{error::parameter_invalid, "client_nickname_phonetic"}; + } + this->properties()[property::CLIENT_NICKNAME_PHONETIC] = name; + } else if(key == "client_version" || key == "client_platform") { + auto value = cmd[key].string(); + if(value.length() > 64) { + return command_result{error::client_hacked}; + } + + for(auto& character : value) { + if(!isascii(character)) { + logWarning(this->getServerId(), "{} Tried to join within an invalid supplied '{}' ({})", CLIENT_STR_LOG_PREFIX, key,cmd[key].string()); + return command_result{error::client_hacked}; + } + } + + if(key == "client_version") { + this->properties()[property::CLIENT_VERSION] = value; + } else { + this->properties()[property::CLIENT_PLATFORM] = value; + } + } else if(key == "client_version_sign") { + /* We currently don't check this parameter nor we need it later. Don't store it. */ + } else if(key == "hwid") { + auto value = cmd[key].string(); + if(value.length() > 255) { + return command_result{error::parameter_invalid, "hwid"}; + } + this->properties()[property::CLIENT_HARDWARE_ID] = value; + } else if(key == "client_input_muted") { + this->properties()[property::CLIENT_INPUT_MUTED] = cmd[key].as(); + } else if(key == "client_input_hardware") { + this->properties()[property::CLIENT_INPUT_HARDWARE] = cmd[key].as(); + } else if(key == "client_output_hardware") { + this->properties()[property::CLIENT_OUTPUT_HARDWARE] = cmd[key].as(); + } else if(key == "client_output_muted") { + this->properties()[property::CLIENT_OUTPUT_MUTED] = cmd[key].as(); + } else if(key == "client_default_channel") { + auto value = cmd[key].string(); + if(value.length() > 255) { + return command_result{error::parameter_invalid, "client_default_channel"}; + } + this->properties()[property::CLIENT_DEFAULT_CHANNEL] = cmd[key].string(); + } else if(key == "client_default_channel_password") { + auto value = cmd[key].string(); + if(value.length() > 255) { + return command_result{error::parameter_invalid, "client_default_channel_password"}; + } + this->properties()[property::CLIENT_DEFAULT_CHANNEL_PASSWORD] = cmd[key].string(); + } else if(key == "client_away") { + this->properties()[property::CLIENT_AWAY] = cmd[key].as(); + } else if(key == "client_away_message") { + auto value = cmd[key].string(); + if(value.length() > ts::config::server::limits::afk_message_length) { + return command_result{error::parameter_invalid, "client_away_message"}; + } + this->properties()[property::CLIENT_AWAY_MESSAGE] = cmd[key].string(); + } else if(key == "client_badges") { + auto value = cmd[key].string(); + if(value.length() > 255) { + return command_result{error::parameter_invalid, "client_badges"}; + } + this->properties()[property::CLIENT_BADGES] = value; + } else if(key == "client_meta_data") { + auto value = cmd[key].string(); + if(value.length() > 65536) { + return command_result{error::parameter_invalid, "client_meta_data"}; + } + this->properties()[property::CLIENT_META_DATA] = value; + } else if(key == "client_key_offset") { + /* We don't really care about this value */ + } else if(key == "client_server_password") { + /* We don't need to store the password. */ + } else if(key == "client_default_token") { + auto value = cmd[key].string(); + if(value.length() > 255) { + return command_result{error::parameter_invalid, "client_default_token"}; + } + this->properties()[property::CLIENT_DEFAULT_TOKEN] = cmd[key].string(); + } else if(key == "client_myteamspeak_id" || key == "myTeamspeakId") { + auto value = cmd[key].string(); + if(value.length() > 255) { + return command_result{error::parameter_invalid, "client_myteamspeak_id"}; + } + this->properties()[property::CLIENT_MYTEAMSPEAK_ID] = cmd[key].string(); + } else if(key == "acTime" || key == "userPubKey" || key == "authSign" || key == "pubSign" || key == "pubSignCert") { + /* Used for the MyTeamSpeak services. We don't store them. */ + } else if(key == "client_integrations") { + auto value = cmd[key].string(); + if(value.length() > 255) { + return command_result{error::parameter_invalid, "client_integrations"}; + } + this->properties()[property::CLIENT_INTEGRATIONS] = cmd[key].string(); + } else if(key == "client_active_integrations_info") { + auto value = cmd[key].string(); + if(value.length() > 255) { + return command_result{error::parameter_invalid, "client_active_integrations_info"}; + } + this->properties()[property::CLIENT_ACTIVE_INTEGRATIONS_INFO] = cmd[key].string(); + } else if(key == "client_browser_engine") { + /* Currently not really used but passed by the web client */ + } else { + debugMessage(this->getServerId(), "{} Received unknown clientinit parameter {}. Ignoring it.", this->getLoggingPrefix(), key); + } + } + + return ts::command_result{error::ok}; +} + +command_result SpeakingClient::resolveClientInitBan() { + auto active_ban = this->resolveActiveBan(this->getPeerIp()); + if(!active_ban) { + return ts::command_result{error::ok}; + } + + logMessage(this->getServerId(), "{} Disconnecting while init because of ban record. Record id {} at server {}", + CLIENT_STR_LOG_PREFIX, + active_ban->banId, + active_ban->serverId); + serverInstance->banManager()->trigger_ban(active_ban, this->getServerId(), this->getUid(), this->getHardwareId(), this->getDisplayName(), this->getPeerIp()); + + string fullReason = string() + "You are banned " + (active_ban->serverId == 0 ? "globally" : "from this server") + ". Reason: \"" + active_ban->reason + "\". Ban expires "; + + string time; + if(active_ban->until.time_since_epoch().count() != 0) { + time += "in "; + auto seconds = chrono::ceil(active_ban->until - chrono::system_clock::now()).count(); + tm p{}; + memset(&p, 0, sizeof(p)); + + while(seconds >= 365 * 24 * 60 * 60){ + p.tm_year++; + seconds -= 365 * 24 * 60 * 60; + } + + while(seconds >= 24 * 60 * 60){ + p.tm_yday++; + seconds -= 24 * 60 * 60; + } + + while(seconds >= 60 * 60){ + p.tm_hour++; + seconds -= 60 * 60; + } + + while(seconds >= 60){ + p.tm_min++; + seconds -= 60; + } + p.tm_sec = (int) seconds; + + if(p.tm_year > 0) { + time += to_string(p.tm_year) + " years, "; + } + + if(p.tm_yday > 0) { + time += to_string(p.tm_yday) + " days, "; + } + + if(p.tm_hour > 0) { + time += to_string(p.tm_hour) + " hours, "; + } + + if(p.tm_min > 0) { + time += to_string(p.tm_min) + " minutes, "; + } + + if(p.tm_sec > 0) { + time += to_string(p.tm_sec) + " seconds, "; + } + + if(time.empty()) time = "now, "; + time = time.substr(0, time.length() - 2); + } else time = "never"; + fullReason += time + "!"; + + return command_result{error::server_connect_banned, fullReason}; +} + command_result SpeakingClient::handleCommandClientInit(Command& cmd) { TIMING_START(timings); + /* Firstly check if the client tries to join flood */ { - lock_guard lock(this->server->join_attempts_lock); + lock_guard lock{this->server->join_attempts_lock}; auto client_address = this->getPeerIp(); auto& client_join_attempts = this->server->join_attempts[client_address]; auto& general_join_attempts = this->server->join_attempts["_"]; @@ -116,320 +314,231 @@ command_result SpeakingClient::handleCommandClientInit(Command& cmd) { client_join_attempts++; general_join_attempts++; } - TIMING_STEP(timings, "join atmp c"); + + /* Check if we've enabled the modal quit modal. If so just deny the join in general */ + { + auto host_message_mode = this->server->properties()[property::VIRTUALSERVER_HOSTMESSAGE_MODE].as_or(0); + auto quit_message = this->server->properties()[property::VIRTUALSERVER_HOSTMESSAGE].value(); + + if(host_message_mode == 3) { + return ts::command_result{error::server_modal_quit, quit_message}; + } + } + + TIMING_STEP(timings, "join limit check"); if(!DatabaseHelper::assignDatabaseId(this->server->getSql(), this->server->getServerId(), this->ref())) { return command_result{error::vs_critical, "Could not assign database id!"}; } - TIMING_STEP(timings, "db assign "); + { + auto result{this->applyClientInitParameters(cmd)}; + if(result.has_error()) { + return result; + } + result.release_data(); + } - const static vector available_parameters = { - "client_nickname", - "client_version", - "client_platform", - "client_input_muted", - "client_input_hardware", - "client_output_hardware", - "client_output_muted", - "client_default_channel", - "client_default_channel_password", - "client_server_password", - "client_meta_data", - "client_version_sign", - "client_key_offset", - "client_nickname_phonetic", - "client_default_token", - "client_badges=badges", - "client_badges", - "client_myteamspeak_id", - "client_integrations", - "client_active_integrations_info", - "client_browser_engine", + TIMING_STEP(timings, "state load (db) "); - "client_away", - "client_away_message", + ClientPermissionCalculator permission_calculator{this, nullptr}; - "hwid", - "myTeamspeakId", - "acTime", - "userPubKey", - "authSign", - "pubSign", - "pubSignCert" - }; + /* Check if the target client ip address has been denied */ + if(geoloc::provider && !permission_calculator.permission_granted(permission::b_client_ignore_vpn, 1)) { + auto provider = this->isAddressV4() ? geoloc::provider_vpn->resolveInfoV4(this->getPeerIp(), true) : geoloc::provider_vpn->resolveInfoV6(this->getPeerIp(), true); + if(provider) { + auto message = strvar::transform(ts::config::messages::kick_vpn, strvar::StringValue{"provider.name", provider->name}, strvar::StringValue{"provider.website", provider->side}); + return command_result{error::server_connect_banned, message}; + } - for(const auto& key : cmd[0].keys()) { - if(key == "return_code") continue; - bool parm_allowed = false; - for(const auto& _allowed_key : available_parameters) { - if(_allowed_key == key) { - parm_allowed = true; - break; + TIMING_STEP(timings, "VPN block test "); + } + + /* + * Check if the supplied client hardware id is correct (only applies for TeamSpeak 3 clients) + * This has been created due to Bluescrems request but I think we might want to drop this. + */ + if(this->getType() == ClientType::CLIENT_TEAMSPEAK) { + if(permission_calculator.permission_granted(permission::b_client_enforce_valid_hwid, 1)) { + auto hardware_id = this->properties()[property::CLIENT_HARDWARE_ID].value(); + if( + !std::regex_match(hardware_id, regex_hwid_windows) && + !std::regex_match(hardware_id, regex_hwid_unix) && + !std::regex_match(hardware_id, regex_hwid_android) && + !std::regex_match(hardware_id, regex_hwid_ios) + ) { + return command_result{error::parameter_invalid, config::messages::kick_invalid_hardware_id}; } } - if(!parm_allowed) { - debugMessage(this->getServerId(), "{} Tried to insert a not allowed parameter within clientinit (Key: {}, Value: {})", CLIENT_STR_LOG_PREFIX, key,cmd[key].string()); - continue; - } - - - if(key == "myTeamspeakId") { - this->properties()[property::CLIENT_MYTEAMSPEAK_ID] = cmd[key].string(); - continue; - } else if(key == "acTime") continue; - else if(key == "userPubKey") continue; - else if(key == "authSign") continue; - else if(key == "pubSign") continue; - else if(key == "pubSignCert") continue; - else if(key == "client_version" || key == "client_platform") { - for(auto& character : cmd[key].string()) - if(!isascii(character)) { - logWarning(this->getServerId(), "{} Tried to join within an invalid supplied '{}' ({})", CLIENT_STR_LOG_PREFIX, key,cmd[key].string()); - return command_result{error::client_hacked}; - } - } else if(key == "client_talk_request_msg") { - if(cmd["client_talk_request_msg"].string().length() > ts::config::server::limits::talk_power_request_message_length) - return command_result{error::parameter_invalid_size, "client_talk_request_msg"}; - } else if(key == "client_away_message") { - if(cmd["client_away_message"].string().length() > ts::config::server::limits::afk_message_length) - return command_result{error::parameter_invalid_size, "client_away_message"}; - } else if(key == "client_nickname_phonetic") { - auto name = cmd["client_nickname_phonetic"].string(); - if (count_characters(name) > 30) return command_result{error::parameter_invalid, "client_nickname_phonetic"}; - } else if(key == "client_nickname") { - auto name = cmd["client_nickname"].string(); - if (count_characters(name) < 3) return command_result{error::parameter_invalid, "client_nickname"}; - if (count_characters(name) > 30) return command_result{error::parameter_invalid, "client_nickname"}; - } - - const auto &info = property::find(key); - if(info.is_undefined()) { - logError(this->getServerId(), "{} Tried to pass a unknown value {}. Please report this, if you're sure that this key should be known!", CLIENT_STR_LOG_PREFIX, key); - continue; - //return {findError("parameter_invalid"), "Unknown property " + key}; - } - if(!info.validate_input(cmd[key].as())) - return command_result{error::parameter_invalid}; - - this->properties()[info] = cmd[key].as(); - } - debugMessage(this->getServerId(), "{} Got client init. (HWID: {})", CLIENT_STR_LOG_PREFIX, this->getHardwareId()); - TIMING_STEP(timings, "props apply"); - - auto permissions_list = this->calculate_permissions({ - permission::b_virtualserver_join_ignore_password, - permission::b_client_ignore_bans, - permission::b_client_ignore_vpn, - - permission::i_client_max_clones_uid, - permission::i_client_max_clones_ip, - permission::i_client_max_clones_hwid, - - permission::b_client_enforce_valid_hwid, - - permission::b_client_use_reserved_slot - }, 0); - auto permissions = map(permissions_list.begin(), permissions_list.end()); - TIMING_STEP(timings, "perm calc 1"); - - if(geoloc::provider_vpn && !permission::v2::permission_granted(1, permissions[permission::b_client_ignore_vpn])) { - auto provider = this->isAddressV4() ? geoloc::provider_vpn->resolveInfoV4(this->getPeerIp(), true) : geoloc::provider_vpn->resolveInfoV6(this->getPeerIp(), true); - if(provider) - return command_result{error::server_connect_banned, strvar::transform(ts::config::messages::kick_vpn, strvar::StringValue{"provider.name", provider->name}, strvar::StringValue{"provider.website", provider->side})}; + TIMING_STEP(timings, "hwid check "); } - if(this->getType() == ClientType::CLIENT_TEAMSPEAK && permission::v2::permission_granted(1, permissions[permission::b_client_enforce_valid_hwid])) { - auto hwid = this->properties()[property::CLIENT_HARDWARE_ID].value(); - if( - !std::regex_match(hwid, regex_hwid_windows) && - !std::regex_match(hwid, regex_hwid_unix) && - !std::regex_match(hwid, regex_hwid_android) && - !std::regex_match(hwid, regex_hwid_ios) - ) { - return command_result{error::parameter_invalid, config::messages::kick_invalid_hardware_id}; - } - } - TIMING_STEP(timings, "valid hw ip"); - - if(!permission::v2::permission_granted(1, permissions[permission::b_virtualserver_join_ignore_password])) { - if(!this->server->verifyServerPassword(cmd["client_server_password"].string(), true)) { + /* Check if the client has supplied a valid password or permissions to ignore it */ + if(!this->server->verifyServerPassword(cmd["client_server_password"].string(), true)) { + if(!permission_calculator.permission_granted(permission::b_virtualserver_join_ignore_password, 1)) { return command_result{error::server_invalid_password}; } } + TIMING_STEP(timings, "server password "); + /* Maximal client connected clones */ if(!config::server::clients::ignore_max_clone_permissions) { - size_t clones_uid = 0; - size_t clones_ip = 0; - size_t clones_hwid = 0; + size_t clones_hardware_id{0}; + size_t clones_unique_id{0}; + size_t clones_ip{0}; + + auto own_hardware_id = this->getHardwareId(); + auto own_unique_id = this->getUid(); + auto own_ip = this->getPeerIp(); - auto _own_hwid = this->getHardwareId(); this->server->forEachClient([&](const shared_ptr& client) { - if(client->getExternalType() != CLIENT_TEAMSPEAK) return; - if(client->getUid() == this->getUid()) - clones_uid++; - if(client->getPeerIp() == this->getPeerIp()) + if(&*client == this) { + return; + } + + switch(client->getType()) { + case ClientType::CLIENT_TEASPEAK: + case ClientType::CLIENT_TEAMSPEAK: + case ClientType::CLIENT_WEB: + break; + + case ClientType::CLIENT_MUSIC: + case ClientType::CLIENT_INTERNAL: + case ClientType::CLIENT_QUERY: + return; + + case ClientType::MAX: + default: + assert(false); + return; + } + + if(client->getUid() == this->getUid()) { + clones_unique_id++; + } + + if(client->getPeerIp() == this->getPeerIp()) { clones_ip++; - if(!_own_hwid.empty() && client->getHardwareId() == _own_hwid) - clones_hwid++; + } + + if(client->getHardwareId() == own_hardware_id) { + clones_hardware_id++; + } }); - if(clones_uid > 0 && permissions[permission::i_client_max_clones_uid].has_value && !permission::v2::permission_granted(clones_uid, permissions[permission::i_client_max_clones_uid])) { - logMessage(this->getServerId(), "{} Disconnecting because there are already {} uid clones connected. (Allowed: {})", CLIENT_STR_LOG_PREFIX, clones_uid, permissions[permission::i_client_max_clones_uid].value); - return command_result{error:: client_too_many_clones_connected, "too many clones connected (uid)"}; + if(clones_unique_id) { + auto max_clones = permission_calculator.calculate_permission(permission::i_client_max_clones_uid); + if(max_clones.has_value && !permission::v2::permission_granted(clones_unique_id, max_clones)) { + return command_result{error:: client_too_many_clones_connected, "too many clones connected (unique id)"}; + } } - if(clones_ip > 0 && permissions[permission::i_client_max_clones_ip].has_value && !permission::v2::permission_granted(clones_ip, permissions[permission::i_client_max_clones_ip])) { - logMessage(this->getServerId(), "{} Disconnecting because there are already {} ip clones connected. (Allowed: {})", CLIENT_STR_LOG_PREFIX, clones_ip, permissions[permission::i_client_max_clones_ip].value); - return command_result{error:: client_too_many_clones_connected, "too many clones connected (ip)"}; + if(clones_ip) { + auto max_clones = permission_calculator.calculate_permission(permission::i_client_max_clones_ip); + if(max_clones.has_value && !permission::v2::permission_granted(clones_ip, max_clones)) { + return command_result{error:: client_too_many_clones_connected, "too many clones connected (ip)"}; + } } - if(clones_hwid > 0 && permissions[permission::i_client_max_clones_hwid].has_value && !permission::v2::permission_granted(clones_hwid, permissions[permission::i_client_max_clones_hwid])) { - logMessage(this->getServerId(), "{} Disconnecting because there are already {} hwid clones connected. (Allowed: {})", CLIENT_STR_LOG_PREFIX, clones_hwid, permissions[permission::i_client_max_clones_hwid].value); - return command_result{error:: client_too_many_clones_connected, "too many clones connected (hwid)"}; - } - TIMING_STEP(timings, "max clones "); - } - - auto banEntry = this->resolveActiveBan(this->getPeerIp()); - if(banEntry) { - logMessage(this->getServerId(), "{} Disconnecting while init because of ban record. Record id {} at server {}", - CLIENT_STR_LOG_PREFIX, - banEntry->banId, - banEntry->serverId); - serverInstance->banManager()->trigger_ban(banEntry, this->getServerId(), this->getUid(), this->getHardwareId(), this->getDisplayName(), this->getPeerIp()); - - string fullReason = string() + "You are banned " + (banEntry->serverId == 0 ? "globally" : "from this server") + ". Reason: \"" + banEntry->reason + "\". Ban expires "; - - string time; - if(banEntry->until.time_since_epoch().count() != 0) { - time += "in "; - auto seconds = chrono::ceil(banEntry->until - chrono::system_clock::now()).count(); - tm p{}; - memset(&p, 0, sizeof(p)); - - while(seconds >= 365 * 24 * 60 * 60){ - p.tm_year++; - seconds -= 365 * 24 * 60 * 60; - } - - while(seconds >= 24 * 60 * 60){ - p.tm_yday++; - seconds -= 24 * 60 * 60; - } - - while(seconds >= 60 * 60){ - p.tm_hour++; - seconds -= 60 * 60; - } - - while(seconds >= 60){ - p.tm_min++; - seconds -= 60; - } - p.tm_sec = (int) seconds; - - if(p.tm_year > 0) { - time += to_string(p.tm_year) + " years, "; - } - - if(p.tm_yday > 0) { - time += to_string(p.tm_yday) + " days, "; - } - - if(p.tm_hour > 0) { - time += to_string(p.tm_hour) + " hours, "; - } - - if(p.tm_min > 0) { - time += to_string(p.tm_min) + " minutes, "; - } - - if(p.tm_sec > 0) { - time += to_string(p.tm_sec) + " seconds, "; - } - - if(time.empty()) time = "now, "; - time = time.substr(0, time.length() - 2); - } else time = "never"; - fullReason += time + "!"; - - return command_result{error::server_connect_banned, fullReason}; - } - TIMING_STEP(timings, "ban resolve"); - - size_t count = 0; - for(const auto &cl : this->server->getClients()) { - if((cl->getType() == CLIENT_TEAMSPEAK || cl->getType() == CLIENT_WEB || cl->getType() == CLIENT_TEASPEAK || cl->getType() == CLIENT_MUSIC)) { - if(cl->connectionState() <= ConnectionState::CONNECTED && cl->connectionState() >= ConnectionState::INIT_HIGH) { - count++; + /* If we have no hardware id don't count any clones */ + if(clones_hardware_id && !own_hardware_id.empty()) { + auto max_clones = permission_calculator.calculate_permission(permission::i_client_max_clones_hwid); + if(max_clones.has_value && !permission::v2::permission_granted(clones_hardware_id, max_clones)) { + return command_result{error:: client_too_many_clones_connected, "too many clones connected (hardware id)"}; } } } + TIMING_STEP(timings, "max client clone"); - auto maxClients = this->server->properties()[property::VIRTUALSERVER_MAXCLIENTS].as_or(0); - auto reserved = this->server->properties()[property::VIRTUALSERVER_RESERVED_SLOTS].as_or(0); - - bool allowReserved = permission::v2::permission_granted(1, permissions[permission::b_client_use_reserved_slot]); - if(reserved > maxClients){ - if(!allowReserved) { - return command_result{error::server_maxclients_reached}; + /* Resolve active bans and deny the connect */ + if(!permission_calculator.permission_granted(permission::b_client_ignore_bans, 1)) { + auto result{this->resolveClientInitBan()}; + if(result.has_error()) { + return result; } - } else if(maxClients - (allowReserved ? 0 : reserved) <= count) { - return command_result{error::server_maxclients_reached}; + result.release_data(); } - TIMING_STEP(timings, "max clients"); + TIMING_STEP(timings, "active ban test "); - - auto old_last_connected = this->properties()[property::CLIENT_LASTCONNECTED].as_or(0); - serverInstance->databaseHelper()->updateClientIpAddress(this->getServerId(), this->getClientDatabaseId(), this->getLoggingPeerIp()); - this->properties()[property::CLIENT_LASTCONNECTED] = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); - this->properties()[property::CLIENT_TOTALCONNECTIONS].increment_by(1); + /* Check if the server might be full */ { + size_t online_client_count{0}; + for(const auto &cl : this->server->getClients()) { + switch(cl->getType()) { + case ClientType::CLIENT_TEASPEAK: + case ClientType::CLIENT_TEAMSPEAK: + case ClientType::CLIENT_WEB: + switch(cl->connectionState()) { + case ConnectionState::CONNECTED: + case ConnectionState::INIT_HIGH: + online_client_count++; + break; + + case ConnectionState::INIT_LOW: + case ConnectionState::DISCONNECTING: + case ConnectionState::DISCONNECTED: + break; + + case ConnectionState::UNKNWON: + default: + assert(false); + continue; + } + break; + + case ClientType::CLIENT_MUSIC: + case ClientType::CLIENT_INTERNAL: + case ClientType::CLIENT_QUERY: + break; + + case ClientType::MAX: + default: + assert(false); + break; + } + } + + auto server_client_limit = this->server->properties()[property::VIRTUALSERVER_MAXCLIENTS].as_or(0); + auto server_reserved_slots = this->server->properties()[property::VIRTUALSERVER_RESERVED_SLOTS].as_or(0); + + auto normal_slots = server_reserved_slots >= server_client_limit ? 0 : server_client_limit - server_reserved_slots; + if(normal_slots <= online_client_count) { + if(server_client_limit <= online_client_count || !permission_calculator.permission_granted(permission::b_client_use_reserved_slot, 1)) { + return command_result{error::server_maxclients_reached}; + } + } + } + TIMING_STEP(timings, "server slot test"); + + + /* Update the last connected and total connected statistics and send a permission list update if the server has been updated */ + { + auto old_last_connected = this->properties()[property::CLIENT_LASTCONNECTED].as_or(0); + this->properties()[property::CLIENT_LASTCONNECTED] = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + this->properties()[property::CLIENT_TOTALCONNECTIONS].increment_by(1); + auto time_point = system_clock::time_point() + seconds(old_last_connected); if(time_point < build::version()->timestamp) { logMessage(this->getServerId(), "{} Client may cached a old permission list (Server is newer than the client's last join)", CLIENT_STR_LOG_PREFIX); - TIMING_STEP(timings, "pre dummy p"); Command _dummy("dummy_permissionslist"); this->handleCommandPermissionList(_dummy); - TIMING_STEP(timings, "pst dummy p"); } } - this->postCommandHandler.emplace_back([&](){ - auto self = dynamic_pointer_cast(this->ref()); - std::thread([self](){ - if(self->state != ConnectionState::INIT_HIGH) return; - try { - self->processJoin(); - } catch (std::exception& ex) { - logError(self->getServerId(), "Failed to proceed client join for {}. Got exception with message {}", CLIENT_STR_LOG_PREFIX_(self), ex.what()); - self->close_connection(chrono::system_clock::now() + chrono::seconds{5}); - } - }).detach(); - }); + /* Update the clients IP address within the database */ + serverInstance->databaseHelper()->updateClientIpAddress(this->getServerId(), this->getClientDatabaseId(), this->getLoggingPeerIp()); + TIMING_STEP(timings, "con stats update"); + + this->processJoin(); + TIMING_STEP(timings, "join client "); debugMessage(this->getServerId(), "{} Client init timings: {}", CLIENT_STR_LOG_PREFIX, TIMING_FINISH(timings)); return command_result{error::ok}; } -/* must be triggered while helding an execute lock */ void SpeakingClient::processJoin() { TIMING_START(timings); - this->resetIdleTime(); - - /* don't process any commands */ - std::lock_guard command_lock_{this->command_lock}; - auto ref_server = this->server; - assert(ref_server); - - if(this->state != ConnectionState::INIT_HIGH) { - logError(this->getServerId(), "{} Invalid processJoin() connection state!", CLIENT_STR_LOG_PREFIX); - return; - } - - TIMING_STEP(timings, "setup "); - ref_server->registerClient(this->ref()); + this->server->registerClient(this->ref()); { if(this->rtc_client_id) { /* in case of client reconnect */ @@ -465,17 +574,6 @@ void SpeakingClient::processJoin() { debugMessage(this->getServerId(), "{} Could not resolve IP info for {}", this->getLoggingPrefix(), this->getLoggingPeerIp()); } } - //this->updateChannelClientProperties(false); /* will be already updated via assignChannel */ - if(ref_server->properties()[property::VIRTUALSERVER_HOSTMESSAGE_MODE].as_or(0) == 3 && !ref_server->properties()[property::VIRTUALSERVER_HOSTMESSAGE].value().empty()) { - auto weak = this->_this; - threads::Thread([weak](){ - threads::self::sleep_for(milliseconds(2000)); - auto client = weak.lock(); - if(!client || !client->server) return; - - client->disconnect(client->server->properties()[property::VIRTUALSERVER_HOSTMESSAGE].value()); - }).detach(); - } TIMING_STEP(timings, "ip 2 loc as"); //IP to location assignment this->sendServerInit(); @@ -483,7 +581,7 @@ void SpeakingClient::processJoin() { TIMING_STEP(timings, "notify sini"); if(auto token{this->properties()[property::CLIENT_DEFAULT_TOKEN].value()}; !token.empty()){ - auto token_info = ref_server->getTokenManager().load_token(token, true); + auto token_info = this->server->getTokenManager().load_token(token, true); if(token_info) { if(token_info->is_expired()) { debugMessage(this->getServerId(), "{} Client tried to use an expired token {}", this->getLoginName(), token); @@ -491,17 +589,20 @@ void SpeakingClient::processJoin() { debugMessage(this->getServerId(), "{} Client tried to use an token which reached the use limit {}", this->getLoginName(), token); } else { debugMessage(this->getServerId(), "{} Client used token {}", this->getLoginName(), token); - ref_server->getTokenManager().log_token_use(token_info->id); + this->server->getTokenManager().log_token_use(token_info->id); this->useToken(token_info->id); } } else { debugMessage(this->getServerId(), "{} Client tried to use an unknown token {}", token); } + + /* Clear out the value. We don't need the default token any more */ + this->properties()[property::CLIENT_DEFAULT_TOKEN] = ""; } TIMING_STEP(timings, "token use "); - if(!ref_server->assignDefaultChannel(this->ref(), false)) { + if(!this->server->assignDefaultChannel(this->ref(), false)) { auto result = command_result{error::vs_critical, "Could not assign default channel!"}; this->notifyError(result); result.release_data(); @@ -583,12 +684,18 @@ void SpeakingClient::processLeave() { auto ownLock = this->ref(); auto server = this->getServer(); + auto channel = this->currentChannel; - if(server){ + if(server) { + if(this->rtc_client_id) { + server->rtc_server().destroy_client(this->rtc_client_id); + this->rtc_client_id = 0; + } + logMessage(this->getServerId(), "Voice client {}/{} ({}) from {} left.", this->getClientDatabaseId(), this->getUid(), this->getDisplayName(), this->getLoggingPeerIp() + ":" + to_string(this->getPeerPort())); { - unique_lock server_channel_lock(this->server->channel_tree_mutex); - server->unregisterClient(ownLock, "disconnected", server_channel_lock); /* already moves client to void if needed */ + std::unique_lock server_channel_lock(server->channel_tree_mutex); + server->unregisterClient(ownLock, "disconnected", server_channel_lock); } server->music_manager_->cleanup_client_bots(this->getClientDatabaseId()); //ref_server = nullptr; Removed caused nullptr exceptions diff --git a/server/src/client/SpeakingClient.h b/server/src/client/SpeakingClient.h index 03c4714..08cf77c 100644 --- a/server/src/client/SpeakingClient.h +++ b/server/src/client/SpeakingClient.h @@ -78,6 +78,10 @@ namespace ts::server { virtual command_result handleCommandBroadcastVideoConfig(Command &); virtual command_result handleCommandBroadcastVideoConfigure(Command &); + /* clientinit method helpers */ + command_result applyClientInitParameters(Command&); + command_result resolveClientInitBan(); + void triggerVoiceEnd(); void updateSpeak(bool onlyUpdate, const std::chrono::system_clock::time_point &time); std::chrono::milliseconds speak_accuracy{1000}; diff --git a/server/src/client/command_handler/channel.cpp b/server/src/client/command_handler/channel.cpp index 454b3fa..4426b4c 100644 --- a/server/src/client/command_handler/channel.cpp +++ b/server/src/client/command_handler/channel.cpp @@ -2004,19 +2004,15 @@ command_result ConnectedClient::handleCommandChannelClientDelPerm(Command &cmd) 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::parameter_invalid, "Invalid manager db id"}; - RESOLVE_CHANNEL_R(cmd["cid"], true); auto channel = dynamic_pointer_cast(l_channel->entry); if (!channel) return command_result{error::vs_critical}; - auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->getServerId(), cldbid); - { - auto required_permissions = this->server->calculate_permission(permission::i_client_needed_permission_modify_power, cmd["cldbid"], ClientType::CLIENT_TEAMSPEAK, channel_id); - ACTION_REQUIRES_PERMISSION(permission::i_client_permission_modify_power, required_permissions, channel_id); - } + auto target_client_database_id = cmd["cldbid"].as(); + ClientPermissionCalculator target_client_permission_calculator{this->server, target_client_database_id, ClientType::CLIENT_TEAMSPEAK, channel_id}; + ACTION_REQUIRES_PERMISSION(permission::i_client_permission_modify_power, target_client_permission_calculator.calculate_permission(permission::i_client_needed_permission_modify_power), channel_id); + + auto target_permission_manager = serverInstance->databaseHelper()->loadClientPermissionManager(this->getServerId(), target_client_database_id); ts::command::bulk_parser::PermissionBulksParser pparser{cmd, false}; if (!pparser.validate(this->ref(), channel->channelId())) @@ -2024,20 +2020,20 @@ command_result ConnectedClient::handleCommandChannelClientDelPerm(Command &cmd) bool update_view{false}; for (const auto &ppermission : pparser.iterate_valid_permissions()) { - ppermission.apply_to_channel(mgr, permission::v2::PermissionUpdateType::delete_value, channel->channelId()); + ppermission.apply_to_channel(target_permission_manager, permission::v2::PermissionUpdateType::delete_value, channel->channelId()); ppermission.log_update(serverInstance->action_logger()->permission_logger, this->getServerId(), this->ref(), log::PermissionTarget::CLIENT_CHANNEL, permission::v2::PermissionUpdateType::delete_value, - cldbid, "", + target_client_database_id, "", channel->channelId(), channel->name()); update_view |= ppermission.is_client_view_property(); } - serverInstance->databaseHelper()->saveClientPermissions(this->server, cldbid, mgr); + serverInstance->databaseHelper()->saveClientPermissions(this->server, target_client_database_id, target_permission_manager); - auto onlineClients = this->server->findClientsByCldbId(cldbid); + auto onlineClients = this->server->findClientsByCldbId(target_client_database_id); if (!onlineClients.empty()) { for (const auto &elm : onlineClients) { elm->task_update_needed_permissions.enqueue(); @@ -2073,17 +2069,16 @@ command_result ConnectedClient::handleCommandChannelClientAddPerm(Command &cmd) 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::parameter_invalid, "Invalid client db id"}; RESOLVE_CHANNEL_R(cmd["cid"], true); auto channel = dynamic_pointer_cast(l_channel->entry); if (!channel) return command_result{error::vs_critical}; - auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->getServerId(), cldbid); - auto required_permissions = this->server->calculate_permission(permission::i_client_needed_permission_modify_power, cmd["cldbid"], ClientType::CLIENT_TEAMSPEAK, channel_id); - ACTION_REQUIRES_PERMISSION(permission::i_client_permission_modify_power, required_permissions, channel_id); + auto target_client_database_id = cmd["cldbid"].as(); + ClientPermissionCalculator target_client_permission_calculator{this->server, cmd["cldbid"], ClientType::CLIENT_TEAMSPEAK, channel_id}; + ACTION_REQUIRES_PERMISSION(permission::i_client_permission_modify_power, target_client_permission_calculator.calculate_permission(permission::i_client_needed_permission_modify_power), channel_id); + + auto target_permissions = serverInstance->databaseHelper()->loadClientPermissionManager(this->getServerId(), target_client_database_id); ts::command::bulk_parser::PermissionBulksParser pparser{cmd, true}; if (!pparser.validate(this->ref(), channel->channelId())) @@ -2091,21 +2086,21 @@ command_result ConnectedClient::handleCommandChannelClientAddPerm(Command &cmd) bool update_view{false}; for (const auto &ppermission : pparser.iterate_valid_permissions()) { - ppermission.apply_to_channel(mgr, permission::v2::PermissionUpdateType::set_value, channel->channelId()); + ppermission.apply_to_channel(target_permissions, permission::v2::PermissionUpdateType::set_value, channel->channelId()); ppermission.log_update(serverInstance->action_logger()->permission_logger, this->getServerId(), this->ref(), log::PermissionTarget::CLIENT_CHANNEL, permission::v2::PermissionUpdateType::set_value, - cldbid, "", + target_client_database_id, "", channel->channelId(), channel->name()); update_view |= ppermission.is_client_view_property(); } - serverInstance->databaseHelper()->saveClientPermissions(this->server, cldbid, mgr); + serverInstance->databaseHelper()->saveClientPermissions(this->server, target_client_database_id, target_permissions); - auto onlineClients = this->server->findClientsByCldbId(cldbid); + auto onlineClients = this->server->findClientsByCldbId(target_client_database_id); if (!onlineClients.empty()) for (const auto &elm : onlineClients) { elm->task_update_needed_permissions.enqueue(); diff --git a/server/src/client/command_handler/client.cpp b/server/src/client/command_handler/client.cpp index de25f49..014fbc3 100644 --- a/server/src/client/command_handler/client.cpp +++ b/server/src/client/command_handler/client.cpp @@ -495,8 +495,13 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std: unique_ptr> nickname_lock; std::deque> keys; for(const auto& key : cmd[0].keys()) { - if(key == "return_code") continue; - if(key == "clid") continue; + if(key == "return_code") { + continue; + } + + if(key == "clid") { + continue; + } const auto &info = property::find(key); if(info == property::CLIENT_UNDEFINED) { diff --git a/server/src/client/command_handler/groups.cpp b/server/src/client/command_handler/groups.cpp index 927d690..bba4650 100644 --- a/server/src/client/command_handler/groups.cpp +++ b/server/src/client/command_handler/groups.cpp @@ -705,6 +705,10 @@ command_result ConnectedClient::handleCommandGroupDel(Command &cmd, GroupTarget result = group_manager->channel_groups()->delete_group(group->group_id()); break; + + default: + assert(false); + return ts::command_result{error::vs_critical}; } switch(result) { diff --git a/server/src/client/voice/CryptSetupHandler.cpp b/server/src/client/voice/CryptSetupHandler.cpp index 076ee77..44e24c7 100644 --- a/server/src/client/voice/CryptSetupHandler.cpp +++ b/server/src/client/voice/CryptSetupHandler.cpp @@ -73,50 +73,29 @@ CryptSetupHandler::CommandResult CryptSetupHandler::handleCommandClientInitIv(co auto client = this->connection->getCurrentClient(); assert(client); - std::unique_lock state_lock{client->state_lock}; - if(client->connectionState() == ConnectionState::CONNECTED) { /* we've a reconnect */ - auto lastPingResponse = std::max(this->connection->ping_handler().last_ping_response(), this->connection->ping_handler().last_command_acknowledged()); - if(std::chrono::system_clock::now() - lastPingResponse < std::chrono::seconds(5)) { - logMessage(this->connection->virtual_server_id(), "{} Client initialized session reconnect, but last ping response is not older then 5 seconds ({}). Ignoring attempt", - this->connection->log_prefix(), - duration_cast(std::chrono::system_clock::now() - lastPingResponse).count() - ); - return ts::command_result{error::ok}; - } else if(!config::voice::allow_session_reinitialize) { - logMessage(this->connection->virtual_server_id(), "{} Client initialized session reconnect and last ping response is older then 5 seconds ({}). Dropping attempt because its not allowed due to config settings", - this->connection->log_prefix(), - duration_cast(std::chrono::system_clock::now() - lastPingResponse).count() - ); - return ts::command_result{error::ok}; + { + std::lock_guard state_lock{client->state_lock}; + switch(client->state) { + case ConnectionState::INIT_LOW: + client->state = ConnectionState::INIT_HIGH; + break; + + case ConnectionState::INIT_HIGH: + logTrace(client->getServerId(), "{} Received a duplicated initiv. It seems like our initivexpand2 hasn't yet reached the client. The acknowledge handler should handle this issue for us.", CLIENT_STR_LOG_PREFIX_(client)); + return CommandHandleResult::CONSUME_COMMAND; /* we don't want to send an error id=0 msg=ok */ + + case ConnectionState::CONNECTED: + case ConnectionState::DISCONNECTING: + case ConnectionState::DISCONNECTED: + /* That's really odd an should not happen */ + return CommandHandleResult::PASS_THROUGH; + + case ConnectionState::UNKNWON: + default: + assert(false); + return CommandHandleResult::PASS_THROUGH; } - logMessage(this->connection->virtual_server_id(), "{} Client initialized reconnect and last ping response is older then 5 seconds ({}). Allowing attempt", - this->connection->log_prefix(), - duration_cast(std::chrono::system_clock::now() - lastPingResponse).count() - ); - - state_lock.unlock(); - - { - std::unique_lock server_channel_lock(client->server->get_channel_tree_lock()); /* we cant get moved if this is locked! */ - if(client->currentChannel) { - client->server->client_move(client->ref(), nullptr, nullptr, config::messages::timeout::connection_reinitialized, ViewReasonId::VREASON_TIMEOUT, false, server_channel_lock); - } - } - - client->finalDisconnect(); - state_lock.lock(); - } else if(client->state >= ConnectionState::DISCONNECTING) { - state_lock.unlock(); - std::shared_lock disconnect_finish{client->finalDisconnectLock}; /* await until the last disconnect has been processed */ - state_lock.lock(); - client->state = ConnectionState::INIT_HIGH; - } else if(client->state == ConnectionState::INIT_HIGH) { - logTrace(client->getServerId(), "{} Received a duplicated initiv. It seems like our initivexpand2 hasn't yet reached the client. The acknowledge handler should handle this issue for us.", CLIENT_STR_LOG_PREFIX_(client)); - return CommandHandleResult::CONSUME_COMMAND; /* we don't want to send an error id=0 msg=ok */ - } else { - client->state = ConnectionState::INIT_HIGH; } - state_lock.unlock(); this->connection->reset(); this->connection->packet_decoder().register_initiv_packet(); diff --git a/server/src/client/voice/VoiceClient.cpp b/server/src/client/voice/VoiceClient.cpp index a600287..89cade4 100644 --- a/server/src/client/voice/VoiceClient.cpp +++ b/server/src/client/voice/VoiceClient.cpp @@ -3,10 +3,8 @@ #include #include #include -#include #include #include -#include #include "VoiceClient.h" #include "src/InstanceHandler.h" @@ -51,12 +49,9 @@ VoiceClient::~VoiceClient() { delete this->connection; this->connection = nullptr; - { - std::lock_guard fthread_lock{this->flush_thread_mutex}; - if(this->flushing_thread) { - logCritical(this->getServerId(), "Deleting a VoiceClient which should still be hold within the flush thread!"); - this->flushing_thread->detach(); - } + if(this->flush_task) { + logCritical(this->getServerId(), "VoiceClient deleted with an active pending flush task!"); + serverInstance->general_task_executor()->cancel_task(this->flush_task); } memtrack::freed(this); @@ -102,146 +97,213 @@ bool VoiceClient::disconnect(const std::string &reason) { } bool VoiceClient::disconnect(ts::ViewReasonId reason_id, const std::string &reason, const std::shared_ptr& invoker, bool notify_viewer) { - /* - * We don't have to lock the disconnect lock here, because we're not really unregistering the client. - * Its only for the clients own flavour and everything which the client receives after will be ignored :) - */ + task_id disconnect_id; - ConnectionState old_state{}; - { - std::lock_guard state_lock{this->state_lock}; - if(this->state == ConnectionState::DISCONNECTING || this->state == ConnectionState::DISCONNECTED) - return false; //Already disconnecting/disconnected - - old_state = this->state; - this->state = ConnectionState::DISCONNECTING; - } - - if(old_state == ConnectionState::CONNECTED) { - /* Client has been successflly initialized; Send normal disconnect. */ - - Command cmd("notifyclientleftview"); - cmd["reasonmsg"] = reason; - cmd["reasonid"] = reason_id; - cmd["clid"] = this->getClientId(); - cmd["cfid"] = this->currentChannel ? this->currentChannel->channelId() : 0; //Failed when cid = 0???? - cmd["ctid"] = 0; - - if (invoker) { - cmd["invokerid"] = invoker->getClientId(); - cmd["invokername"] = invoker->getDisplayName(); - cmd["invokeruid"] = invoker->getUid(); + auto weak_client{this->weak_ref()}; + serverInstance->general_task_executor()->schedule(disconnect_id, "voice client disconnect of " + this->getLoggingPrefix(), [ + weak_client, + reason_id, + reason, + invoker, + notify_viewer + ]{ + auto client = dynamic_pointer_cast(weak_client.lock()); + if(!client) { + /* client has already been deallocated */ + return; } - auto old_channel = this->currentChannel; - if(old_channel) { - serverInstance->action_logger()->client_channel_logger.log_client_leave(this->getServerId(), this->ref(), old_channel->channelId(), old_channel->name()); - } + bool await_disconnect_ack{false}; + { + std::lock_guard state_lock{client->state_lock}; + switch (client->state) { + case ConnectionState::DISCONNECTING: + case ConnectionState::DISCONNECTED: + /* somebody else already disconnected the client */ + return; - if(notify_viewer && this->server) { - unique_lock channel_lock(this->server->channel_tree_mutex); - this->server->client_move(this->ref(), nullptr, invoker, reason, reason_id, false, channel_lock); - } else { - threads::MutexLock lock(this->command_lock); - auto server_channel = dynamic_pointer_cast(this->currentChannel); - if(server_channel) - server_channel->unregister_client(this->ref()); - this->currentChannel = nullptr; - } + case ConnectionState::INIT_HIGH: + case ConnectionState::INIT_LOW: + /* disconnect the client and close the connection */ + break; - auto weak_self = this->weak_ref(); - this->sendCommand0(cmd.build(), false, std::make_unique>([weak_self](bool success) { - auto self = weak_self.lock(); - if(!self) { - return; + case ConnectionState::CONNECTED: + await_disconnect_ack = true; + break; + + case ConnectionState::UNKNWON: + default: + assert(false); + return; } - if(!success) { - /* In theory we have no need to disconnect the client any more since a failed acknowledge would do the trick for us */ - debugMessage(self->getServerId(), "{} Failed to receive disconnect acknowledge!", CLIENT_STR_LOG_PREFIX_(self)); + client->state = ConnectionState::DISCONNECTING; + } + + /* server should never be a nullptr */ + assert(client->server); + + ChannelId client_channel{client->getChannelId()}; + { + std::lock_guard command_lock{client->command_lock}; + std::unique_lock server_tree_lock{client->server->channel_tree_mutex}; + if(client->currentChannel) { + if(notify_viewer) { + client->server->client_move(client->ref(), nullptr, invoker, reason, reason_id, false, server_tree_lock); + } else { + auto server_channel = dynamic_pointer_cast(client->currentChannel); + assert(server_channel); + server_channel->unregister_client(client); + + client->currentChannel = nullptr; + } + } + } + + { + ts::command_builder notify{"notifyclientleftview"}; + notify.put_unchecked(0, "reasonmsg", reason); + notify.put_unchecked(0, "reasonid", reason_id); + notify.put_unchecked(0, "clid", client->getClientId()); + notify.put_unchecked(0, "cfid", client_channel); + notify.put_unchecked(0, "ctid", "0"); + + if (invoker) { + notify.put_unchecked(0, "invokerid", invoker->getClientId()); + notify.put_unchecked(0, "invokername", invoker->getDisplayName()); + notify.put_unchecked(0, "invokeruid", invoker->getUid()); + } + + if(await_disconnect_ack) { + { + std::lock_guard flush_lock{client->flush_mutex}; + if(!client->disconnect_acknowledged.has_value()) { + client->disconnect_acknowledged = std::make_optional(false); + } else { + /* + * The disconnect acknowledged flag might already has been set to true in cases + * where we know that the client is aware of the disconnect. + * An example scenario would be when the client sends the `clientdisconnect` command. + */ + } + } + + client->sendCommand0(notify.build(), false, std::make_unique>([weak_client](bool success){ + auto self = dynamic_pointer_cast(weak_client.lock()); + if(!self) { + return; + } + + if(!success) { + /* In theory we have no need to disconnect the client any more since a failed acknowledge would do the trick for us */ + debugMessage(self->getServerId(), "{} Failed to receive disconnect acknowledge!", CLIENT_STR_LOG_PREFIX_(self)); + } else { + debugMessage(self->getServerId(), "{} Received disconnect acknowledge!", CLIENT_STR_LOG_PREFIX_(self)); + } + + std::lock_guard flush_lock{self->flush_mutex}; + assert(self->disconnect_acknowledged.has_value()); + *self->disconnect_acknowledged = true; + })); } else { - debugMessage(self->getServerId(), "{} Received disconnect acknowledge!", CLIENT_STR_LOG_PREFIX_(self)); + client->sendCommand(notify, false); } + } - self->close_connection(chrono::system_clock::time_point{}); /* we received the ack, we do not need to flush anything */ - })); - } else { - //TODO: Extra case for INIT_HIGH? - this->close_connection(chrono::system_clock::now() + chrono::seconds{5}); + /* close the connection after 5 seconds regardless if we've received a disconnect acknowledge */ + client->close_connection(chrono::system_clock::now() + chrono::seconds{5}); + }); + + return true; +} + +bool VoiceClient::connection_flushed() { + /* Ensure that we've at least send everything we wanted to send */ + if(!this->connection->wait_empty_write_and_prepare_queue(std::chrono::system_clock::time_point{})) { + /* we still want to write data */ + return false; } + + { + std::lock_guard flush_lock{this->flush_mutex}; + if(this->disconnect_acknowledged.has_value()) { + /* We've send a disconnect command to the client. If the client acknowledges this command we've nothing more to send. */ + return *this->disconnect_acknowledged; + } + } + + if(this->connection->packet_encoder().acknowledge_manager().awaiting_acknowledge() > 0) { + /* We're still waiting for some acknowledges from the client (for example the disconnect/kick packet) */ + return false; + } + return true; } bool VoiceClient::close_connection(const system_clock::time_point &timeout) { - auto self_lock = dynamic_pointer_cast(this->ref()); - assert(self_lock); //Should never happen! - - bool flush = timeout.time_since_epoch().count() > 0; - { - std::lock_guard state_lock{this->state_lock}; - - if(this->state == ConnectionState::DISCONNECTED) { - return false; - } else if(this->state == ConnectionState::DISCONNECTING) { - /* here is nothing to pay attention for */ - } else if(this->state == ConnectionState::DISCONNECTING_FLUSHING) { - if(!flush) { - this->state = ConnectionState::DISCONNECTED; - return true; /* the flush thread will execute the final disconnect */ - } else { - //TODO: May update the flush timeout if its less then the other one? - return true; - } - } - this->state = flush ? ConnectionState::DISCONNECTING_FLUSHING : ConnectionState::DISCONNECTED; - } - - debugMessage(this->getServerId(), "{} Closing voice client connection. (Flush: {})", CLIENT_STR_LOG_PREFIX, flush); - - std::lock_guard fthread_lock{this->flush_thread_mutex}; - //TODO: Move this out into a thread pool? - if(this->flushing_thread && this->flushing_thread->joinable()) { - logCritical(LOG_GENERAL, "VoiceClient::close_connection reached flushing thread with an active old handle. Ignoring request."); + std::lock_guard flush_lock{this->flush_mutex}; + if(this->flush_executed) { + /* We've already scheduled a connection close. We only update the timeout. */ + this->flush_timeout = std::min(timeout, this->flush_timeout); return true; } - auto flush_thread = std::make_shared([this, self_lock, timeout, flush]{ + this->flush_timeout = timeout; + + auto weak_client{this->weak_ref()}; + serverInstance->general_task_executor()->schedule_repeating(this->flush_task, "connection flush/close for " + this->getLoggingPrefix(), std::chrono::milliseconds{25}, [weak_client](const auto&){ + auto client = dynamic_pointer_cast(weak_client.lock()); + if(!client) { + /* client has already been deallocated */ + return; + } + + /* Dont execute any commands which might alter the current client state */ + std::lock_guard command_lock{client->command_lock}; + { - /* Await that all commands have been processed. It does not make sense to unregister the client while command handling. */ - std::lock_guard cmd_lock{this->command_lock}; - } + std::lock_guard state_lock{client->state_lock}; + switch(client->state) { + case ConnectionState::DISCONNECTED: + /* somebody else already disconnected the client */ + return; - if(flush) { - debugMessage(this->getServerId(), "{} Awaiting write prepare, write and acknowledge queue flushed", CLIENT_STR_LOG_PREFIX); - while(this->state == DISCONNECTING_FLUSHING) { - if(system_clock::now() > timeout){ - auto write_queue_flushed = this->connection->wait_empty_write_and_prepare_queue(timeout); - auto acknowledge_received = connection->packet_encoder().acknowledge_manager().awaiting_acknowledge() == 0; - - if(write_queue_flushed && acknowledge_received) - break; - - debugMessage(this->getServerId(), "{} Failed to flush pending messages. Acknowledges pending: {} Buffers pending: {}", CLIENT_STR_LOG_PREFIX, acknowledge_received, write_queue_flushed); + case ConnectionState::CONNECTED: + case ConnectionState::INIT_HIGH: + case ConnectionState::INIT_LOW: + /* It's the first call to this task. Setting the clients state to disconnecting */ break; - } - if(!this->connection->wait_empty_write_and_prepare_queue(timeout)) - continue; - if(connection->packet_encoder().acknowledge_manager().awaiting_acknowledge() > 0) { - usleep(5000); - continue; - } - debugMessage(this->getServerId(), "{} Write and acknowledge queue are flushed", CLIENT_STR_LOG_PREFIX); - break; + case ConnectionState::DISCONNECTING: + /* We're just awaiting for the client to finish stuff */ + break; + + case ConnectionState::UNKNWON: + default: + assert(false); + return; } + + client->state = ConnectionState::DISCONNECTING; } - if(this->state > DISCONNECTING) /* it could happen that the client "reconnects" while flushing this shit */ - this->finalDisconnect(); + auto timestamp_now = std::chrono::system_clock::now(); + auto flushed = client->connection_flushed(); + if(!flushed && client->flush_timeout >= timestamp_now) { + /* connection hasn't yet been flushed */ + return; + } + + if(flushed) { + debugMessage(client->getServerId(), "{} Connection successfully flushed.", client->getLoggingPrefix()); + } else { + debugMessage(client->getServerId(), "{} Connection flush timed out. Force closing connection.", client->getLoggingPrefix()); + } + + /* connection flushed or flush timed out. */ + client->finalDisconnect(); }); - this->flushing_thread = flush_thread; - threads::name(*flush_thread, "Flush thread VC"); return true; } @@ -249,24 +311,33 @@ void VoiceClient::finalDisconnect() { auto ownLock = dynamic_pointer_cast(this->ref()); assert(ownLock); - lock_guard disconnect_lock_final(this->finalDisconnectLock); - if(this->final_disconnected) { - logError(this->getServerId(), "Tried to final disconnect {}/{} twice", this->getLoggingPeerIp() + ":" + to_string(this->getPeerPort()), this->getDisplayName()); - return; - } - this->final_disconnected = true; - this->state = ConnectionState::DISCONNECTED; - - threads::MutexLock command_lock(this->command_lock); //We should not progress any commands while disconnecting - //Unload manager cache - this->processLeave(); { - std::lock_guard fthread_lock{this->flush_thread_mutex}; - if(this->flushing_thread) { - this->flushing_thread->detach(); //The thread itself should be already done or executing this method + std::lock_guard state_lock{this->state_lock}; + if(this->state != ConnectionState::DISCONNECTING) { + logCritical(this->getServerId(), "{} finalDisconnect called but state isn't disconnecting ({}).", this->getLoggingPrefix(), (uint32_t) this->state); + return; } - this->flushing_thread = nullptr; + + this->state = ConnectionState::DISCONNECTED; } + + { + std::lock_guard flush_lock{this->flush_mutex}; + this->flush_executed = true; + if(this->flush_task) { + serverInstance->general_task_executor()->cancel_task(this->flush_task); + this->flush_task = 0; + } + } + + /* TODO: Remove? (legacy) */ + { + lock_guard disconnect_lock_final(this->finalDisconnectLock); + } + + std::lock_guard command_lock{this->command_lock}; + this->processLeave(); + if(this->voice_server) { this->voice_server->unregisterConnection(ownLock); } diff --git a/server/src/client/voice/VoiceClient.h b/server/src/client/voice/VoiceClient.h index d448165..c61e4dc 100644 --- a/server/src/client/voice/VoiceClient.h +++ b/server/src/client/voice/VoiceClient.h @@ -63,6 +63,11 @@ namespace ts { bool close_connection(const std::chrono::system_clock::time_point &timeout) override; bool disconnect(const std::string&) override; + + /* + * TODO: Use a helper class called InvokerDescription containing the invoker properties and not holding a whole connected client reference + * 2. May use some kind of class to easily set the disconnect reason? + */ bool disconnect(ViewReasonId /* reason type */, const std::string& /* reason */, const std::shared_ptr& /* invoker */, bool /* notify viewer */); void sendCommand(const ts::Command &command, bool low = false) override { return this->sendCommand0(command.build(), low, nullptr); } @@ -102,7 +107,9 @@ namespace ts { private: void finalDisconnect(); - bool final_disconnected = false; + + /* Used by close_connection to determine if we've successfully flushed the connection */ + [[nodiscard]] bool connection_flushed(); rtc::NativeAudioSourceSupplier rtc_audio_supplier{}; rtc::NativeAudioSourceSupplier rtc_audio_whisper_supplier{}; @@ -110,13 +117,14 @@ namespace ts { uint16_t stop_seq_counter{0}; uint16_t whisper_head_counter{0}; - //General TS3 manager commands command_result handleCommandClientInit(Command&) override; command_result handleCommandClientDisconnect(Command&); - //Locked by finalDisconnect, disconnect and close connection - std::mutex flush_thread_mutex{}; - std::shared_ptr flushing_thread; + std::mutex flush_mutex{}; + task_id flush_task{0}; + bool flush_executed{false}; + std::chrono::system_clock::time_point flush_timeout{}; + std::optional disconnect_acknowledged{}; /* locked by flush_mutex */ std::unique_ptr server_command_queue_{}; }; diff --git a/server/src/client/voice/VoiceClientCommandHandler.cpp b/server/src/client/voice/VoiceClientCommandHandler.cpp index a8a28c8..f8d4710 100644 --- a/server/src/client/voice/VoiceClientCommandHandler.cpp +++ b/server/src/client/voice/VoiceClientCommandHandler.cpp @@ -21,6 +21,27 @@ bool VoiceClientCommandHandler::handle_command(const std::string_view &command_s return false; } + std::lock_guard command_lock{client->command_lock}; + { + std::lock_guard state_lock{client->state_lock}; + switch(client->state) { + case ConnectionState::INIT_LOW: + case ConnectionState::INIT_HIGH: + case ConnectionState::CONNECTED: + break; + + case ConnectionState::DISCONNECTING: + case ConnectionState::DISCONNECTED: + /* we're just dropping the command and all future commands */ + return false; + + case ConnectionState::UNKNWON: + default: + assert(false); + return false; + } + } + client->handlePacketCommand(command_string); return true; } @@ -88,8 +109,13 @@ command_result VoiceClient::handleCommandClientInit(Command &cmd) { if(this->getType() == ClientType::CLIENT_TEAMSPEAK) { auto client_identity = this->connection->crypt_setup_handler().identity_key(); + auto client_key_offset = cmd["client_key_offset"].string(); + if(client_key_offset.length() > 128 || client_key_offset.find_first_not_of("0123456789") != std::string::npos) { + return command_result{error::parameter_invalid, "client_key_offset"}; + } + int security_level; - if(!calculate_security_level(security_level, &*client_identity, cmd["client_key_offset"].string())) { + if(!calculate_security_level(security_level, &*client_identity, client_key_offset)) { logError(this->getServerId(), "[{}] Failed to calculate security level. Error code: {}", CLIENT_STR_LOG_PREFIX, security_level); return command_result{error::vs_critical}; } @@ -109,18 +135,22 @@ command_result VoiceClient::handleCommandClientInit(Command &cmd) { command_result VoiceClient::handleCommandClientDisconnect(Command& cmd) { auto reason = cmd["reasonmsg"].size() > 0 ? cmd["reasonmsg"].as() : ""; - { - std::shared_lock own_lock{this->channel_tree_mutex}; - this->notifyClientLeftView(this->ref(), nullptr, VREASON_SERVER_LEFT, reason, nullptr, false); //Before we're moving us out of the channel tree! + + if(reason.empty()) { + debugMessage(this->getServerId(), "{} Received client disconnect with no custom reason.", CLIENT_STR_LOG_PREFIX); + } else { + debugMessage(this->getServerId(), "{} Received client disconnect with custom reason: {}", CLIENT_STR_LOG_PREFIX, reason); } - if(this->state == CONNECTED) { - std::unique_lock channel_lock{this->server->channel_tree_mutex}; - this->server->client_move(this->ref(), nullptr, nullptr, reason, VREASON_SERVER_LEFT, true, channel_lock); - } - logMessage(this->getServerId(), "{} Got remote disconnect with the reason '{}'", CLIENT_STR_LOG_PREFIX, reason); + this->disconnect(VREASON_SERVER_LEFT, reason, nullptr, true); + this->postCommandHandler.push_back([&]{ - this->close_connection(std::chrono::system_clock::now() + std::chrono::seconds{1}); /* send acknowledge and close connection */ + /* + * Instantly set the disconnect acknowledged flag since we actually received it from the client. + */ + std::lock_guard flush_lock{this->flush_mutex}; + this->disconnect_acknowledged = std::make_optional(true); }); - return command_result{error::ok}; + + return ts::command_result{error::ok}; } \ No newline at end of file diff --git a/server/src/client/voice/VoiceClientConnection.cpp b/server/src/client/voice/VoiceClientConnection.cpp index 91faf41..8deab5e 100644 --- a/server/src/client/voice/VoiceClientConnection.cpp +++ b/server/src/client/voice/VoiceClientConnection.cpp @@ -264,11 +264,21 @@ void VoiceClientConnection::callback_resend_failed(void *ptr_this, const shared_ debugMessage(connection->virtual_server_id_, "{} Failed to execute packet resend of packet {}. Dropping connection.", connection->log_prefix(), entry->packet_full_id); auto client = connection->getCurrentClient(); assert(client); /* TIXME! */ + if(client->state == ConnectionState::CONNECTED) { client->disconnect(ViewReasonId::VREASON_TIMEOUT, config::messages::timeout::packet_resend_failed, nullptr, true); - } else { - client->close_connection(system_clock::now() + seconds(1)); } + + { + /* + * The connection timeout out. + * We don't expect any disconnect packages to be acknowledges so we don't need to + * await for such. + */ + std::lock_guard lock{client->flush_mutex}; + client->disconnect_acknowledged = std::make_optional(true); + } + client->close_connection(std::chrono::system_clock::time_point{}); } void VoiceClientConnection::callback_resend_statistics(void *ptr_this, size_t send_count) { diff --git a/server/src/server/POWHandler.cpp b/server/src/server/POWHandler.cpp index 335be8d..923461b 100644 --- a/server/src/server/POWHandler.cpp +++ b/server/src/server/POWHandler.cpp @@ -282,14 +282,68 @@ void POWHandler::handle_puzzle_solve(const std::shared_ptr POWHandler::register_verified_client(const std::shared_ptr &client) { - shared_ptr voice_client; + std::shared_ptr voice_client; { lock_guard lock(this->server->connectionLock); for(const auto& connection : this->server->activeConnections) { - if(memcmp(&connection->remote_address, &client->address, sizeof(client->address)) == 0){ - voice_client = connection; - break; + if(memcmp(&connection->remote_address, &client->address, sizeof(client->address)) != 0) { + continue; } + + switch(connection->connectionState()) { + case ConnectionState::DISCONNECTING: + case ConnectionState::DISCONNECTED: + /* Connection already disconnecting/disconnected. Don't use this connection. */ + continue; + + case ConnectionState::INIT_LOW: + case ConnectionState::INIT_HIGH: + /* It seems like a clientinitiv command resend. Process it. */ + break; + + case ConnectionState::CONNECTED: { + auto timestamp_now = std::chrono::system_clock::now(); + auto last_client_alive_signal = std::max(connection->connection->ping_handler().last_ping_response(), connection->connection->ping_handler().last_command_acknowledged()); + if(timestamp_now - last_client_alive_signal < std::chrono::seconds(5)) { + logMessage(connection->connection->virtual_server_id(), "{} Client initialized session reconnect, but last alive signal is not older then 5 seconds ({}). Ignoring attempt.", + connection->connection->log_prefix(), + duration_cast(timestamp_now - last_client_alive_signal).count() + ); + + /* FIXME: Somehow send an error? */ + return nullptr; + } else if(!config::voice::allow_session_reinitialize) { + logMessage(connection->connection->virtual_server_id(), "{} Client initialized session reconnect and last ping response is older then 5 seconds ({}). Dropping attempt because its not allowed due to config settings", + connection->connection->log_prefix(), + duration_cast(timestamp_now - last_client_alive_signal).count() + ); + + /* FIXME: Somehow send an error? */ + return nullptr; + } + + logMessage(connection->connection->virtual_server_id(), "{} Client initialized reconnect and last ping response is older then 5 seconds ({}). Allowing attempt and dropping old connection.", + connection->connection->log_prefix(), + duration_cast(timestamp_now - last_client_alive_signal).count() + ); + + connection->close_connection(std::chrono::system_clock::time_point{}); + { + std::lock_guard flush_lock{connection->flush_mutex}; + connection->disconnect_acknowledged = std::make_optional(true); + } + + continue; + } + + case ConnectionState::UNKNWON: + default: + assert(false); + continue; + } + + voice_client = connection; + break; } } @@ -303,14 +357,14 @@ shared_ptr POWHandler::register_verified_client(const std::shared_p memcpy(&voice_client->connection->remote_address_info_, &client->address_info, sizeof(client->address_info)); { - lock_guard lock(this->server->connectionLock); - server->activeConnections.push_back(voice_client); + std::lock_guard lock{this->server->connectionLock}; + this->server->activeConnections.push_back(voice_client); } - debugMessage(this->get_server_id(), "Having new voice client. Remote address: {}", voice_client->getLoggingPeerIp() +":" + to_string(voice_client->getPeerPort())); + debugMessage(this->get_server_id(), "Having new voice client. Remote address: {}:{}", voice_client->getLoggingPeerIp(), voice_client->getPeerPort()); } + voice_client->getConnection()->crypt_setup_handler().set_client_protocol_time(client->client_version); //voice_client->last_packet_handshake = system_clock::now(); - return voice_client; } \ No newline at end of file diff --git a/server/src/server/PrecomputedPuzzles.cpp b/server/src/server/PrecomputedPuzzles.cpp index ed61362..4ab362c 100644 --- a/server/src/server/PrecomputedPuzzles.cpp +++ b/server/src/server/PrecomputedPuzzles.cpp @@ -95,9 +95,13 @@ void PuzzleManager::generate_puzzle(std::mt19937& random_generator) { puzzle->level = ts::config::voice::RsaPuzzleLevel; while(true) { +#if 0 random_number(random_generator, &puzzle->x, 64); random_number(random_generator, &puzzle->n, 64); - +#else + mp_set(&puzzle->x, 1); + mp_set(&puzzle->n, 1); +#endif if(!solve_puzzle(&*puzzle)) { continue; } diff --git a/server/src/server/VoiceIOManager.cpp b/server/src/server/VoiceIOManager.cpp index 401daab..56e4c41 100644 --- a/server/src/server/VoiceIOManager.cpp +++ b/server/src/server/VoiceIOManager.cpp @@ -88,12 +88,12 @@ void VoiceIOManager::shutdownGlobally() { /* keep a ref to all event loops so they dont despawn in their event thread */ unique_lock executor_lock{this->executorLock}; - auto event_loops = this->event_loops; auto wait_end = system_clock::now() + chrono::seconds{5}; while(true) { - if(this->event_loops.empty()) + if(this->event_loops.empty()) { break; + } auto status = this->ioExecutorNotify.wait_until(executor_lock, wait_end); if(status == std::cv_status::timeout) { @@ -106,7 +106,7 @@ void VoiceIOManager::shutdownGlobally() { } /* now delete all loops */ - event_loops.clear(); + this->event_loops.clear(); } //TODO also reduce thread pool! @@ -299,18 +299,7 @@ void IOEventLoopEvents::despawn() { void VoiceIOManager::dispatchBase(shared_ptr self) { debugMessage(LOG_INSTANCE, "Dispatching io base {}", (void*) self->base); - while(true) { - if(event_base_got_exit(self->base) || self->shutdown) - break; - - event_base_loop(self->base, 0); - { - /* wait until reschedule */ - unique_lock execute_lock(this->executorLock); - if(event_base_get_num_events(self->base, EVENT_BASE_COUNT_ACTIVE | EVENT_BASE_COUNT_ADDED | EVENT_BASE_COUNT_VIRTUAL) == 0) - this->ioExecutorNotify.wait(execute_lock); - } - } + event_base_loop(self->base, EVLOOP_NO_EXIT_ON_EMPTY); debugMessage(LOG_INSTANCE, "Dispatching io base {} finished", (void*) self->base); { diff --git a/server/src/server/VoiceServer.cpp b/server/src/server/VoiceServer.cpp index 794320b..d73984d 100644 --- a/server/src/server/VoiceServer.cpp +++ b/server/src/server/VoiceServer.cpp @@ -194,11 +194,16 @@ std::shared_ptr VoiceServer::findClient(ts::ClientId client) { std::shared_ptr VoiceServer::findClient(sockaddr_in *addr, bool) { lock_guard lock(this->connectionLock); - for(const auto& elm : this->activeConnections) { - if(elm->isAddressV4()) - if(elm->getAddressV4()->sin_addr.s_addr == addr->sin_addr.s_addr) - if(elm->getAddressV4()->sin_port == addr->sin_port) + /* Use a reverse iterator so we're getting the "last"/"newest" connection instance */ + for(auto it{this->activeConnections.rbegin()}; it != this->activeConnections.rend(); it++) { + auto& elm = *it; + if(elm->isAddressV4()) { + if(elm->getAddressV4()->sin_addr.s_addr == addr->sin_addr.s_addr) { + if(elm->getAddressV4()->sin_port == addr->sin_port) { return elm; + } + } + } } return nullptr; } @@ -206,12 +211,18 @@ std::shared_ptr VoiceServer::findClient(sockaddr_in *addr, bool) { std::shared_ptr VoiceServer::findClient(sockaddr_in6 *addr, bool) { lock_guard lock(this->connectionLock); - for(const auto& elm : this->activeConnections) { - if(elm->isAddressV6()) - if(memcmp(elm->getAddressV6()->sin6_addr.__in6_u.__u6_addr8, addr->sin6_addr.__in6_u.__u6_addr8, 16) == 0) - if(elm->getAddressV6()->sin6_port == addr->sin6_port) + /* Use a reverse iterator so we're getting the "last"/"newest" connection instance */ + for(auto it{this->activeConnections.rbegin()}; it != this->activeConnections.rend(); it++) { + auto& elm = *it; + if(elm->isAddressV6()) { + if(memcmp(elm->getAddressV6()->sin6_addr.__in6_u.__u6_addr8, addr->sin6_addr.__in6_u.__u6_addr8, 16) == 0) { + if(elm->getAddressV6()->sin6_port == addr->sin6_port) { return elm; + } + } + } } + return nullptr; } @@ -261,12 +272,15 @@ void VoiceServer::handleMessageRead(int fd, short events, void *_event_handle) { message.msg_flags = 0; bytes_read = recvmsg(fd, &message, 0); - if((message.msg_flags & MSG_TRUNC) > 0) + if((message.msg_flags & MSG_TRUNC) > 0) { logError(ts_server->getServerId(), "Received truncated message from {}", net::to_string(remote_address)); + } if(bytes_read < 0) { - if(errno == EAGAIN) + if(errno == EAGAIN) { break; + } + //Nothing more to read logCritical(ts_server->getServerId(), "Could not receive datagram packet! Code: {} Reason: {}", errno, strerror(errno)); break; @@ -281,7 +295,7 @@ void VoiceServer::handleMessageRead(int fd, short events, void *_event_handle) { continue; } - shared_ptr client; + std::shared_ptr client; { if(*(uint64_t*) raw_read_buffer == TS3INIT.integral) { //Handle ddos protection... @@ -427,6 +441,7 @@ void VoiceServer::handleMessageWrite(int fd, short events, void *_event_handle) more_clients = (bool) client_queue_state; } + client_wbuffer_state = WBufferPopResult::MORE_AVAILABLE; while(system_clock::now() <= write_timeout) { packet = nullptr; client_wbuffer_state = client->connection->packet_encoder().pop_write_buffer(packet); diff --git a/shared b/shared index 8815874..0a73e4c 160000 --- a/shared +++ b/shared @@ -1 +1 @@ -Subproject commit 88158742e873a9ecc076091a76b52af85ca32687 +Subproject commit 0a73e4c10c4af3b6f982c0e2986f122e75bd20a8