793 lines
31 KiB
C++
793 lines
31 KiB
C++
|
#include <memory>
|
||
|
|
||
|
#include <misc/endianness.h>
|
||
|
#include <log/LogUtils.h>
|
||
|
#include <ThreadPool/Timer.h>
|
||
|
#include <regex>
|
||
|
#include <src/build.h>
|
||
|
#include <Properties.h>
|
||
|
#include "src/channel/ClientChannelView.h"
|
||
|
#include "SpeakingClient.h"
|
||
|
#include "src/InstanceHandler.h"
|
||
|
#include "StringVariable.h"
|
||
|
#include "src/music/MusicBotManager.h"
|
||
|
#include "misc/timer.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<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;
|
||
|
|
||
|
auto required_permission = this->cached_permission_value(permission::i_client_needed_whisper_power);
|
||
|
if(required_permission == permNotGranted)
|
||
|
return true;
|
||
|
|
||
|
auto granted_permission = sender->cached_permission_value(permission::i_client_whisper_power);
|
||
|
return granted_permission >= required_permission;
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
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<ConnectedClient>& client) {
|
||
|
if(client == this) return true;
|
||
|
auto speaking_client = dynamic_pointer_cast<SpeakingClient>(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<bool>();
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
for (const auto& client : target_clients) {
|
||
|
auto speaking_client = static_pointer_cast<SpeakingClient>(client);
|
||
|
speaking_client->send_voice_packet(pipes::buffer_view{buffer, data.length() + 2}, 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
|
||
|
};
|
||
|
|
||
|
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
|
||
|
};
|
||
|
|
||
|
//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& 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<shared_ptr<SpeakingClient>> available_clients;
|
||
|
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) {
|
||
|
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<bool>())
|
||
|
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<SpeakingClient>& 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<SpeakingClient>& target) {
|
||
|
return target->currentChannel != current_parent;
|
||
|
}), available_clients.end());
|
||
|
} else if(target == WhisperTarget::CHANNEL_ALL_PARENT) {
|
||
|
shared_ptr<BasicChannel> 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<SpeakingClient>& 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<BasicChannel> current = this->currentChannel;
|
||
|
available_clients.erase(std::remove_if(available_clients.begin(), available_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;
|
||
|
}), available_clients.end());
|
||
|
} else if(target == WhisperTarget::CHANNEL_COMPLETE_FAMILY) {
|
||
|
shared_ptr<BasicChannel> 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<SpeakingClient>& 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<BasicChannel> current = this->currentChannel;
|
||
|
available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const shared_ptr<SpeakingClient>& target) {
|
||
|
return target->currentChannel->parent() != current;
|
||
|
}), available_clients.end());
|
||
|
}
|
||
|
if(available_clients.empty()) 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){
|
||
|
if(cl->shouldReceiveVoiceWhisper(_this.lock()))
|
||
|
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);
|
||
|
|
||
|
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 : this->server->getClients()){ //Faster?
|
||
|
auto speakingClient = dynamic_pointer_cast<SpeakingClient>(cl);
|
||
|
if(!speakingClient || cl == this) continue;
|
||
|
if(!cl->currentChannel) continue;
|
||
|
|
||
|
auto clientChannelId = cl->currentChannel->channelId();
|
||
|
auto clientId = cl->getClientId();
|
||
|
|
||
|
for(uint8_t index = 0; index < clientCount; index++)
|
||
|
if(channelIds[index] == clientChannelId) goto handleSend;
|
||
|
for(uint8_t index = 0; index < channelCount; index++)
|
||
|
if(clientIds[index] == clientId) goto handleSend;
|
||
|
continue;
|
||
|
|
||
|
handleSend:
|
||
|
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<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 = [](){ \
|
||
|
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}$");
|
||
|
|
||
|
CommandResult SpeakingClient::handleCommandClientInit(Command& cmd) {
|
||
|
TIMING_START(timings);
|
||
|
if(!DatabaseHelper::assignDatabaseId(this->server->getSql(), this->server->getServerId(), _this.lock())) return {findError("vs_critical"), "Could not assign database id!"};
|
||
|
TIMING_STEP(timings, "db assign ");
|
||
|
this->server->getGroupManager()->enableCache(_this.lock());
|
||
|
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))
|
||
|
return {findError("parameter_invalid"), "invalid version or platform"};
|
||
|
}
|
||
|
|
||
|
const auto &info = property::info<property::ClientProperties>(key);
|
||
|
if(*info == property::CLIENT_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 {findError("parameter_invalid"), "Unknown value type for " + key};
|
||
|
|
||
|
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");
|
||
|
|
||
|
{
|
||
|
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 {findError("client_is_flooding"), "To many joins per second per ip"};
|
||
|
if(config::voice::connectLimit > 0 && this->server->join_attempts["_"] + 1 > config::voice::connectLimit)
|
||
|
return {findError("client_is_flooding"), "To many joins per second"};
|
||
|
this->server->join_attempts[inetAddr]++;
|
||
|
this->server->join_attempts["_"]++;
|
||
|
}
|
||
|
TIMING_STEP(timings, "join atmp c");
|
||
|
|
||
|
auto permissions_list = this->permissionValues(permission::PERMTEST_ORDERED, {
|
||
|
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
|
||
|
}, nullptr);
|
||
|
auto permissions = map<permission::PermissionType, permission::PermissionValue>(permissions_list.begin(), permissions_list.end());
|
||
|
TIMING_STEP(timings, "perm calc 1");
|
||
|
|
||
|
if(geoloc::provider_vpn && permissions[permission::b_client_ignore_vpn] == permNotGranted) {
|
||
|
auto provider = this->isAddressV4() ? geoloc::provider_vpn->resolveInfoV4(this->getPeerIp(), true) : geoloc::provider_vpn->resolveInfoV6(this->getPeerIp(), true);
|
||
|
if(provider)
|
||
|
return {findError(0xD01), 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 && (permissions[permission::b_client_enforce_valid_hwid] == permNotGranted && permissions[permission::b_client_enforce_valid_hwid] == 0)) {
|
||
|
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 {findError("parameter_invalid"), config::messages::kick_invalid_hardware_id};
|
||
|
}
|
||
|
}
|
||
|
TIMING_STEP(timings, "valid hw ip");
|
||
|
|
||
|
auto ignorePassword = permissions[permission::b_virtualserver_join_ignore_password] > 0 && permissions[permission::b_virtualserver_join_ignore_password] != permNotGranted;
|
||
|
if(!ignorePassword)
|
||
|
if(!this->server->verifyServerPassword(cmd["client_server_password"].string(), true)) return {findError("server_invalid_password"), "invalid server password"};
|
||
|
|
||
|
size_t clones_uid = 0;
|
||
|
size_t clones_ip = 0;
|
||
|
size_t clones_hwid = 0;
|
||
|
|
||
|
this->server->forEachClient([&](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(client->getHardwareId() == this->getHardwareId())
|
||
|
clones_hwid++;
|
||
|
});
|
||
|
|
||
|
if(permissions[permission::i_client_max_clones_uid] > 0 && 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 {findError("client_too_many_clones_connected"), ""};
|
||
|
}
|
||
|
|
||
|
if(permissions[permission::i_client_max_clones_ip] > 0 && 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 {findError("client_too_many_clones_connected"), ""};
|
||
|
}
|
||
|
|
||
|
if(permissions[permission::i_client_max_clones_hwid] > 0 && 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 {findError("client_too_many_clones_connected"), ""};
|
||
|
}
|
||
|
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 {findError(0xD01), 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))
|
||
|
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 = permissions[permission::b_client_use_reserved_slot] != permNotGranted && permissions[permission::b_client_use_reserved_slot] != 0;
|
||
|
if(reserved > maxClients){
|
||
|
if(!allowReserved) return {findError("server_maxclients_reached"), "server max clients reached"};
|
||
|
} else if(maxClients - (allowReserved ? 0 : reserved) <= count)
|
||
|
return {findError("server_maxclients_reached"), "server max clients 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](){
|
||
|
threads::MutexLock l1(self->disconnectLock);
|
||
|
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->closeConnection();
|
||
|
}
|
||
|
}).detach();
|
||
|
});
|
||
|
|
||
|
debugMessage(this->getServerId(), "{} Client init timings: {}", CLIENT_STR_LOG_PREFIX, TIMING_FINISH(timings));
|
||
|
return CommandResult::Success;
|
||
|
}
|
||
|
|
||
|
/* 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);
|
||
|
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)) {
|
||
|
this->notifyError({findError("vs_critical"), "Could not assign default channel!"});
|
||
|
this->closeConnection(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_SERVERGROUPS] = "0";
|
||
|
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);
|
||
|
this->subscribeChannel({this->currentChannel}, false, true);
|
||
|
}
|
||
|
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();
|
||
|
|
||
|
debugMessage(this->getServerId(), "{} Client join timings: {}", CLIENT_STR_LOG_PREFIX, TIMING_FINISH(timings));
|
||
|
}
|
||
|
|
||
|
void SpeakingClient::processLeave() {
|
||
|
auto ownLock = _this.lock();
|
||
|
auto server = this->getServer();
|
||
|
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);
|
||
|
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<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(!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 != permNotGranted)
|
||
|
if(this->idleTimestamp.time_since_epoch().count() > 0 && this->max_idle_time > 0 && duration_cast<seconds>(time - this->idleTimestamp).count() > this->max_idle_time) {
|
||
|
this->server->notify_client_kick(this->ref(), this->server->getServerRoot(), ts::config::messages::idle_time_exceeded, nullptr);
|
||
|
this->closeConnection(system_clock::now() + seconds(1));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void SpeakingClient::updateChannelClientProperties(bool channel_lock, bool notify) {
|
||
|
ConnectedClient::updateChannelClientProperties(channel_lock, notify);
|
||
|
this->max_idle_time = this->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_max_idletime, this->currentChannel);
|
||
|
}
|
||
|
|
||
|
CommandResult SpeakingClient::handleCommand(Command &command) {
|
||
|
if(this->connectionState() == ConnectionState::INIT_HIGH) {
|
||
|
if(this->handshake.state == HandshakeState::BEGIN || this->handshake.state == HandshakeState::IDENTITY_PROOF) {
|
||
|
CommandResult result;
|
||
|
if(command.command() == "handshakebegin")
|
||
|
result = this->handleCommandHandshakeBegin(command);
|
||
|
else if(command.command() == "handshakeindentityproof")
|
||
|
result = this->handleCommandHandshakeIdentityProof(command);
|
||
|
else
|
||
|
result = {findError("client_not_logged_in")};
|
||
|
|
||
|
if(!result)
|
||
|
this->closeConnection(system_clock::now() + seconds(1));
|
||
|
return result;
|
||
|
}
|
||
|
}
|
||
|
return ConnectedClient::handleCommand(command);
|
||
|
}
|