Teaspeak-Server/server/src/client/SpeakingClient.cpp

1025 lines
46 KiB
C++

#include <memory>
#include <PermissionManager.h>
#include <misc/endianness.h>
#include <log/LogUtils.h>
#include <ThreadPool/Timer.h>
#include <regex>
#include <src/build.h>
#include <Properties.h>
#include <src/client/command_handler/helpers.h>
#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
constexpr static auto kMaxWhisperClientNameLength{30};
constexpr static auto kWhisperClientUniqueIdLength{28}; /* base64 encoded SHA1 hash */
constexpr static auto kWhisperMaxHeaderLength{2 + 2 + 1 + 2 + kWhisperClientUniqueIdLength + 1 + kMaxWhisperClientNameLength};
SpeakingClient::SpeakingClient(sql::SqlManager *a, const std::shared_ptr<VirtualServer> &b) : ConnectedClient(a, b) {
speak_begin = std::chrono::system_clock::now();
speak_last_packet = std::chrono::system_clock::now();
};
SpeakingClient::~SpeakingClient() {
if(auto server{this->server}; this->rtc_client_id > 0 && server) {
server->rtc_server().destroy_client(this->rtc_client_id);
}
}
bool SpeakingClient::shouldReceiveVoice(const std::shared_ptr<ConnectedClient> &sender) {
//if(this->properties()[property::CLIENT_AWAY].as<bool>()) return false;
if(!this->properties()[property::CLIENT_OUTPUT_HARDWARE].as<bool>()) return false;
if(this->properties()[property::CLIENT_OUTPUT_MUTED].as<bool>()) 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<ConnectedClient> &sender) {
if(!this->shouldReceiveVoice(sender))
return false;
return permission::v2::permission_granted(this->cpmerission_needed_whisper_power, sender->cpmerission_whisper_power, false);
}
bool SpeakingClient::should_handle_voice_packet(size_t) {
auto current_channel = this->currentChannel;
if(!current_channel) { return false; }
if(!this->allowedToTalk) { return false; }
this->updateSpeak(false, system_clock::now());
this->resetIdleTime();
return true;
}
//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 = 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 := <server group id>
//Channel group => type := CHANNEL_GROUP and target_id := <channel group id>
//Channel commander => type := CHANNEL_COMMANDER and target_id := 0
void SpeakingClient::handlePacketVoiceWhisper(const pipes::buffer_view& payload, bool new_packet, bool head) {
if(payload.length() < 5) {
this->disconnect("Invalid packet (Voice whisper)");
logMessage(this->getServerId(), "{} Tried to send a too short whisper packet. Length: {}", CLIENT_STR_LOG_PREFIX, payload.length());
return;
}
uint16_t payload_offset{0};
auto voice_packet_id = be2le16((char*) payload.data_ptr(), payload_offset, &payload_offset);
auto voice_codec = (uint8_t) payload[payload_offset++];
std::deque<std::shared_ptr<SpeakingClient>> target_clients;
if(new_packet) {
if(payload.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, payload.length());
return;
}
auto type = (WhisperType) payload[payload_offset++];
auto target = (WhisperTarget) payload[payload_offset++];
auto type_id = be2le64((char*) payload.data_ptr(), payload_offset, &payload_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
if(type == WhisperType::ECHO) {
target_clients.push_back(dynamic_pointer_cast<SpeakingClient>(this->ref()));
} else {
for(const auto& client : this->server->getClients()) {
auto speakingClient = dynamic_pointer_cast<SpeakingClient>(client);
if(!speakingClient || client == this) continue;
if(!speakingClient->currentChannel) continue;
if(type == WhisperType::ALL) {
target_clients.push_back(speakingClient);
} else if(type == WhisperType::SERVER_GROUP) {
if(type_id == 0)
target_clients.push_back(speakingClient);
else {
shared_lock client_lock(this->channel_lock);
for(const auto& id : client->cached_server_groups) {
if(id == type_id) {
target_clients.push_back(speakingClient);
break;
}
}
}
} else if(type == WhisperType::CHANNEL_GROUP) {
if(client->cached_channel_group == type_id)
target_clients.push_back(speakingClient);
} else if(type == WhisperType::CHANNEL_COMMANDER) {
if(client->properties()[property::CLIENT_IS_CHANNEL_COMMANDER].as<bool>())
target_clients.push_back(speakingClient);
}
}
if(target == WhisperTarget::CHANNEL_CURRENT) {
target_clients.erase(std::remove_if(target_clients.begin(), target_clients.end(), [&](const shared_ptr<SpeakingClient>& target) {
return target->currentChannel != this->currentChannel;
}), target_clients.end());
} else if(target == WhisperTarget::CHANNEL_PARENT) {
auto current_parent = this->currentChannel->parent();
if(!current_parent) return;
target_clients.erase(std::remove_if(target_clients.begin(), target_clients.end(), [&](const shared_ptr<SpeakingClient>& target) {
return target->currentChannel != current_parent;
}), target_clients.end());
} else if(target == WhisperTarget::CHANNEL_ALL_PARENT) {
shared_ptr<BasicChannel> current_parent;
{
current_parent = this->currentChannel->parent();
if(!current_parent) return;
}
target_clients.erase(std::remove_if(target_clients.begin(), target_clients.end(), [&](const shared_ptr<SpeakingClient>& target) {
auto tmp_parent = current_parent;
while(tmp_parent && tmp_parent != target->currentChannel)
tmp_parent = tmp_parent->parent();
return target->currentChannel != tmp_parent;
}), target_clients.end());
} else if(target == WhisperTarget::CHANNEL_FAMILY) {
shared_ptr<BasicChannel> current = this->currentChannel;
target_clients.erase(std::remove_if(target_clients.begin(), target_clients.end(), [&](const shared_ptr<SpeakingClient>& target) {
auto tmp_current = target->currentChannel;
while(tmp_current && tmp_current != current)
tmp_current = tmp_current->parent();
return current != tmp_current;
}), target_clients.end());
} else if(target == WhisperTarget::CHANNEL_COMPLETE_FAMILY) {
shared_ptr<BasicChannel> current = this->currentChannel;
while(current && current->parent()) current = current->parent();
target_clients.erase(std::remove_if(target_clients.begin(), target_clients.end(), [&](const shared_ptr<SpeakingClient>& target) {
auto tmp_current = target->currentChannel;
while(tmp_current && tmp_current != current)
tmp_current = tmp_current->parent();
return current != tmp_current;
}), target_clients.end());
} else if(target == WhisperTarget::CHANNEL_SUBCHANNELS) {
shared_ptr<BasicChannel> current = this->currentChannel;
target_clients.erase(std::remove_if(target_clients.begin(), target_clients.end(), [&](const shared_ptr<SpeakingClient>& target) {
return target->currentChannel->parent() != current;
}), target_clients.end());
}
auto self_lock = this->_this.lock();
target_clients.erase(std::remove_if(target_clients.begin(), target_clients.end(), [&](const std::shared_ptr<ConnectedClient>& cl) {
auto speakingClient = dynamic_pointer_cast<SpeakingClient>(cl);
return !speakingClient->shouldReceiveVoiceWhisper(self_lock);
}), target_clients.end());
if(target_clients.empty()) {
if(update_whisper_error(this->speak_last_no_whisper_target)) {
command_result result{error::whisper_no_targets};
this->notifyError(result);
}
return;
}
if(target_clients.size() > this->server->properties()[property::VIRTUALSERVER_MIN_CLIENTS_IN_CHANNEL_BEFORE_FORCED_SILENCE].as_save<size_t>()) {
if(update_whisper_error(this->speak_last_too_many_whisper_targets)) {
command_result result{error::whisper_too_many_targets};
this->notifyError(result);
}
return;
}
}
} else {
auto channelCount = (uint8_t) payload[payload_offset++];
auto clientCount = (uint8_t) payload[payload_offset++];
if(payload.length() < 5 + clientCount * 2 + channelCount * 8) {
logMessage(this->getServerId(), "{} Tried to send a too short whisper packet. Length: {} Required: {}", CLIENT_STR_LOG_PREFIX, payload.length(), to_string(5 + channelCount * 2 + clientCount * 8));
return;
}
ChannelId channelIds[channelCount];
ClientId clientIds[clientCount];
for(uint8_t index = 0; index < channelCount; index++) {
channelIds[index] = be2le64((char*) payload.data_ptr(), payload_offset, &payload_offset);
}
for(uint8_t index = 0; index < clientCount; index++) {
clientIds[index] = be2le16((char*) payload.data_ptr(), payload_offset, &payload_offset);
}
#ifdef PKT_LOG_WHISPER
logTrace(this->getServerId(), "{} Whisper data length: {}. Client count: {}. Channel count: {}.", CLIENT_STR_LOG_PREFIX, dataLength, clientCount, channelCount);
#endif
for(const auto& client : this->server->getClients()) {
auto speaking_client = dynamic_pointer_cast<SpeakingClient>(client);
if(!speaking_client || client == this || !speaking_client->currentChannel)
continue;
auto clientChannelId = speaking_client->getChannelId();
auto clientId = speaking_client->getClientId();
for(uint8_t index = 0; index < channelCount; index++) {
if(channelIds[index] == clientChannelId) {
goto add_client;
}
}
for(uint8_t index = 0; index < clientCount; index++) {
if(clientIds[index] == clientId) {
goto add_client;
}
}
continue;
add_client:
if(!speaking_client->shouldReceiveVoiceWhisper(this->ref())) {
continue;
}
target_clients.push_back(speaking_client);
}
}
if(target_clients.empty()) {
if(update_whisper_error(this->speak_last_no_whisper_target)) {
command_result result{error::whisper_no_targets};
this->notifyError(result);
}
return;
}
if(target_clients.size() > this->server->properties()[property::VIRTUALSERVER_MIN_CLIENTS_IN_CHANNEL_BEFORE_FORCED_SILENCE].as_save<size_t>()) {
if(update_whisper_error(this->speak_last_too_many_whisper_targets)) {
command_result result{error::whisper_too_many_targets};
this->notifyError(result);
}
return;
}
/* send the packet */
{
size_t voice_payload_length = payload.length() - payload_offset;
//Create the packet data
char whisper_packet_buffer[kWhisperMaxHeaderLength + voice_payload_length];
size_t whisper_packet_offset{0};
size_t whisper_packet_teamspeak_offset{0};
/* writing the teaspeak header */
if(head) {
auto uniqueId = this->getUid();
auto nickname = this->getDisplayName();
if(uniqueId.length() > kWhisperClientUniqueIdLength) {
logCritical(LOG_GENERAL, "Clients unique id is longer than the expected max length of {}. Unique length: {}", kWhisperClientUniqueIdLength, uniqueId.length());
return;
}
if(nickname.length() > kMaxWhisperClientNameLength) {
logCritical(LOG_GENERAL, "Clients name is longer than the expected max length of {}. Name length: {}", kMaxWhisperClientNameLength, nickname.length());
return;
}
memset(whisper_packet_buffer + whisper_packet_offset, 0, kWhisperClientUniqueIdLength);
memcpy(whisper_packet_buffer + whisper_packet_offset, uniqueId.data(), uniqueId.length());
whisper_packet_offset += kWhisperClientUniqueIdLength;
whisper_packet_buffer[whisper_packet_offset++] = nickname.length();
memcpy(whisper_packet_buffer + whisper_packet_offset, nickname.data(), nickname.length());
whisper_packet_offset += nickname.length();
}
/* writing the "normal" header and payload */
{
whisper_packet_teamspeak_offset = whisper_packet_offset;
*(uint16_t*) &whisper_packet_buffer[whisper_packet_offset] = htons(voice_packet_id);
whisper_packet_offset += 2;
*(uint16_t*) &whisper_packet_buffer[whisper_packet_offset] = htons(this->getClientId());
whisper_packet_offset += 2;
whisper_packet_buffer[whisper_packet_offset++] = voice_codec;
if(voice_payload_length > 0) {
memcpy(&whisper_packet_buffer[whisper_packet_offset], &payload[payload_offset], voice_payload_length);
whisper_packet_offset += voice_payload_length;
}
}
VoicePacketFlags flags{};
flags.head = head;
pipes::buffer_view teaspeak_packet{}, teamspeak_packet{};
teaspeak_packet = pipes::buffer_view{whisper_packet_buffer, whisper_packet_offset};
teamspeak_packet = pipes::buffer_view{whisper_packet_buffer + whisper_packet_teamspeak_offset, whisper_packet_offset - whisper_packet_teamspeak_offset};
auto self_ref = this->ref();
for(const auto& cl : target_clients) {
if(cl == self_ref || cl->shouldReceiveVoiceWhisper(self_ref)) {
cl->send_voice_whisper_packet(teamspeak_packet, teaspeak_packet, flags);
}
}
}
this->resetIdleTime();
this->updateSpeak(false, std::chrono::system_clock::now());
}
#define TEST_PARM(type) \
do {\
if(!cmd[0][key].castable<type>())\
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<threads::Mutex> 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<string> 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() > 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<property::ClientProperties>(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<string>()))
return command_result{error::parameter_invalid};
this->properties()[info] = cmd[key].as<std::string>();
}
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<permission::PermissionType, permission::v2::PermissionFlaggedValue>(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<string>();
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<ConnectedClient>& 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<chrono::seconds>(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<size_t>();
auto reserved = this->server->properties()[property::VIRTUALSERVER_RESERVED_SLOTS].as<size_t>();
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<int64_t>();
this->properties()[property::CONNECTION_CLIENT_IP] = this->getLoggingPeerIp();
this->properties()[property::CLIENT_LASTCONNECTED] = std::chrono::duration_cast<std::chrono::seconds>(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<SpeakingClient>(_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());
if(this->rtc_client_id) {
/* in case of client reconnect */
this->server->rtc_server().destroy_client(this->rtc_client_id);
}
if(this->getType() == ClientType::CLIENT_TEAMSPEAK) {
this->rtc_client_id = this->server->rtc_server().create_native_client(dynamic_pointer_cast<SpeakingClient>(this->ref()));
} else if(this->getType() == ClientType::CLIENT_TEASPEAK) {
/* TODO: Will be a RTP client later on, just without audio */
this->rtc_client_id = this->server->rtc_server().create_native_client(dynamic_pointer_cast<SpeakingClient>(this->ref()));
} else if(this->getType() == ClientType::CLIENT_WEB) {
this->rtc_client_id = this->server->rtc_server().create_rtp_client(dynamic_pointer_cast<SpeakingClient>(this->ref()));
}
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<int>() == 3 && !ref_server->properties()[property::VIRTUALSERVER_HOSTMESSAGE].as<string>().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<string>());
}).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->music_manager_->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 only_update, const std::chrono::system_clock::time_point &now) {
std::lock_guard speak_lock{this->speak_mutex};
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<bool>()) {
this->properties()[property::CLIENT_FLAG_TALKING] = true;
}
this->speak_time += duration_cast<milliseconds>(this->speak_last_packet - this->speak_begin);
} else {
if(this->properties()[property::CLIENT_FLAG_TALKING].as<bool>()) {
this->properties()[property::CLIENT_FLAG_TALKING] = false;
}
}
this->speak_begin = now;
this->speak_last_packet = now;
}
if(!only_update) {
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<seconds>(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;
}
} else if(this->connectionState() == ConnectionState::CONNECTED) {
if(command.command() == "rtcsessiondescribe") {
return this->handleCommandRtcSessionDescribe(command);
} else if(command.command() == "rtcicecandidate") {
return this->handleCommandRtcIceCandidate(command);
} else if(command.command() == "rtcbroadcast") {
return this->handleCommandRtcBroadcast(command);
} else if(command.command() == "rtcsessionreset") {
return this->handleCommandRtcSessionReset(command);
}
}
return ConnectedClient::handleCommand(command);
}
command_result SpeakingClient::handleCommandRtcSessionDescribe(Command &command) {
CMD_REQ_SERVER;
CMD_CHK_AND_INC_FLOOD_POINTS(15);
uint32_t mode;
if(command["mode"].string() == "offer") {
mode = 1;
} else if(command["mode"].string() == "answer") {
mode = 2;
} else {
return command_result{error::parameter_invalid, "mode"};
}
std::string error{};
if(!this->server->rtc_server().apply_remote_description(error, this->rtc_client_id, mode, command["sdp"])) {
return command_result{error::vs_critical, error};
}
if(mode == 1) {
std::string result{};
if(!this->server->rtc_server().generate_local_description(this->rtc_client_id, result)) {
return command_result{error::vs_critical, result};
} else {
ts::command_builder notify{"notifyrtcsessiondescription"};
notify.put_unchecked(0, "mode", "answer");
notify.put_unchecked(0, "sdp", result);
this->sendCommand(notify);
}
}
return command_result{error::ok};
}
command_result SpeakingClient::handleCommandRtcSessionReset(Command &command) {
CMD_REQ_SERVER;
CMD_CHK_AND_INC_FLOOD_POINTS(15);
this->server->rtc_server().reset_rtp_session(this->rtc_client_id);
return command_result{error::ok};
}
command_result SpeakingClient::handleCommandRtcIceCandidate(Command &command) {
CMD_REQ_SERVER;
std::string error;
if(command[0].has("candidate")) {
auto candidate = command["candidate"].string();
if(!this->server->rtc_server().add_ice_candidate(error, this->rtc_client_id, command["media_line"], candidate)) {
return command_result{error::vs_critical, error};
}
} else {
this->server->rtc_server().ice_candidates_finished(this->rtc_client_id);
}
return command_result{error::ok};
}
command_result SpeakingClient::handleCommandRtcBroadcast(Command &command) {
CMD_REQ_SERVER;
CMD_CHK_AND_INC_FLOOD_POINTS(15);
/* TODO: Filter out duplicates */
std::vector<std::tuple<uint8_t, uint32_t>> broadcasts{};
broadcasts.reserve(command.bulkCount());
for(size_t index{0}; index < command.bulkCount(); index++) {
auto& bulk = command[index];
broadcasts.push_back(std::make_tuple(bulk["type"], bulk.has("ssrc") ? bulk["ssrc"].as<uint32_t>() : (uint32_t) 0));
}
ts::command_result_bulk result{};
for(size_t index{0}; index < command.bulkCount(); index++) {
auto broadcast_result = this->server->rtc_server().start_broadcast(this->rtc_client_id, std::get<0>(broadcasts[index]), std::get<1>(broadcasts[index]));
switch(broadcast_result) {
case rtc::BroadcastStartResult::Success:
result.emplace_result(error::ok);
break;
case rtc::BroadcastStartResult::InvalidBroadcastType:
result.emplace_result(error::parameter_invalid, "type");
break;
case rtc::BroadcastStartResult::InvalidStreamId:
result.emplace_result(error::rtc_missing_target_channel);
break;
case rtc::BroadcastStartResult::ClientHasNoChannel:
result.emplace_result(error::vs_critical, "no channel");
break;
case rtc::BroadcastStartResult::InvalidClient:
result.emplace_result(error::vs_critical, "invalid client");
break;
case rtc::BroadcastStartResult::UnknownError:
default:
result.emplace_result(error::vs_critical, "unknown error");
}
}
return ts::command_result{std::move(result)};
}