DarkflameServer/dNet/AuthPackets.cpp
David Markowitz a60865cd19
feat: allow SQLite database backend (#1663)
* simplify leaderboard code, fully abstract database

* update exception catching

* update exception catching and sql references, remove ugc from gamemessages

fix deleting model

remove unrelated changes

Update GameMessages.cpp

* remove ugc from gamemessages

* Update GameMessages.cpp

* Update Leaderboard.cpp

* bug fixes

* fix racing leaderboard

* remove extra stuff

* update

* add sqlite

* use a default for optimizations

* update sqlite

* Fix limits on update and delete

* fix bugs

* use definition to switch between databases

* add switch for different backends

* fix include guard and includes

* always build both

* add mysql if block

* Update Database.cpp

* add new options and add check to prevent overriding mysql

* correct config names

* Update README.md

* Update README.md

* merge to 1 sql file for sqlite database

* move to sqlite folder

* add back mysql migrations

* Update README.md

* add migration to correct the folder name or mysql

* yes aron

* updates

* Update CMakeLists.txt

* dont use paths at all, add where check to only update if folder name still exist

check also doesnt check for slashes and assumes one will be there since it will be.

* default dont auto create account

for releases we can change this flag

* default 0

* add times played query

* fix leaderboard not incrementing on a not better score

* add env vars with defaults for docker

* use an "enum"

* default to mariadb

* Update .env.example
2024-12-17 16:07:07 -08:00

315 lines
12 KiB
C++

#include "AuthPackets.h"
#include "BitStreamUtils.h"
#include "dNetCommon.h"
#include "dServer.h"
#include "Logger.h"
#include "Database.h"
#include "ZoneInstanceManager.h"
#include "MD5.h"
#include "GeneralUtils.h"
#include "dClient/ClientVersion.h"
#include <bcrypt/BCrypt.hpp>
#include "BitStream.h"
#include <future>
#include "Game.h"
#include "dConfig.h"
#include "eServerDisconnectIdentifiers.h"
#include "eLoginResponse.h"
#include "eConnectionType.h"
#include "MessageType/Server.h"
#include "MessageType/Master.h"
#include "eGameMasterLevel.h"
#include "StringifiedEnum.h"
namespace {
std::vector<uint32_t> claimCodes;
}
void Stamp::Serialize(RakNet::BitStream& outBitStream){
outBitStream.Write(type);
outBitStream.Write(value);
outBitStream.Write(timestamp);
};
void AuthPackets::LoadClaimCodes() {
if(!claimCodes.empty()) return;
auto rcstring = Game::config->GetValue("rewardcodes");
auto codestrings = GeneralUtils::SplitString(rcstring, ',');
for(auto const &codestring: codestrings){
const auto code = GeneralUtils::TryParse<uint32_t>(codestring);
if (code && code.value() != -1) claimCodes.push_back(code.value());
}
}
void AuthPackets::HandleHandshake(dServer* server, Packet* packet) {
CINSTREAM_SKIP_HEADER
uint32_t clientVersion = 0;
inStream.Read(clientVersion);
inStream.IgnoreBytes(4);
ServiceId serviceId;
inStream.Read(serviceId);
if (serviceId != ServiceId::Client) LOG("WARNING: Service ID is not a Client!");
uint32_t processID;
inStream.Read(processID);
uint16_t port;
inStream.Read(port);
if (port != packet->systemAddress.port) LOG("WARNING: Port written in packet does not match the port the client is connecting over!");
inStream.IgnoreBytes(33);
LOG_DEBUG("Client Data [Version: %i, Service: %s, Process: %u, Port: %u, Sysaddr Port: %u]", clientVersion, StringifiedEnum::ToString(serviceId).data(), processID, port, packet->systemAddress.port);
SendHandshake(server, packet->systemAddress, server->GetIP(), server->GetPort(), server->GetServerType());
}
void AuthPackets::SendHandshake(dServer* server, const SystemAddress& sysAddr, const std::string& nextServerIP, uint16_t nextServerPort, const ServerType serverType) {
RakNet::BitStream bitStream;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::SERVER, MessageType::Server::VERSION_CONFIRM);
const auto clientNetVersionString = Game::config->GetValue("client_net_version");
const uint32_t clientNetVersion = GeneralUtils::TryParse<uint32_t>(clientNetVersionString).value_or(171022);
bitStream.Write<uint32_t>(clientNetVersion);
bitStream.Write<uint32_t>(861228100);
if (serverType == ServerType::Auth) bitStream.Write(ServiceId::Auth);
else if (serverType == ServerType::World) bitStream.Write(ServiceId::World);
else bitStream.Write(ServiceId::General);
bitStream.Write<uint64_t>(219818241584);
server->Send(bitStream, sysAddr, false);
}
std::string CleanReceivedString(const std::string& str) {
std::string toReturn = str;
const auto removed = std::ranges::find_if(toReturn, [](unsigned char c) { return isprint(c) == 0 && isblank(c) == 0; });
toReturn.erase(removed, toReturn.end());
return toReturn;
}
void AuthPackets::HandleLoginRequest(dServer* server, Packet* packet) {
CINSTREAM_SKIP_HEADER;
std::vector<Stamp> stamps;
stamps.emplace_back(eStamps::PASSPORT_AUTH_START, 0);
LUWString usernameLUString;
inStream.Read(usernameLUString);
const auto username = usernameLUString.GetAsString();
LUWString password(41);
inStream.Read(password);
LanguageCodeID locale_id;
inStream.Read(locale_id);
LOG_DEBUG("Locale ID: %s", StringifiedEnum::ToString(locale_id).data());
ClientOS clientOS;
inStream.Read(clientOS);
LOG_DEBUG("Operating System: %s", StringifiedEnum::ToString(clientOS).data());
stamps.emplace_back(eStamps::PASSPORT_AUTH_CLIENT_OS, 0);
LUWString memoryStats(256);
inStream.Read(memoryStats);
LOG_DEBUG("Memory Stats [%s]", CleanReceivedString(memoryStats.GetAsString()).c_str());
LUWString videoCard(128);
inStream.Read(videoCard);
LOG_DEBUG("VideoCard Info: [%s]", CleanReceivedString(videoCard.GetAsString()).c_str());
// Processor/CPU info
uint32_t numOfProcessors;
inStream.Read(numOfProcessors);
uint32_t processorType;
inStream.Read(processorType);
uint16_t processorLevel;
inStream.Read(processorLevel);
uint16_t processorRevision;
inStream.Read(processorRevision);
LOG_DEBUG("CPU Info: [#Processors: %i, Processor Type: %i, Processor Level: %i, Processor Revision: %i]", numOfProcessors, processorType, processorLevel, processorRevision);
// OS Info
uint32_t osVersionInfoSize;
inStream.Read(osVersionInfoSize);
uint32_t majorVersion;
inStream.Read(majorVersion);
uint32_t minorVersion;
inStream.Read(minorVersion);
uint32_t buildNumber;
inStream.Read(buildNumber);
uint32_t platformID;
inStream.Read(platformID);
LOG_DEBUG("OS Info: [Size: %i, Major: %i, Minor %i, Buid#: %i, platformID: %i]", osVersionInfoSize, majorVersion, minorVersion, buildNumber, platformID);
// Fetch account details
auto accountInfo = Database::Get()->GetAccountInfo(username);
if (!accountInfo) {
LOG("No user by name %s found!", username.c_str());
stamps.emplace_back(eStamps::PASSPORT_AUTH_ERROR, 1);
AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::INVALID_USER, "", "", 2001, username, stamps);
return;
}
//If we aren't running in live mode, then only GMs are allowed to enter:
const auto& closedToNonDevs = Game::config->GetValue("closed_to_non_devs");
if (closedToNonDevs.size() > 0 && bool(std::stoi(closedToNonDevs)) && accountInfo->maxGmLevel == eGameMasterLevel::CIVILIAN) {
stamps.emplace_back(eStamps::GM_REQUIRED, 1);
AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::PERMISSIONS_NOT_HIGH_ENOUGH, "The server is currently only open to developers.", "", 2001, username, stamps);
return;
}
if (Game::config->GetValue("dont_use_keys") != "1" && accountInfo->maxGmLevel == eGameMasterLevel::CIVILIAN) {
//Check to see if we have a play key:
if (accountInfo->playKeyId == 0) {
stamps.emplace_back(eStamps::PASSPORT_AUTH_ERROR, 1);
AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::PERMISSIONS_NOT_HIGH_ENOUGH, "Your account doesn't have a play key associated with it!", "", 2001, username, stamps);
LOG("User %s tried to log in, but they don't have a play key.", username.c_str());
return;
}
//Check if the play key is _valid_:
auto playKeyStatus = Database::Get()->IsPlaykeyActive(accountInfo->playKeyId);
if (!playKeyStatus) {
stamps.emplace_back(eStamps::PASSPORT_AUTH_ERROR, 1);
AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::PERMISSIONS_NOT_HIGH_ENOUGH, "Your account doesn't have a valid play key associated with it!", "", 2001, username, stamps);
return;
}
if (!playKeyStatus.value()) {
stamps.emplace_back(eStamps::PASSPORT_AUTH_ERROR, 1);
AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::PERMISSIONS_NOT_HIGH_ENOUGH, "Your play key has been disabled.", "", 2001, username, stamps);
LOG("User %s tried to log in, but their play key was disabled", username.c_str());
return;
}
} else if (Game::config->GetValue("dont_use_keys") == "1" || accountInfo->maxGmLevel > eGameMasterLevel::CIVILIAN){
stamps.emplace_back(eStamps::PASSPORT_AUTH_BYPASS, 1);
}
if (accountInfo->banned) {
stamps.emplace_back(eStamps::PASSPORT_AUTH_ERROR, 1);
AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::BANNED, "", "", 2001, username, stamps);
return;
}
if (accountInfo->locked) {
stamps.emplace_back(eStamps::PASSPORT_AUTH_ERROR, 1);
AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::ACCOUNT_LOCKED, "", "", 2001, username, stamps);
return;
}
bool loginSuccess = ::bcrypt_checkpw(password.GetAsString().c_str(), accountInfo->bcryptPassword.c_str()) == 0;
if (!loginSuccess) {
stamps.emplace_back(eStamps::PASSPORT_AUTH_ERROR, 1);
AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::WRONG_PASS, "", "", 2001, username, stamps);
LOG("Wrong password used");
} else {
SystemAddress system = packet->systemAddress; //Copy the sysAddr before the Packet gets destroyed from main
if (!server->GetIsConnectedToMaster()) {
stamps.emplace_back(eStamps::PASSPORT_AUTH_WORLD_DISCONNECT, 1);
AuthPackets::SendLoginResponse(server, system, eLoginResponse::GENERAL_FAILED, "", "", 0, username, stamps);
return;
}
stamps.emplace_back(eStamps::PASSPORT_AUTH_WORLD_SESSION_CONFIRM_TO_AUTH, 1);
ZoneInstanceManager::Instance()->RequestZoneTransfer(server, 0, 0, false, [system, server, username, stamps](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string zoneIP, uint16_t zonePort) mutable {
AuthPackets::SendLoginResponse(server, system, eLoginResponse::SUCCESS, "", zoneIP, zonePort, username, stamps);
});
}
for(auto const code: claimCodes){
Database::Get()->InsertRewardCode(accountInfo->id, code);
}
}
void AuthPackets::SendLoginResponse(dServer* server, const SystemAddress& sysAddr, eLoginResponse responseCode, const std::string& errorMsg, const std::string& wServerIP, uint16_t wServerPort, std::string username, std::vector<Stamp>& stamps) {
stamps.emplace_back(eStamps::PASSPORT_AUTH_IM_LOGIN_START, 1);
RakNet::BitStream loginResponse;
BitStreamUtils::WriteHeader(loginResponse, eConnectionType::CLIENT, MessageType::Client::LOGIN_RESPONSE);
loginResponse.Write(responseCode);
// Event Gating
loginResponse.Write(LUString(Game::config->GetValue("event_1")));
loginResponse.Write(LUString(Game::config->GetValue("event_2")));
loginResponse.Write(LUString(Game::config->GetValue("event_3")));
loginResponse.Write(LUString(Game::config->GetValue("event_4")));
loginResponse.Write(LUString(Game::config->GetValue("event_5")));
loginResponse.Write(LUString(Game::config->GetValue("event_6")));
loginResponse.Write(LUString(Game::config->GetValue("event_7")));
loginResponse.Write(LUString(Game::config->GetValue("event_8")));
const uint16_t version_major =
GeneralUtils::TryParse<uint16_t>(Game::config->GetValue("version_major")).value_or(ClientVersion::major);
const uint16_t version_current =
GeneralUtils::TryParse<uint16_t>(Game::config->GetValue("version_current")).value_or(ClientVersion::current);
const uint16_t version_minor =
GeneralUtils::TryParse<uint16_t>(Game::config->GetValue("version_minor")).value_or(ClientVersion::minor);
loginResponse.Write(version_major);
loginResponse.Write(version_current);
loginResponse.Write(version_minor);
// Writes the user key
uint32_t sessionKey = GeneralUtils::GenerateRandomNumber<uint32_t>();
std::string userHash = std::to_string(sessionKey);
userHash = md5(userHash);
loginResponse.Write(LUWString(userHash));
// World Server IP
loginResponse.Write(LUString(wServerIP));
// Chat Server IP (unused)
loginResponse.Write(LUString(""));
// World Server Redirect port
loginResponse.Write(wServerPort);
// Char Server Redirect port (unused)
loginResponse.Write(static_cast<uint16_t>(0));
// CDN Key
loginResponse.Write(LUString(""));
// CDN Ticket
loginResponse.Write(LUString("00000000-0000-0000-0000-000000000000", 37));
// Language
loginResponse.Write(Language::en_US);
// Write the localization
loginResponse.Write(LUString("US", 3));
loginResponse.Write<uint8_t>(false); // Just upgraded from F2P
loginResponse.Write<uint8_t>(false); // User is F2P
loginResponse.Write<uint64_t>(0); // Time Remaining in F2P
// Write custom error message
loginResponse.Write<uint16_t>(errorMsg.length());
loginResponse.Write(LUWString(errorMsg, static_cast<uint32_t>(errorMsg.length())));
stamps.emplace_back(eStamps::PASSPORT_AUTH_WORLD_COMMUNICATION_FINISH, 1);
loginResponse.Write<uint32_t>((sizeof(Stamp) * stamps.size()) + sizeof(uint32_t));
for (auto& stamp : stamps) stamp.Serialize(loginResponse);
server->Send(loginResponse, sysAddr, false);
//Inform the master server that we've created a session for this user:
if (responseCode == eLoginResponse::SUCCESS) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::MASTER, MessageType::Master::SET_SESSION_KEY);
bitStream.Write(sessionKey);
bitStream.Write(LUString(username));
server->SendToMaster(bitStream);
LOG("Set sessionKey: %i for user %s", sessionKey, username.c_str());
}
}