#include "SpeakingClient.h" #include #include #include #include #include #include #include #if defined(TCP_CORK) && !defined(TCP_NOPUSH) #define TCP_NOPUSH TCP_CORK #endif using namespace std; using namespace std::chrono; using namespace ts; using namespace ts::server; using namespace ts::protocol; void free_ecc(ecc_key* key) { if(!key) return; ecc_free(key); delete key; } CommandResult SpeakingClient::handleCommandHandshakeBegin(Command& cmd) { //If !result than the connection will be closed! if(this->handshake.state != HandshakeState::BEGIN) return {findError("web_handshake_invalid"), "invalid connection state!"}; auto intention = cmd["intention"].as(); if(intention != 0) return {findError("web_handshake_unsupported"), ""}; auto authenticationMethod = cmd["authentication_method"].as(); if(authenticationMethod == IdentityType::TEAMSPEAK) { this->handshake.identityType = IdentityType::TEAMSPEAK; auto identity = base64::decode(cmd["publicKey"]); this->properties()[property::CLIENT_UNIQUE_IDENTIFIER] = base64::encode(digest::sha1(cmd["publicKey"].string())); this->handshake.identityKey = shared_ptr(new ecc_key{}, free_ecc); if(ecc_import((u_char*) identity.data(), identity.length(), this->handshake.identityKey.get()) != CRYPT_OK) { this->handshake.identityKey = nullptr; return {findError("web_handshake_invalid"), "invalid ecc key state!"}; } auto message = "TeaSpeak, made with love and coffee by WolverinDEV (#" + base64::encode(rnd_string(32)) + ")"; this->handshake.proof_message = digest::sha256(message); this->sendCommand(Command("handshakeidentityproof", { {"message",message}, {"digest", "SHA-256"} })); this->handshake.state = HandshakeState::IDENTITY_PROOF; } else if(authenticationMethod == IdentityType::TEASPEAK_FORUM) { this->handshake.identityType = IdentityType::TEASPEAK_FORUM; try { this->handshake.identityData = make_shared(); this->handshake.proof_message = cmd["data"].string(); std::string error{}; Json::CharReaderBuilder rbuilder{}; const std::unique_ptr reader(rbuilder.newCharReader()); auto& json_str = this->handshake.proof_message; if(!reader->parse(json_str.data(), json_str.data() + json_str.size(), &*this->handshake.identityData, &error)) { debugMessage(this->getServerId(), "[{}] Failed to parse forum account data: {}", error); return {findError("web_handshake_invalid"), "invalid json!"}; } auto& json_data = *this->handshake.identityData; if(json_data["user_id"].isNull()) return {findError("web_handshake_invalid"), "Missing json data (user_id)!"}; if(json_data["user_name"].isNull()) return {findError("web_handshake_invalid"), "Missing json data (user_name)!"}; if(json_data["user_group"].isNull()) return {findError("web_handshake_invalid"), "Missing json data (user_group)!"}; if(json_data["user_groups"].isNull()) return {findError("web_handshake_invalid"), "Missing json data (user_groups)!"}; if(json_data["data_age"].isNull()) return {findError("web_handshake_invalid"), "Missing json data (data_age)!"}; //Type test json_data["user_id"].asInt64(); if(json_data["data_age"].asUInt64() < duration_cast((system_clock::now() - hours(72)).time_since_epoch()).count()) return {findError("web_handshake_invalid"), "Provided data is too old!"}; this->properties()[property::CLIENT_UNIQUE_IDENTIFIER] = base64::encode(digest::sha1("TeaSpeak-Forum#" + json_data["user_id"].asString())); this->properties()[property::CLIENT_TEAFORO_ID] = json_data["user_id"].asInt64(); this->properties()[property::CLIENT_TEAFORO_NAME] = json_data["user_name"].asString(); { ///* 0x01 := Banned | 0x02 := Stuff | 0x04 := Premium */ uint64_t flags = 0; if(json_data["is_banned"].isBool() && json_data["is_banned"].asBool()) flags |= 0x01U; if(json_data["is_staff"].isBool() && json_data["is_staff"].asBool()) flags |= 0x02U; if(json_data["is_premium"].isBool() && json_data["is_premium"].asBool()) flags |= 0x04U; this->properties()[property::CLIENT_TEAFORO_FLAGS] = flags; } } catch (Json::Exception& exception) { return {findError("web_handshake_invalid"), "invalid json!"}; } this->sendCommand(Command("handshakeidentityproof")); this->handshake.state = HandshakeState::IDENTITY_PROOF; } else if(authenticationMethod == IdentityType::NICKNAME) { if(!config::server::authentication::name) return {findError("web_handshake_identity_unsupported"), "Name authentication has been disabled"}; this->handshake.state = HandshakeState::SUCCEEDED; this->handshake.identityType = IdentityType::NICKNAME; this->properties()[property::CLIENT_UNIQUE_IDENTIFIER] = base64::encode(digest::sha1("UserName#" + cmd["client_nickname"].string())); } else { return {findError("web_handshake_identity_unsupported"), ""}; } return CommandResult::Success; } CommandResult SpeakingClient::handleCommandHandshakeIdentityProof(Command& cmd) { if(this->handshake.state != HandshakeState::IDENTITY_PROOF) return {findError("web_handshake_invalid"), "invalid connection state!"}; if(this->handshake.identityType == IdentityType::TEASPEAK_FORUM) { auto encodedProof = cmd["proof"].string(); auto proof = base64::decode(encodedProof); auto key = serverInstance->sslManager()->getRsaKey("teaforo_sign"); if(!key) return {findError("web_handshake_identity_unsupported"), "Missing server public key!"}; if(!serverInstance->sslManager()->verifySign(key, this->handshake.proof_message, proof)) return {findError("web_handshake_identity_proof_failed"), ""}; this->properties()[property::CLIENT_TEAFORO_ID] = (int64_t) (*this->handshake.identityData)["user_id"].asInt64(); this->properties()[property::CLIENT_TEAFORO_NAME] = (*this->handshake.identityData)["user_name"].asString(); this->handshake.state = HandshakeState::SUCCEEDED; } else if(this->handshake.identityType == IdentityType::TEAMSPEAK) { auto proof = base64::decode(cmd["proof"]); int result; if(ecc_verify_hash((u_char*) proof.data(), proof.length(), (u_char*) this->handshake.proof_message.data(), this->handshake.proof_message.length(), &result, this->handshake.identityKey.get()) != CRYPT_OK) return {findError("web_handshake_identity_proof_failed"), ""}; if(!result) return {findError("web_handshake_identity_proof_failed"), ""}; this->handshake.state = HandshakeState::SUCCEEDED; } else return {findError("web_handshake_invalid"), "identity isn't required to proof authentication"}; return CommandResult::Success; }