#include #include #include #include #include #include #include #include "src/channel/ClientChannelView.h" #include "SpeakingClient.h" #include "src/InstanceHandler.h" #include "StringVariable.h" #include "misc/timer.h" #include "../manager/ActionLogger.h" using namespace std::chrono; using namespace ts; using namespace ts::server; using namespace ts::protocol; //#define PKT_LOG_VOICE //#define PKT_LOG_WHISPER bool SpeakingClient::shouldReceiveVoice(const std::shared_ptr &sender) { //if(this->properties()[property::CLIENT_AWAY].as()) return false; if(!this->properties()[property::CLIENT_OUTPUT_HARDWARE].as()) return false; if(this->properties()[property::CLIENT_OUTPUT_MUTED].as()) return false; { shared_lock client_lock(this->channel_lock); for(const auto& entry : this->mutedClients) if(entry.lock() == sender) return false; } return true; } bool SpeakingClient::shouldReceiveVoiceWhisper(const std::shared_ptr &sender) { if(!this->shouldReceiveVoice(sender)) return false; return permission::v2::permission_granted(this->cpmerission_needed_whisper_power, sender->cpmerission_whisper_power, false); } void SpeakingClient::handlePacketVoice(const pipes::buffer_view& data, bool head, bool fragmented) { auto server = this->getServer(); auto self = _this.lock(); if(!self || !server) return; if(data.length() < 3) { this->disconnect("invalid packet (Voice; Length: " + to_string(data.length()) + ")"); return; } #if 0 if(rand() % 10 == 0) { logMessage(0, "Dropping audio packet"); return; } logMessage(0, "Received voice: Head: {} Fragmented: {}, length: {}", head, fragmented, data.length()); #endif auto current_channel = this->currentChannel; if(!current_channel) { return; } if(!this->allowedToTalk) { return; } this->updateSpeak(false, system_clock::now()); this->resetIdleTime(); auto target_clients = this->server->getClientsByChannel(current_channel); target_clients.erase(std::remove_if(target_clients.begin(), target_clients.end(), [&](const shared_ptr& client) { if(client == this) return true; auto speaking_client = dynamic_pointer_cast(client); if(!speaking_client) return true; return !speaking_client->shouldReceiveVoice(self); }), target_clients.end()); if(target_clients.empty()) { return; } VoicePacketFlags flags{}; flags.head = head; flags.fragmented = fragmented; flags.new_protocol = false; { //crypt_mode = 1 | disabled //crypt_mode = 2 | enabled auto crypt_mode = this->server->voice_encryption_mode(); if(crypt_mode == 0) flags.encrypted = !current_channel->properties()[property::CHANNEL_CODEC_IS_UNENCRYPTED].as(); else flags.encrypted = crypt_mode == 2; } uint16_t vpacketId = be2le16((char*) data.data_ptr()); auto codec = (uint8_t) data[2]; #ifdef PKT_LOG_VOICE logTrace(lstream << CLIENT_LOG_PREFIX << "Voice length: " << data.length() << " -> id: " << vpacketId << " codec: " << (int) codec << " head: " << head << " fragmented: " << fragmented); #endif char buffer[data.length() + 2]; le2be16(vpacketId, &buffer[0]); le2be16(getClientId(), &buffer[2]); buffer[4] = codec; if(data.length() - 3 > 0) { memcpy(&buffer[5], &data[3], data.length() - 3); } auto bview = pipes::buffer_view{buffer, data.length() + 2}; for (const auto& client : target_clients) { auto speaking_client = static_pointer_cast(client); speaking_client->send_voice_packet(bview, flags); } } //2 + 2 + 8 #define OUT_WHISPER_PKT_OFFSET 5 //#define PKT_LOG_WHISPER enum WhisperType { SERVER_GROUP = 0, CHANNEL_GROUP = 1, CHANNEL_COMMANDER = 2, ALL = 3, ECHO_TEXT = 0x10, }; enum WhisperTarget { CHANNEL_ALL = 0, CHANNEL_CURRENT = 1, CHANNEL_PARENT = 2, CHANNEL_ALL_PARENT = 3, CHANNEL_FAMILY = 4, CHANNEL_COMPLETE_FAMILY = 5, CHANNEL_SUBCHANNELS = 6 }; inline bool update_whisper_error(std::chrono::system_clock::time_point& last) { auto now = std::chrono::system_clock::now(); if(last + std::chrono::milliseconds{500} < now) { last = now; return true; } return false; } //All clients => type := SERVER_GROUP and target_id := 0 //Server group => type := SERVER_GROUP and target_id := //Channel group => type := CHANNEL_GROUP and target_id := //Channel commander => type := CHANNEL_COMMANDER and target_id := 0 void SpeakingClient::handlePacketVoiceWhisper(const pipes::buffer_view& data, bool new_packet) { if(data.length() < 5) { this->disconnect("Invalid packet (Voice whisper)"); logMessage(this->getServerId(), "{} Tried to send a too short whisper packet. Length: {}", CLIENT_STR_LOG_PREFIX, data.length()); return; } uint16_t offset = 0; auto vpacketId = be2le16((char*) data.data_ptr(), offset, &offset); auto codec = (uint8_t) data[offset++]; VoicePacketFlags flags{}; flags.head = false; flags.fragmented = false; flags.new_protocol = new_packet; if(new_packet) { if(data.length() < 7) { this->disconnect("Invalid packet (Voice whisper | New)"); logMessage(this->getServerId(), "{} Tried to send a too short whisper packet. Length: {}", CLIENT_STR_LOG_PREFIX, data.length()); return; } auto type = (WhisperType) data[offset++]; auto target = (WhisperTarget) data[offset++]; auto type_id = be2le64((char*) data.data_ptr(), offset, &offset); this->resetIdleTime(); size_t data_length = data.length() - offset; #ifdef PKT_LOG_WHISPER logTrace(this->getServerId(), "{} Whisper data length: {}. Type: {}. Target: {}. Target ID: {}.", CLIENT_STR_LOG_PREFIX, data_length, type, target, type_id); #endif deque> available_clients; if(type == WhisperType::ECHO_TEXT) { available_clients.push_back(dynamic_pointer_cast(this->ref())); } else { for(const auto& client : this->server->getClients()) { auto speakingClient = dynamic_pointer_cast(client); if(!speakingClient || client == this) continue; if(!speakingClient->currentChannel) continue; if(type == WhisperType::ALL) { available_clients.push_back(speakingClient); } else if(type == WhisperType::SERVER_GROUP) { if(type_id == 0) available_clients.push_back(speakingClient); else { shared_lock client_lock(this->channel_lock); for(const auto& id : client->cached_server_groups) { if(id == type_id) { available_clients.push_back(speakingClient); break; } } } } else if(type == WhisperType::CHANNEL_GROUP) { if(client->cached_channel_group == type_id) available_clients.push_back(speakingClient); } else if(type == WhisperType::CHANNEL_COMMANDER) { if(client->properties()[property::CLIENT_IS_CHANNEL_COMMANDER].as()) available_clients.push_back(speakingClient); } } if(target == WhisperTarget::CHANNEL_CURRENT) { available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const shared_ptr& target) { return target->currentChannel != this->currentChannel; }), available_clients.end()); } else if(target == WhisperTarget::CHANNEL_PARENT) { auto current_parent = this->currentChannel->parent(); if(!current_parent) return; available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const shared_ptr& target) { return target->currentChannel != current_parent; }), available_clients.end()); } else if(target == WhisperTarget::CHANNEL_ALL_PARENT) { shared_ptr current_parent; { current_parent = this->currentChannel->parent(); if(!current_parent) return; } available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const shared_ptr& target) { auto tmp_parent = current_parent; while(tmp_parent && tmp_parent != target->currentChannel) tmp_parent = tmp_parent->parent(); return target->currentChannel != tmp_parent; }), available_clients.end()); } else if(target == WhisperTarget::CHANNEL_FAMILY) { shared_ptr current = this->currentChannel; available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const shared_ptr& target) { auto tmp_current = target->currentChannel; while(tmp_current && tmp_current != current) tmp_current = tmp_current->parent(); return current != tmp_current; }), available_clients.end()); } else if(target == WhisperTarget::CHANNEL_COMPLETE_FAMILY) { shared_ptr current = this->currentChannel; while(current && current->parent()) current = current->parent(); available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const shared_ptr& target) { auto tmp_current = target->currentChannel; while(tmp_current && tmp_current != current) tmp_current = tmp_current->parent(); return current != tmp_current; }), available_clients.end()); } else if(target == WhisperTarget::CHANNEL_SUBCHANNELS) { shared_ptr current = this->currentChannel; available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const shared_ptr& target) { return target->currentChannel->parent() != current; }), available_clients.end()); } auto self_lock = this->_this.lock(); available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const std::shared_ptr& cl) { auto speakingClient = dynamic_pointer_cast(cl); return !speakingClient->shouldReceiveVoiceWhisper(self_lock); }), available_clients.end()); } if(available_clients.empty()) { if(update_whisper_error(this->speak_last_no_whisper_target)) { command_result result{error::whisper_no_targets}; this->notifyError(result); } return; } if(available_clients.size() > this->server->properties()[property::VIRTUALSERVER_MIN_CLIENTS_IN_CHANNEL_BEFORE_FORCED_SILENCE].as_save()) { if(update_whisper_error(this->speak_last_too_many_whisper_targets)) { command_result result{error::whisper_too_many_targets}; this->notifyError(result); } return; } //Create the packet data char packet_buffer[OUT_WHISPER_PKT_OFFSET + data_length]; if(offset < data.length()) memcpy(&packet_buffer[OUT_WHISPER_PKT_OFFSET], &data[offset], data_length); le2be16(vpacketId, packet_buffer, 0); le2be16(this->getClientId(), packet_buffer, 2); packet_buffer[4] = codec; VoicePacketFlags flags{}; auto data = pipes::buffer_view(packet_buffer, OUT_WHISPER_PKT_OFFSET + data_length); for(const auto& cl : available_clients){ cl->send_voice_whisper_packet(data, flags); } this->updateSpeak(false, system_clock::now()); } else { auto clientCount = (uint8_t) data[offset++]; auto channelCount = (uint8_t) data[offset++]; if(data.length() < 5 + channelCount * 2 + clientCount * 8) { logMessage(this->getServerId(), "{} Tried to send a too short whisper packet. Length: {} Required: {}", CLIENT_STR_LOG_PREFIX, data.length(), to_string(5 + channelCount * 2 + clientCount * 8)); return; } this->resetIdleTime(); ChannelId channelIds[clientCount]; ClientId clientIds[channelCount]; for(uint8_t index = 0; index < clientCount; index++) channelIds[index] = be2le64((char*) data.data_ptr(), offset, &offset); for(uint8_t index = 0; index < channelCount; index++) clientIds[index] = be2le16((char*) data.data_ptr(), offset, &offset); auto available_clients = this->server->getClients(); available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const std::shared_ptr& cl) { auto speakingClient = dynamic_pointer_cast(cl); if(!speakingClient || cl == this || !speakingClient->currentChannel) return true; auto clientChannelId = cl->currentChannel->channelId(); auto clientId = cl->getClientId(); for(uint8_t index = 0; index < clientCount; index++) if(channelIds[index] == clientChannelId) return false; for(uint8_t index = 0; index < channelCount; index++) if(clientIds[index] == clientId) return false; return true; }), available_clients.end()); auto self_lock = this->_this.lock(); available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const std::shared_ptr& cl) { auto speakingClient = dynamic_pointer_cast(cl); return !speakingClient->shouldReceiveVoiceWhisper(self_lock); }), available_clients.end()); if(available_clients.empty()) { if(update_whisper_error(this->speak_last_no_whisper_target)) { command_result result{error::whisper_no_targets}; this->notifyError(result); } return; } if(available_clients.size() > this->server->properties()[property::VIRTUALSERVER_MIN_CLIENTS_IN_CHANNEL_BEFORE_FORCED_SILENCE].as_save()) { if(update_whisper_error(this->speak_last_too_many_whisper_targets)) { command_result result{error::whisper_too_many_targets}; this->notifyError(result); } return; } size_t dataLength = data.length() - offset; #ifdef PKT_LOG_WHISPER logTrace(this->getServerId(), "{} Whisper data length: {}. Client count: {}. Channel count: {}.", CLIENT_STR_LOG_PREFIX, dataLength, clientCount, channelCount); #endif //Create the packet data char packetBuffer[OUT_WHISPER_PKT_OFFSET + dataLength]; if(offset < data.length()) memcpy(&packetBuffer[OUT_WHISPER_PKT_OFFSET], &data[offset], dataLength); le2be16(vpacketId, packetBuffer, 0); le2be16(this->getClientId(), packetBuffer, 2); packetBuffer[4] = codec; VoicePacketFlags flags{}; auto data = pipes::buffer_view(packetBuffer, OUT_WHISPER_PKT_OFFSET + dataLength); for(const auto& cl : available_clients){ //Faster? auto speakingClient = dynamic_pointer_cast(cl); assert(speakingClient); if(speakingClient->shouldReceiveVoiceWhisper(_this.lock())) speakingClient->send_voice_whisper_packet(data, flags); } this->updateSpeak(false, system_clock::now()); } } #define TEST_PARM(type) \ do {\ if(!cmd[0][key].castable())\ return {findError("parameter_invalid"), "Invalid type for " + key};\ } while(false) auto regex_wildcard = std::regex(".*"); #define S(x) #x #define HWID_REGEX(name, pattern) \ auto regex_hwid_ ##name = []() noexcept { \ try { \ return std::regex(pattern); \ } catch (std::exception& ex) { \ logError(0, "Failed to parse regex for " S(name)); \ } \ return regex_wildcard; \ }(); HWID_REGEX(windows, "^[a-z0-9]{32},[a-z0-9]{32}$"); 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::handleCommandClientInit(Command& cmd) { TIMING_START(timings); { lock_guard lock(this->server->join_attempts_lock); auto inetAddr = this->getPeerIp(); if(config::voice::clientConnectLimit > 0 && this->server->join_attempts[inetAddr] + 1 > config::voice::clientConnectLimit) return command_result{error::client_join_rate_limit_reached}; if(config::voice::connectLimit > 0 && this->server->join_attempts["_"] + 1 > config::voice::connectLimit) return command_result{error::server_join_rate_limit_reached}; this->server->join_attempts[inetAddr]++; this->server->join_attempts["_"]++; } TIMING_STEP(timings, "join atmp c"); if(!DatabaseHelper::assignDatabaseId(this->server->getSql(), this->server->getServerId(), _this.lock())) return command_result{error::vs_critical, "Could not assign database id!"}; TIMING_STEP(timings, "db assign "); this->server->getGroupManager()->enableCache(this->getClientDatabaseId()); TIMING_STEP(timings, "gr cache "); 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", "client_away", "client_away_message", "hwid", "myTeamspeakId", "acTime", "userPubKey", "authSign", "pubSign", "pubSignCert" }; 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; } } 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() > 50) 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::kMaxAfkMessageLength) return command_result{error::parameter_invalid_size, "client_away_message"}; } 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})}; } 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].as(); 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)) return command_result{error::server_invalid_password}; if(!config::server::clients::ignore_max_clone_permissions) { size_t clones_uid = 0; size_t clones_ip = 0; size_t clones_hwid = 0; 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()) clones_ip++; if(!_own_hwid.empty() && client->getHardwareId() == _own_hwid) clones_hwid++; }); 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]); return command_result{error:: client_too_many_clones_connected, "too many clones connected (uid)"}; } 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]); 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]); 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++; } auto maxClients = this->server->properties()[property::VIRTUALSERVER_MAXCLIENTS].as(); auto reserved = this->server->properties()[property::VIRTUALSERVER_RESERVED_SLOTS].as(); 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}; } else if(maxClients - (allowReserved ? 0 : reserved) <= count) return command_result{error::server_maxclients_reached}; TIMING_STEP(timings, "max clients"); auto old_last_connected = this->properties()[property::CLIENT_LASTCONNECTED].as(); this->properties()[property::CONNECTION_CLIENT_IP] = 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]++; { 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.lock()); 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(); }); 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 */ //Note: Client permissions are may not really void SpeakingClient::processJoin() { TIMING_START(timings); auto ref_server = this->server; this->resetIdleTime(); threads::MutexLock lock(this->command_lock); //Don't process any commands! 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.lock()); TIMING_STEP(timings, "server reg "); ref_server->getGroupManager()->cleanupAssignments(this->getClientDatabaseId()); TIMING_STEP(timings, "grp cleanup"); ref_server->getGroupManager()->update_server_group_property(_this.lock(), true, nullptr); TIMING_STEP(timings, "grp apply "); this->properties()[property::CLIENT_COUNTRY] = config::geo::countryFlag; if(geoloc::provider) { auto loc = this->isAddressV4() ? geoloc::provider->resolveInfoV4(this->getPeerIp(), false) : geoloc::provider->resolveInfoV6(this->getPeerIp(), false); if(loc) { debugMessage(this->getServerId(), "Client " + this->getDisplayName() + "|" + this->getLoggingPeerIp() + " comes from " + loc->name + "|" + loc->identifier); this->properties()[property::CLIENT_COUNTRY] = loc->identifier; } else { logError(ref_server ? ref_server->getServerId() : 0, "Could not resolve country for ip " + this->getLoggingPeerIp() + "|" + this->getDisplayName()); } } //this->updateChannelClientProperties(false); /* will be already updated via assignChannel */ if(ref_server->properties()[property::VIRTUALSERVER_HOSTMESSAGE_MODE].as() == 3 && !ref_server->properties()[property::VIRTUALSERVER_HOSTMESSAGE].as().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].as()); }).detach(); } TIMING_STEP(timings, "ip 2 loc as"); //IP to location assignment this->sendServerInit(); debugMessage(this->getServerId(), "Client id: " + to_string(this->getClientId())); TIMING_STEP(timings, "notify sini"); if(!ref_server->assignDefaultChannel(this->ref(), false)) { auto result = command_result{error::vs_critical, "Could not assign default channel!"}; this->notifyError(result); result.release_data(); this->close_connection(system_clock::now() + seconds(1)); return; } TIMING_STEP(timings, "assign chan"); this->sendChannelList(true); this->state = ConnectionState::CONNECTED; TIMING_STEP(timings, "send chan t"); /* trick the join method */ auto channel = this->currentChannel; this->currentChannel = nullptr; { /* enforce an update of these properties */ this->properties()[property::CLIENT_CHANNEL_GROUP_INHERITED_CHANNEL_ID] = "0"; this->properties()[property::CLIENT_CHANNEL_GROUP_ID] = "0"; this->properties()[property::CLIENT_TALK_POWER] = "0"; unique_lock server_channel_lock(this->server->channel_tree_lock); this->server->client_move(this->ref(), channel, nullptr, "", ViewReasonId::VREASON_USER_ACTION, false, server_channel_lock); if(this->getType() != ClientType::CLIENT_TEAMSPEAK) this->subscribeChannel({this->currentChannel}, false, true); /* su "improve" the TS3 clients join speed we send the channel clients a bit later, when the TS3 client gets his own client variables */ } TIMING_STEP(timings, "join move "); this->properties()->triggerAllModified(); this->notifyServerGroupList(); this->notifyChannelGroupList(); TIMING_STEP(timings, "notify grou"); logMessage(this->getServerId(), "Voice client {}/{} ({}) from {} joined.", this->getClientDatabaseId(), this->getUid(), this->getDisplayName(), this->getLoggingPeerIp() + ":" + to_string(this->getPeerPort()) ); this->connectTimestamp = chrono::system_clock::now(); this->idleTimestamp = chrono::system_clock::now(); TIMING_STEP(timings, "welcome msg"); { std::string message{}; config::server::clients::WelcomeMessageType type{config::server::clients::WELCOME_MESSAGE_TYPE_NONE}; if(this->getType() == ClientType::CLIENT_TEASPEAK) { message = config::server::clients::extra_welcome_message_teaspeak; type = config::server::clients::extra_welcome_message_type_teaspeak; } else if(this->getType() == ClientType::CLIENT_TEAMSPEAK) { message = config::server::clients::extra_welcome_message_teamspeak; type = config::server::clients::extra_welcome_message_type_teamspeak; } else if(this->getType() == ClientType::CLIENT_WEB) { message = config::server::clients::extra_welcome_message_teaweb; type = config::server::clients::extra_welcome_message_type_teaweb; } if(type == config::server::clients::WELCOME_MESSAGE_TYPE_POKE) { this->notifyClientPoke(this->server->serverRoot, message); } else if(type == config::server::clients::WELCOME_MESSAGE_TYPE_CHAT) { this->notifyTextMessage(ChatMessageMode::TEXTMODE_SERVER, this->server->serverRoot, 0, 0, std::chrono::system_clock::now(), message); } } debugMessage(this->getServerId(), "{} Client join timings: {}", CLIENT_STR_LOG_PREFIX, TIMING_FINISH(timings)); serverInstance->action_logger()->client_channel_logger.log_client_join(this->getServerId(), this->ref(), this->getChannelId(), this->currentChannel->name()); } void SpeakingClient::processLeave() { auto ownLock = _this.lock(); auto server = this->getServer(); auto channel = this->currentChannel; if(server){ 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_lock); server->unregisterClient(ownLock, "disconnected", server_channel_lock); /* already moves client to void if needed */ } server->groups->disableCache(ownLock->getClientDatabaseId()); server->musicManager->cleanup_client_bots(this->getClientDatabaseId()); //ref_server = nullptr; Removed caused nullptr exceptions } { //Delete own viewing clients /* * No need, are only weak references! threads::MutexLock l(this->viewLock); this->visibleClients.clear(); this->mutedClients.clear(); */ } } void SpeakingClient::triggerVoiceEnd() { this->properties()[property::CLIENT_FLAG_TALKING] = false; } void SpeakingClient::updateSpeak(bool onlyUpdate, const std::chrono::system_clock::time_point &now) { threads::MutexLock lock(this->speak_lock); if(this->speak_last_packet + this->speak_accuracy < now) { if(this->speak_last_packet > this->speak_begin) { if(!this->properties()[property::CLIENT_FLAG_TALKING].as()) this->properties()[property::CLIENT_FLAG_TALKING] = true; this->speak_time += duration_cast(this->speak_last_packet - this->speak_begin); } else { if(this->properties()[property::CLIENT_FLAG_TALKING].as()) this->properties()[property::CLIENT_FLAG_TALKING] = false; } this->speak_begin = now; this->speak_last_packet = now; } if(!onlyUpdate) this->speak_last_packet = now; } void SpeakingClient::tick(const std::chrono::system_clock::time_point &time) { ConnectedClient::tick(time); ALARM_TIMER(A1, "SpeakingClient::tick", milliseconds(2)); this->updateSpeak(true, time); if(this->state == ConnectionState::CONNECTED) { if(this->max_idle_time.has_value) { auto max_idle = this->max_idle_time.value; if(max_idle > 0 && this->idleTimestamp.time_since_epoch().count() > 0 && duration_cast(time - this->idleTimestamp).count() > max_idle) { this->server->notify_client_kick(this->ref(), this->server->getServerRoot(), ts::config::messages::idle_time_exceeded, nullptr); this->close_connection(system_clock::now() + seconds(1)); } } } } void SpeakingClient::updateChannelClientProperties(bool channel_lock, bool notify) { ConnectedClient::updateChannelClientProperties(channel_lock, notify); this->max_idle_time = this->calculate_permission(permission::i_client_max_idletime, this->currentChannel ? this->currentChannel->channelId() : 0); } command_result SpeakingClient::handleCommand(Command &command) { if(this->connectionState() == ConnectionState::INIT_HIGH) { if(this->handshake.state == HandshakeState::BEGIN || this->handshake.state == HandshakeState::IDENTITY_PROOF) { command_result result; if(command.command() == "handshakebegin") result.reset(this->handleCommandHandshakeBegin(command)); else if(command.command() == "handshakeindentityproof") result.reset(this->handleCommandHandshakeIdentityProof(command)); else result.reset(command_result{error::client_not_logged_in}); if(result.has_error()) this->postCommandHandler.push_back([&]{ this->close_connection(system_clock::now() + seconds(1)); }); return result; } } return ConnectedClient::handleCommand(command); }