server/src/game/Warden/Warden.cpp

382 lines
11 KiB
C++

/**
* MaNGOS is a full featured server for World of Warcraft, supporting
* the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8
*
* Copyright (C) 2005-2020 MaNGOS <https://getmangos.eu>
* Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* World of Warcraft, and all World of Warcraft or Warcraft art, images,
* and lore are copyrighted by Blizzard Entertainment, Inc.
*/
#include "Common.h"
#include "WorldPacket.h"
#include "WorldSession.h"
#include "Log.h"
#include "Opcodes.h"
#include "ByteBuffer.h"
#include <openssl/sha.h>
#include "World.h"
#include "Util.h"
#include "Warden.h"
#include "AccountMgr.h"
Warden::Warden() : _session(NULL), _inputCrypto(16), _outputCrypto(16), _checkTimer(10000/*10 sec*/), _clientResponseTimer(0),
_module(NULL), _state(WardenState::STATE_INITIAL)
{
memset(_inputKey, 0, sizeof(_inputKey));
memset(_outputKey, 0, sizeof(_outputKey));
memset(_seed, 0, sizeof(_seed));
_previousTimestamp = WorldTimer::getMSTime();
}
Warden::~Warden()
{
delete[] _module->CompressedData;
delete _module;
_module = NULL;
}
void Warden::InitializeModule()
{
SetNewState(WardenState::STATE_INITIALIZE_MODULE);
}
void Warden::RequestHash()
{
sLog.outWarden("Request hash");
// Create packet structure
WardenHashRequest Request;
Request.Command = WARDEN_SMSG_HASH_REQUEST;
memcpy(Request.Seed, _seed, 16);
// Encrypt with warden RC4 key.
EncryptData((uint8*)&Request, sizeof(WardenHashRequest));
WorldPacket pkt(SMSG_WARDEN_DATA, sizeof(WardenHashRequest));
pkt.append((uint8*)&Request, sizeof(WardenHashRequest));
_session->SendPacket(&pkt);
SetNewState(WardenState::STATE_REQUESTED_HASH);
}
void Warden::SendModuleToClient()
{
sLog.outWarden("Send module to client");
// Create packet structure
WardenModuleTransfer packet;
uint32 sizeLeft = _module->CompressedSize;
uint32 pos = 0;
uint16 burstSize;
while (sizeLeft > 0)
{
burstSize = sizeLeft < 500 ? sizeLeft : 500;
packet.Command = WARDEN_SMSG_MODULE_CACHE;
packet.DataSize = burstSize;
memcpy(packet.Data, &_module->CompressedData[pos], burstSize);
sizeLeft -= burstSize;
pos += burstSize;
EncryptData((uint8*)&packet, burstSize + 3);
WorldPacket pkt1(SMSG_WARDEN_DATA, burstSize + 3);
pkt1.append((uint8*)&packet, burstSize + 3);
_session->SendPacket(&pkt1);
}
SetNewState(WardenState::STATE_SENT_MODULE);
}
void Warden::RequestModule()
{
sLog.outWarden("Request module");
// Create packet structure
WardenModuleUse request;
request.Command = WARDEN_SMSG_MODULE_USE;
memcpy(request.ModuleId, _module->Id, 16);
memcpy(request.ModuleKey, _module->Key, 16);
request.Size = _module->CompressedSize;
// Encrypt with warden RC4 key.
EncryptData((uint8*)&request, sizeof(WardenModuleUse));
WorldPacket pkt(SMSG_WARDEN_DATA, sizeof(WardenModuleUse));
pkt.append((uint8*)&request, sizeof(WardenModuleUse));
_session->SendPacket(&pkt);
SetNewState(WardenState::STATE_REQUESTED_MODULE);
}
void Warden::Update()
{
uint32 currentTimestamp = WorldTimer::getMSTime();
uint32 diff = currentTimestamp - _previousTimestamp;
_previousTimestamp = currentTimestamp;
switch (_state)
{
case WardenState::STATE_INITIAL:
break;
case WardenState::STATE_REQUESTED_MODULE:
case WardenState::STATE_SENT_MODULE:
case WardenState::STATE_REQUESTED_HASH:
case WardenState::STATE_REQUESTED_DATA:
{
uint32 maxClientResponseDelay = sWorld.getConfig(CONFIG_UINT32_WARDEN_CLIENT_RESPONSE_DELAY);
if (maxClientResponseDelay > 0)
{
// Kick player if client response delays more than set in config
if (_clientResponseTimer > maxClientResponseDelay * IN_MILLISECONDS)
{
sLog.outWarden("%s (latency: %u, IP: %s) exceeded Warden module response delay on state %s for more than %s - disconnecting client",
_session->GetPlayerName(), _session->GetLatency(), _session->GetRemoteAddress().c_str(), WardenState::to_string(_state), secsToTimeString(maxClientResponseDelay, true).c_str());
_session->KickPlayer();
}
else
{
_clientResponseTimer += diff;
}
}
}
break;
case WardenState::STATE_INITIALIZE_MODULE:
case WardenState::STATE_RESTING:
{
if (diff >= _checkTimer)
{
RequestData();
}
else
{
_checkTimer -= diff;
}
}
break;
default:
sLog.outWarden("Unimplemented warden state!");
break;
}
}
void Warden::DecryptData(uint8* buffer, uint32 length)
{
_inputCrypto.UpdateData(length, buffer);
}
void Warden::EncryptData(uint8* buffer, uint32 length)
{
_outputCrypto.UpdateData(length, buffer);
}
void Warden::SetNewState(WardenState::Value state)
{
//if we pass all initial checks, allow change
if (state < WardenState::STATE_REQUESTED_DATA)
{
if (state < _state)
{
sLog.outWarden("Warden Error: jump from %s to %s which is lower by initialization routine", WardenState::to_string(_state), WardenState::to_string(state));
return;
}
}
_state = state;
//Reset timers
// Set hold off timer, minimum timer should at least be 1 second
uint32 holdOff = sWorld.getConfig(CONFIG_UINT32_WARDEN_CLIENT_CHECK_HOLDOFF);
_checkTimer = (holdOff < 1 ? 1 : holdOff) * IN_MILLISECONDS;
_clientResponseTimer = 0;
}
bool Warden::IsValidCheckSum(uint32 checksum, const uint8* data, const uint16 length)
{
uint32 newChecksum = BuildChecksum(data, length);
if (checksum != newChecksum)
{
sLog.outWarden("CHECKSUM IS NOT VALID");
return false;
}
else
{
sLog.outWarden("CHECKSUM IS VALID");
return true;
}
}
struct keyData {
union
{
struct
{
uint8 bytes[20];
} bytes;
struct
{
uint32 ints[5];
} ints;
};
};
uint32 Warden::BuildChecksum(const uint8* data, uint32 length)
{
keyData hash;
SHA1(data, length, hash.bytes.bytes);
uint32 checkSum = 0;
for (uint8 i = 0; i < 5; ++i)
{
checkSum = checkSum ^ hash.ints.ints[i];
}
return checkSum;
}
std::string Warden::Penalty(WardenCheck* check /*= NULL*/)
{
WardenActions action;
if (check)
action = check->Action;
else
action = WardenActions(sWorld.getConfig(CONFIG_UINT32_WARDEN_CLIENT_FAIL_ACTION));
switch (action)
{
case WARDEN_ACTION_LOG:
return "None";
break;
case WARDEN_ACTION_KICK:
_session->KickPlayer();
return "Kick";
break;
case WARDEN_ACTION_BAN:
{
std::stringstream duration;
std::string accountName;
sAccountMgr.GetName(_session->GetAccountId(), accountName);
std::stringstream banReason;
banReason << "Warden Anticheat Violation";
// Check can be NULL, for example if the client sent a wrong signature in the warden packet (CHECKSUM FAIL)
if (check)
banReason << ": " << (check->Comment.empty() ? std::string("Undocumented Check") : check->Comment) << " (CheckId: " << check->CheckId << ")";
sWorld.BanAccount(BAN_ACCOUNT, accountName, sWorld.getConfig(CONFIG_UINT32_WARDEN_CLIENT_BAN_DURATION), banReason.str(), "Warden");
return "Ban";
}
default:
break;
}
return "Undefined";
}
void WorldSession::HandleWardenDataOpcode(WorldPacket& recvData)
{
if (!_warden || recvData.empty())
{
return;
}
_warden->DecryptData(const_cast<uint8*>(recvData.contents()), recvData.size());
uint8 opcode;
recvData >> opcode;
sLog.outWarden("Got packet, opcode %02X, size %u", opcode, uint32(recvData.size()));
recvData.hexlike();
switch (opcode)
{
case WARDEN_CMSG_MODULE_MISSING:
_warden->SendModuleToClient();
break;
case WARDEN_CMSG_MODULE_OK:
_warden->RequestHash();
break;
case WARDEN_CMSG_CHEAT_CHECKS_RESULT:
_warden->HandleData(recvData);
break;
case WARDEN_CMSG_MEM_CHECKS_RESULT:
sLog.outWarden("NYI WARDEN_CMSG_MEM_CHECKS_RESULT received!");
break;
case WARDEN_CMSG_HASH_RESULT:
_warden->HandleHashResult(recvData);
_warden->InitializeModule();
break;
case WARDEN_CMSG_MODULE_FAILED:
sLog.outWarden("NYI WARDEN_CMSG_MODULE_FAILED received!");
break;
default:
sLog.outWarden("Got unknown warden opcode %02X of size %u.", opcode, uint32(recvData.size() - 1));
break;
}
}
void Warden::RequestData()
{
SetNewState(WardenState::STATE_REQUESTED_DATA);
}
void Warden::HandleData(ByteBuffer& /*buff*/)
{
SetNewState(WardenState::STATE_RESTING);
}
void Warden::LogPositiveToDB(WardenCheck* check)
{
if (!check || !_session)
{
return;
}
if (uint32(check->Action) < sWorld.getConfig(CONFIG_UINT32_WARDEN_DB_LOGLEVEL))
{
return;
}
static SqlStatementID insWardenPositive;
SqlStatement stmt = LoginDatabase.CreateStatement(insWardenPositive, "INSERT INTO `warden_log` (`check`, `action`, `account`, `guid`, `map`, `position_x`, `position_y`, `position_z`) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
stmt.addUInt16(check->CheckId);
stmt.addInt8(check->Action);
stmt.addUInt32(_session->GetAccountId());
if (Player* pl = _session->GetPlayer())
{
stmt.addUInt64(pl->GetObjectGuid().GetRawValue());
stmt.addUInt32(pl->GetMapId());
stmt.addFloat(pl->GetPositionX());
stmt.addFloat(pl->GetPositionY());
stmt.addFloat(pl->GetPositionZ());
}
else
{
stmt.addUInt64(0);
stmt.addUInt32(0xFFFFFFFF);
stmt.addFloat(0.0f);
stmt.addFloat(0.0f);
stmt.addFloat(0.0f);
}
stmt.Execute();
}