122 lines
5.8 KiB
C++
122 lines
5.8 KiB
C++
|
|
#include "SpeakingClient.h"
|
||
|
|
#include <misc/endianness.h>
|
||
|
|
#include <src/ServerManager.h>
|
||
|
|
#include <netinet/tcp.h>
|
||
|
|
#include <src/InstanceHandler.h>
|
||
|
|
#include <misc/base64.h>
|
||
|
|
#include <misc/digest.h>
|
||
|
|
#include <misc/rnd.h>
|
||
|
|
|
||
|
|
#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<int>();
|
||
|
|
if(intention != 0)
|
||
|
|
return {findError("web_handshake_unsupported"), ""};
|
||
|
|
|
||
|
|
auto authenticationMethod = cmd["authentication_method"].as<int>();
|
||
|
|
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<ecc_key>(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<Json::Value>();
|
||
|
|
this->handshake.proof_message = cmd["data"].string();
|
||
|
|
Json::Reader reader;
|
||
|
|
std::istringstream stream(this->handshake.proof_message);
|
||
|
|
reader.parse(stream, *this->handshake.identityData);
|
||
|
|
if(!stream) return {findError("web_handshake_invalid"), "invalid json!"};
|
||
|
|
|
||
|
|
if((*this->handshake.identityData)["user_id"].isNull()) return {findError("web_handshake_invalid"), "Missing json data!"};
|
||
|
|
if((*this->handshake.identityData)["user_name"].isNull()) return {findError("web_handshake_invalid"), "Missing json data!"};
|
||
|
|
if((*this->handshake.identityData)["user_group"].isNull()) return {findError("web_handshake_invalid"), "Missing json data!"};
|
||
|
|
if((*this->handshake.identityData)["user_groups"].isNull()) return {findError("web_handshake_invalid"), "Missing json data!"};
|
||
|
|
if((*this->handshake.identityData)["data_age"].isNull()) return {findError("web_handshake_invalid"), "Missing json data!"};
|
||
|
|
|
||
|
|
//Type test
|
||
|
|
(*this->handshake.identityData)["user_id"].asInt64();
|
||
|
|
|
||
|
|
if((*this->handshake.identityData)["data_age"].asUInt64() < duration_cast<milliseconds>((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#" + (*this->handshake.identityData)["user_id"].asString()));
|
||
|
|
} 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;
|
||
|
|
}
|