mirror of
https://github.com/TrinityCore/TrinityCore.git
synced 2026-01-21 09:44:45 +01:00
Core/NetworkIO: Moved SMSG_AUTH_CHALLENGE and CMSG_AUTH_SESSION to packet claases, added SMSG_COMPRESSED_PACKET
This commit is contained in:
@@ -1317,7 +1317,7 @@ void Object::AddDynamicValue(uint16 index, uint32 value)
|
||||
}
|
||||
}
|
||||
|
||||
void Object::RemoveDynamicValue(uint16 index, uint32 value)
|
||||
void Object::RemoveDynamicValue(uint16 index, uint32 /*value*/)
|
||||
{
|
||||
ASSERT(index < _dynamicValuesCount || PrintIndexError(index, false));
|
||||
/// TODO: Research if this is actually needed
|
||||
|
||||
@@ -181,7 +181,7 @@ public:
|
||||
|
||||
inline static ObjectGuid MapSpecific(HighGuid type, uint8 subType, uint16 mapId, uint32 serverId, uint32 entry, ObjectGuid::LowType counter)
|
||||
{
|
||||
return ObjectGuid(uint64((uint64(type) << 58) | (uint64(realmHandle.Index & 0x1FFF) << 42) | (uint64(mapId & 0x1FFF) << 29) | (uint64(entry & 0x7FFFFF) << 6)),
|
||||
return ObjectGuid(uint64((uint64(type) << 58) | (uint64(realmHandle.Index & 0x1FFF) << 42) | (uint64(mapId & 0x1FFF) << 29) | (uint64(entry & 0x7FFFFF) << 6) | (uint64(subType) & 0x3F)),
|
||||
uint64((uint64(serverId & 0xFFFFFF) << 40) | (counter & UI64LIT(0xFFFFFFFFFF))));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -770,7 +770,6 @@ bool Pet::CreateBaseAtCreatureInfo(CreatureTemplate const* cinfo, Unit* owner)
|
||||
bool Pet::CreateBaseAtTamed(CreatureTemplate const* cinfo, Map* map, uint32 phaseMask)
|
||||
{
|
||||
TC_LOG_DEBUG("entities.pet", "Pet::CreateBaseForTamed");
|
||||
uint32 petId = sObjectMgr->GeneratePetNumber();
|
||||
if (!Create(sObjectMgr->GetGenerator<HighGuid::Pet>()->Generate(), map, phaseMask, cinfo->Entry))
|
||||
return false;
|
||||
|
||||
|
||||
@@ -48,6 +48,14 @@ namespace WorldPackets
|
||||
size_t GetSize() const { return _worldPacket.size(); }
|
||||
void Reset() { _worldPacket.clear(); }
|
||||
};
|
||||
|
||||
class ClientPacket : public Packet
|
||||
{
|
||||
public:
|
||||
ClientPacket(WorldPacket&& packet) : Packet(std::move(packet)) { }
|
||||
|
||||
void Write() override final { ASSERT(!"Write not allowed for client packets."); }
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -17,6 +17,37 @@
|
||||
|
||||
#include "AuthenticationPackets.h"
|
||||
|
||||
void WorldPackets::Auth::AuthChallenge::Write()
|
||||
{
|
||||
_worldPacket << uint32(Challenge);
|
||||
_worldPacket.append(DosChallenge, sizeof(DosChallenge));
|
||||
_worldPacket << uint8(DosZeroBits);
|
||||
}
|
||||
|
||||
void WorldPackets::Auth::AuthSession::Read()
|
||||
{
|
||||
uint32 addonDataSize;
|
||||
|
||||
_worldPacket >> LoginServerID;
|
||||
_worldPacket >> Build;
|
||||
_worldPacket >> RegionID;
|
||||
_worldPacket >> BattlegroupID;
|
||||
_worldPacket >> RealmID;
|
||||
_worldPacket >> LoginServerType;
|
||||
_worldPacket >> BuildType;
|
||||
_worldPacket >> LocalChallenge;
|
||||
_worldPacket >> DosResponse;
|
||||
_worldPacket.read(Digest, SHA_DIGEST_LENGTH);
|
||||
Account = _worldPacket.ReadString(_worldPacket.ReadBits(11));
|
||||
UseIPv6 = _worldPacket.ReadBit(); // UseIPv6
|
||||
_worldPacket >> addonDataSize;
|
||||
if (addonDataSize)
|
||||
{
|
||||
AddonInfo.resize(addonDataSize);
|
||||
_worldPacket.read(AddonInfo.contents(), addonDataSize);
|
||||
}
|
||||
}
|
||||
|
||||
WorldPackets::Auth::AuthResponse::AuthResponse()
|
||||
: ServerPacket(SMSG_AUTH_RESPONSE, 132)
|
||||
{
|
||||
@@ -104,4 +135,4 @@ void WorldPackets::Auth::AuthResponse::Write()
|
||||
}
|
||||
|
||||
_worldPacket.FlushBits();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,11 +19,49 @@
|
||||
|
||||
#include "Packet.h"
|
||||
#include "Util.h"
|
||||
#include <SHA1.h>
|
||||
|
||||
namespace WorldPackets
|
||||
{
|
||||
namespace Auth
|
||||
{
|
||||
class AuthChallenge final : public ServerPacket
|
||||
{
|
||||
public:
|
||||
AuthChallenge() : ServerPacket(SMSG_AUTH_CHALLENGE, 8 + 32 + 1), Challenge(0) { }
|
||||
|
||||
void Write() override;
|
||||
|
||||
uint32 Challenge;
|
||||
uint32 DosChallenge[8]; ///< Encryption seeds
|
||||
uint8 DosZeroBits;
|
||||
};
|
||||
|
||||
class AuthSession final : public ClientPacket
|
||||
{
|
||||
public:
|
||||
AuthSession(WorldPacket&& packet) : ClientPacket(std::move(packet))
|
||||
{
|
||||
memset(Digest, 0, SHA_DIGEST_LENGTH);
|
||||
}
|
||||
|
||||
void Read() override;
|
||||
|
||||
uint32 BattlegroupID = 0;
|
||||
int8 LoginServerType = 0; ///< Auth type used - 0 GRUNT, 1 battle.net
|
||||
int8 BuildType = 0;
|
||||
uint32 RealmID = 0;
|
||||
uint16 Build = 0;
|
||||
uint32 LocalChallenge = 0;
|
||||
int32 LoginServerID = 0;
|
||||
uint32 RegionID = 0;
|
||||
uint64 DosResponse = 0;
|
||||
uint8 Digest[SHA_DIGEST_LENGTH];
|
||||
std::string Account;
|
||||
bool UseIPv6 = false;
|
||||
ByteBuffer AddonInfo;
|
||||
};
|
||||
|
||||
class AuthResponse final : public ServerPacket
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -31,7 +31,6 @@ enum OpcodeMisc : uint32
|
||||
NUM_OPCODE_HANDLERS = (MAX_OPCODE + 1),
|
||||
UNKNOWN_OPCODE = (0xFFFF + 1),
|
||||
NULL_OPCODE = 0,
|
||||
COMPRESSED_OPCODE_MASK = 0x8000
|
||||
};
|
||||
|
||||
// CMSGs
|
||||
@@ -69,7 +68,7 @@ enum OpcodeClient : uint32
|
||||
CMSG_AUCTION_PLACE_BID = 0x0000,
|
||||
CMSG_AUCTION_REMOVE_ITEM = 0x0000,
|
||||
CMSG_AUCTION_SELL_ITEM = 0x0000,
|
||||
CMSG_AUTH_SESSION = 0x1B05,
|
||||
CMSG_AUTH_SESSION = 0x0487,
|
||||
CMSG_AUTOBANK_ITEM = 0x0000,
|
||||
CMSG_AUTOEQUIP_GROUND_ITEM = 0x0000,
|
||||
CMSG_AUTOEQUIP_ITEM = 0x0000,
|
||||
@@ -712,8 +711,8 @@ enum OpcodeServer : uint32
|
||||
SMSG_AURA_POINTS_DEPLETED = 0x0000,
|
||||
SMSG_AURA_UPDATE = 0x128B,
|
||||
SMSG_AURA_UPDATE_ALL = 0x0000,
|
||||
SMSG_AUTH_CHALLENGE = 0x10AA,
|
||||
SMSG_AUTH_RESPONSE = 0x0564,
|
||||
SMSG_AUTH_CHALLENGE = 0x1759,
|
||||
SMSG_AUTH_RESPONSE = 0x0DA9,
|
||||
SMSG_AVAILABLE_VOICE_CHANNEL = 0x0000,
|
||||
SMSG_AVERAGE_ITEM_LEVEL_INFORM = 0x0000,
|
||||
SMSG_BARBER_SHOP_RESULT = 0x0000,
|
||||
@@ -809,6 +808,7 @@ enum OpcodeServer : uint32
|
||||
SMSG_COMMENTATOR_STATE_CHANGED = 0x0000,
|
||||
SMSG_COMPLAIN_RESULT = 0x0000,
|
||||
SMSG_COMPRESSED_MOVES = 0x0000,
|
||||
SMSG_COMPRESSED_PACKET = 0x07CA,
|
||||
SMSG_COMSAT_CONNECT_FAIL = 0x0000,
|
||||
SMSG_COMSAT_DISCONNECT = 0x0000,
|
||||
SMSG_COMSAT_RECONNECT_TRY = 0x0000,
|
||||
@@ -1512,11 +1512,7 @@ inline std::string GetOpcodeNameForLogging(T id)
|
||||
if (static_cast<uint32>(id) < UNKNOWN_OPCODE)
|
||||
{
|
||||
if (OpcodeHandler const* handler = opcodeTable[T(opcode & 0x7FFF)])
|
||||
{
|
||||
ss << handler->Name;
|
||||
if (opcode & COMPRESSED_OPCODE_MASK)
|
||||
ss << "_COMPRESSED";
|
||||
}
|
||||
else
|
||||
ss << "UNKNOWN OPCODE";
|
||||
}
|
||||
|
||||
@@ -23,13 +23,12 @@
|
||||
void WorldPacket::Compress(z_stream* compressionStream)
|
||||
{
|
||||
OpcodeServer uncompressedOpcode = static_cast<OpcodeServer>(GetOpcode());
|
||||
if (uncompressedOpcode & COMPRESSED_OPCODE_MASK)
|
||||
if (uncompressedOpcode == SMSG_COMPRESSED_PACKET)
|
||||
{
|
||||
TC_LOG_ERROR("network", "Packet with opcode 0x%04X is already compressed!", uncompressedOpcode);
|
||||
return;
|
||||
}
|
||||
|
||||
OpcodeServer opcode = OpcodeServer(uncompressedOpcode | COMPRESSED_OPCODE_MASK);
|
||||
uint32 size = wpos();
|
||||
uint32 destsize = compressBound(size);
|
||||
|
||||
@@ -41,11 +40,12 @@ void WorldPacket::Compress(z_stream* compressionStream)
|
||||
return;
|
||||
|
||||
clear();
|
||||
reserve(destsize + sizeof(uint32));
|
||||
reserve(destsize + sizeof(uint32) * 2);
|
||||
*this << uint32(uncompressedOpcode);
|
||||
*this << uint32(size);
|
||||
append(&storage[0], destsize);
|
||||
SetOpcode(opcode);
|
||||
TC_LOG_INFO("network", "%s (len %u) successfully compressed to %04X (len %u)", GetOpcodeNameForLogging(uncompressedOpcode).c_str(), size, opcode, destsize);
|
||||
SetOpcode(SMSG_COMPRESSED_PACKET);
|
||||
TC_LOG_INFO("network", "%s (len %u) successfully compressed to len %u", GetOpcodeNameForLogging(uncompressedOpcode).c_str(), size, destsize);
|
||||
}
|
||||
|
||||
//! Compresses another packet and stores it in self (source left intact)
|
||||
@@ -54,30 +54,29 @@ void WorldPacket::Compress(z_stream* compressionStream, WorldPacket const* sourc
|
||||
ASSERT(source != this);
|
||||
|
||||
OpcodeServer uncompressedOpcode = static_cast<OpcodeServer>(source->GetOpcode());
|
||||
if (uncompressedOpcode & COMPRESSED_OPCODE_MASK)
|
||||
if (uncompressedOpcode == SMSG_COMPRESSED_PACKET)
|
||||
{
|
||||
TC_LOG_ERROR("network", "Packet with opcode 0x%04X is already compressed!", uncompressedOpcode);
|
||||
return;
|
||||
}
|
||||
|
||||
OpcodeServer opcode = OpcodeServer(uncompressedOpcode | COMPRESSED_OPCODE_MASK);
|
||||
uint32 size = source->size();
|
||||
uint32 destsize = compressBound(size);
|
||||
|
||||
size_t sizePos = 0;
|
||||
resize(destsize + sizeof(uint32));
|
||||
resize(destsize + sizeof(uint32) * 2);
|
||||
|
||||
_compressionStream = compressionStream;
|
||||
Compress(static_cast<void*>(&_storage[0] + sizeof(uint32)), &destsize, static_cast<const void*>(source->contents()), size);
|
||||
Compress(static_cast<void*>(&_storage[0] + sizeof(uint32) * 2), &destsize, static_cast<const void*>(source->contents()), size);
|
||||
if (destsize == 0)
|
||||
return;
|
||||
|
||||
put<uint32>(sizePos, size);
|
||||
put<uint32>(0, uncompressedOpcode);
|
||||
put<uint32>(4, size);
|
||||
resize(destsize + sizeof(uint32));
|
||||
|
||||
SetOpcode(opcode);
|
||||
SetOpcode(SMSG_COMPRESSED_PACKET);
|
||||
|
||||
TC_LOG_INFO("network", "%s (len %u) successfully compressed to %04X (len %u)", GetOpcodeNameForLogging(uncompressedOpcode).c_str(), size, opcode, destsize);
|
||||
TC_LOG_INFO("network", "%s (len %u) successfully compressed to len %u", GetOpcodeNameForLogging(uncompressedOpcode).c_str(), size, destsize);
|
||||
}
|
||||
|
||||
void WorldPacket::Compress(void* dst, uint32 *dst_size, const void* src, int src_size)
|
||||
|
||||
@@ -65,7 +65,7 @@ class WorldPacket : public ByteBuffer
|
||||
|
||||
uint32 GetOpcode() const { return m_opcode; }
|
||||
void SetOpcode(uint32 opcode) { m_opcode = opcode; }
|
||||
bool IsCompressed() const { return (m_opcode & COMPRESSED_OPCODE_MASK) != 0; }
|
||||
bool IsCompressed() const { return m_opcode == SMSG_COMPRESSED_PACKET; }
|
||||
void Compress(z_stream_s* compressionStream);
|
||||
void Compress(z_stream_s* compressionStream, WorldPacket const* source);
|
||||
|
||||
|
||||
@@ -806,7 +806,7 @@ void WorldSession::SaveTutorialsData(SQLTransaction &trans)
|
||||
m_TutorialsChanged = false;
|
||||
}
|
||||
|
||||
void WorldSession::ReadAddonsInfo(WorldPacket &data)
|
||||
void WorldSession::ReadAddonsInfo(ByteBuffer& data)
|
||||
{
|
||||
if (data.rpos() + 4 > data.size())
|
||||
return;
|
||||
|
||||
@@ -268,7 +268,7 @@ class WorldSession
|
||||
bool PlayerLogoutWithSave() const { return m_playerLogout && m_playerSave; }
|
||||
bool PlayerRecentlyLoggedOut() const { return m_playerRecentlyLogout; }
|
||||
|
||||
void ReadAddonsInfo(WorldPacket& data);
|
||||
void ReadAddonsInfo(ByteBuffer& data);
|
||||
void SendAddonsInfo();
|
||||
bool IsAddonRegistered(const std::string& prefix) const;
|
||||
|
||||
|
||||
@@ -63,19 +63,20 @@ void WorldSocket::Start()
|
||||
|
||||
void WorldSocket::HandleSendAuthSession()
|
||||
{
|
||||
WorldPacket packet(SMSG_AUTH_CHALLENGE, 37);
|
||||
packet << uint32(_authSeed);
|
||||
|
||||
BigNumber seed1;
|
||||
seed1.SetRand(16 * 8);
|
||||
packet.append(seed1.AsByteArray(16).get(), 16); // new encryption seeds
|
||||
|
||||
BigNumber seed2;
|
||||
seed1.SetRand(16 * 8);
|
||||
seed2.SetRand(16 * 8);
|
||||
packet.append(seed2.AsByteArray(16).get(), 16); // new encryption seeds
|
||||
|
||||
packet << uint8(1);
|
||||
SendPacket(packet);
|
||||
WorldPackets::Auth::AuthChallenge challenge;
|
||||
challenge.Challenge = _authSeed;
|
||||
memcpy(&challenge.DosChallenge[0], seed1.AsByteArray(16).get(), 16);
|
||||
memcpy(&challenge.DosChallenge[4], seed2.AsByteArray(16).get(), 16);
|
||||
challenge.DosZeroBits = 1;
|
||||
|
||||
challenge.Write();
|
||||
|
||||
SendPacket(challenge.GetWorldPacket());
|
||||
}
|
||||
|
||||
void WorldSocket::ReadHandler()
|
||||
@@ -334,50 +335,21 @@ void WorldSocket::SendPacket(WorldPacket& packet)
|
||||
|
||||
void WorldSocket::HandleAuthSession(WorldPacket& recvPacket)
|
||||
{
|
||||
uint8 digest[SHA_DIGEST_LENGTH];
|
||||
uint32 clientSeed;
|
||||
WorldPackets::Auth::AuthSession authSession(std::move(recvPacket));
|
||||
authSession.Read();
|
||||
|
||||
uint8 security;
|
||||
uint16 clientBuild;
|
||||
uint32 id;
|
||||
uint32 addonSize;
|
||||
LocaleConstant locale;
|
||||
std::string account;
|
||||
SHA1Hash sha;
|
||||
BigNumber k;
|
||||
bool wardenActive = sWorld->getBoolConfig(CONFIG_WARDEN_ENABLED);
|
||||
WorldPacket addonsData;
|
||||
uint8 loginServerType;
|
||||
uint32 realmIndex;
|
||||
|
||||
recvPacket.read_skip<uint32>(); // Grunt - ServerId
|
||||
recvPacket >> clientBuild;
|
||||
recvPacket.read_skip<uint32>(); // Region
|
||||
recvPacket.read_skip<uint32>(); // Battlegroup
|
||||
recvPacket >> realmIndex;
|
||||
recvPacket >> loginServerType; // could be swapped with other uint8 (both always 1)
|
||||
recvPacket.read_skip<uint8>();
|
||||
recvPacket >> clientSeed;
|
||||
recvPacket.read_skip<uint64>(); // DosResponse
|
||||
|
||||
for (int i = 0; i < SHA_DIGEST_LENGTH; i++)
|
||||
recvPacket >> digest[i];
|
||||
|
||||
uint32 accountNameLength = recvPacket.ReadBits(11);
|
||||
account = recvPacket.ReadString(accountNameLength);
|
||||
recvPacket.ReadBit(); // UseIPv6
|
||||
recvPacket >> addonSize;
|
||||
|
||||
if (addonSize)
|
||||
{
|
||||
addonsData.resize(addonSize);
|
||||
recvPacket.read((uint8*)addonsData.contents(), addonSize);
|
||||
}
|
||||
|
||||
// Get the account information from the auth database
|
||||
// 0 1 2 3 4 5 6 7 8
|
||||
// SELECT id, sessionkey, last_ip, locked, expansion, mutetime, locale, recruiter, os FROM account WHERE username = ?
|
||||
PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_ACCOUNT_INFO_BY_NAME);
|
||||
stmt->setString(0, account);
|
||||
stmt->setString(0, authSession.Account);
|
||||
|
||||
PreparedQueryResult result = LoginDatabase.Query(stmt);
|
||||
|
||||
@@ -405,7 +377,7 @@ void WorldSocket::HandleAuthSession(WorldPacket& recvPacket)
|
||||
stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LAST_ATTEMPT_IP);
|
||||
|
||||
stmt->setString(0, address);
|
||||
stmt->setString(1, account);
|
||||
stmt->setString(1, authSession.Account);
|
||||
|
||||
LoginDatabase.Execute(stmt);
|
||||
// This also allows to check for possible "hack" attempts on account
|
||||
@@ -428,7 +400,7 @@ void WorldSocket::HandleAuthSession(WorldPacket& recvPacket)
|
||||
return;
|
||||
}
|
||||
|
||||
if (realmIndex != realmHandle.Index)
|
||||
if (authSession.RealmID != realmHandle.Index)
|
||||
{
|
||||
SendAuthResponseError(REALM_LIST_REALM_NOT_FOUND);
|
||||
TC_LOG_ERROR("network", "WorldSocket::HandleAuthSession: Sent Auth Response (bad realm).");
|
||||
@@ -450,17 +422,17 @@ void WorldSocket::HandleAuthSession(WorldPacket& recvPacket)
|
||||
// Check that Key and account name are the same on client and server
|
||||
uint32 t = 0;
|
||||
|
||||
sha.UpdateData(account);
|
||||
sha.UpdateData(authSession.Account);
|
||||
sha.UpdateData((uint8*)&t, 4);
|
||||
sha.UpdateData((uint8*)&clientSeed, 4);
|
||||
sha.UpdateData((uint8*)&authSession.LocalChallenge, 4);
|
||||
sha.UpdateData((uint8*)&_authSeed, 4);
|
||||
sha.UpdateBigNumbers(&k, NULL);
|
||||
sha.Finalize();
|
||||
|
||||
if (memcmp(sha.GetDigest(), digest, SHA_DIGEST_LENGTH) != 0)
|
||||
if (memcmp(sha.GetDigest(), authSession.Digest, SHA_DIGEST_LENGTH) != 0)
|
||||
{
|
||||
SendAuthResponseError(AUTH_FAILED);
|
||||
TC_LOG_ERROR("network", "WorldSocket::HandleAuthSession: Authentication failed for account: %u ('%s') address: %s", id, account.c_str(), address.c_str());
|
||||
TC_LOG_ERROR("network", "WorldSocket::HandleAuthSession: Authentication failed for account: %u ('%s') address: %s", id, authSession.Account.c_str(), address.c_str());
|
||||
DelayedCloseSocket();
|
||||
return;
|
||||
}
|
||||
@@ -500,7 +472,7 @@ void WorldSocket::HandleAuthSession(WorldPacket& recvPacket)
|
||||
uint32 recruiter = fields[7].GetUInt32();
|
||||
|
||||
uint32 battlenetAccountId = 0;
|
||||
if (loginServerType == 1)
|
||||
if (authSession.LoginServerType == 1)
|
||||
battlenetAccountId = Battlenet::AccountMgr::GetIdByGameAccount(id);
|
||||
|
||||
// Checks gmlevel per Realm
|
||||
@@ -549,8 +521,7 @@ void WorldSocket::HandleAuthSession(WorldPacket& recvPacket)
|
||||
}
|
||||
|
||||
TC_LOG_DEBUG("network", "WorldSocket::HandleAuthSession: Client '%s' authenticated successfully from %s.",
|
||||
account.c_str(),
|
||||
address.c_str());
|
||||
authSession.Account.c_str(), address.c_str());
|
||||
|
||||
// Check if this user is by any chance a recruiter
|
||||
stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_ACCOUNT_RECRUITER);
|
||||
@@ -567,7 +538,7 @@ void WorldSocket::HandleAuthSession(WorldPacket& recvPacket)
|
||||
stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LAST_IP);
|
||||
|
||||
stmt->setString(0, address);
|
||||
stmt->setString(1, account);
|
||||
stmt->setString(1, authSession.Account);
|
||||
|
||||
LoginDatabase.Execute(stmt);
|
||||
|
||||
@@ -577,7 +548,7 @@ void WorldSocket::HandleAuthSession(WorldPacket& recvPacket)
|
||||
_worldSession = new WorldSession(id, battlenetAccountId, shared_from_this(), AccountTypes(security), expansion, mutetime, locale, recruiter, isRecruiter);
|
||||
_worldSession->LoadGlobalAccountData();
|
||||
_worldSession->LoadTutorialsData();
|
||||
_worldSession->ReadAddonsInfo(addonsData);
|
||||
_worldSession->ReadAddonsInfo(authSession.AddonInfo);
|
||||
_worldSession->LoadPermissions();
|
||||
|
||||
// Initialize Warden system only if it is enabled by config
|
||||
|
||||
Reference in New Issue
Block a user