From 6b9ae4c32521b82f1de9675dd24e173bf9b679a9 Mon Sep 17 00:00:00 2001 From: Gent Semaj Date: Mon, 6 Jan 2025 23:43:37 -0500 Subject: [PATCH] Validate name wheel names --- config.ini | 2 + src/TableData.cpp | 21 ++++++ src/db/Database.hpp | 4 +- src/db/login.cpp | 30 +++------ src/servers/CNLoginServer.cpp | 122 +++++++++++++++++++++++++++------- src/servers/CNLoginServer.hpp | 7 ++ src/settings.cpp | 6 +- src/settings.hpp | 3 +- 8 files changed, 147 insertions(+), 48 deletions(-) diff --git a/config.ini b/config.ini index 50993a7..8e9e26a 100644 --- a/config.ini +++ b/config.ini @@ -12,6 +12,8 @@ sandbox=true [login] # must be kept in sync with loginInfo.php port=23000 +# will all name wheel names be approved instantly? +acceptallwheelnames=true # will all custom names be approved instantly? acceptallcustomnames=true # should attempts to log into non-existent accounts diff --git a/src/TableData.cpp b/src/TableData.cpp index 8460d45..9a4e76f 100644 --- a/src/TableData.cpp +++ b/src/TableData.cpp @@ -1,5 +1,7 @@ #include "TableData.hpp" +#include "servers/CNLoginServer.hpp" + #include "NPCManager.hpp" #include "Missions.hpp" #include "Items.hpp" @@ -79,6 +81,25 @@ static void loadXDT(json& xdtData) { NPCManager::NPCData = xdtData["m_pNpcTable"]["m_pNpcData"]; try { + // load name wheel names + json firstNameData = xdtData["m_pNameTable"]["m_pFirstName"]; + for (json::iterator _name = firstNameData.begin(); _name != firstNameData.end(); _name++) { + auto name = _name.value(); + LoginServer::WheelFirstNames.push_back(name["m_pstrNameString"]); + } + + json middleNameData = xdtData["m_pNameTable"]["m_pMiddleName"]; + for (json::iterator _name = middleNameData.begin(); _name != middleNameData.end(); _name++) { + auto name = _name.value(); + LoginServer::WheelMiddleNames.push_back(name["m_pstrNameString"]); + } + + json lastNameData = xdtData["m_pNameTable"]["m_pLastName"]; + for (json::iterator _name = lastNameData.begin(); _name != lastNameData.end(); _name++) { + auto name = _name.value(); + LoginServer::WheelLastNames.push_back(name["m_pstrNameString"]); + } + // load warps json warpData = xdtData["m_pInstanceTable"]["m_pWarpData"]; diff --git a/src/db/Database.hpp b/src/db/Database.hpp index 57bc3bf..7f63d16 100644 --- a/src/db/Database.hpp +++ b/src/db/Database.hpp @@ -69,7 +69,7 @@ namespace Database { bool isNameFree(std::string firstName, std::string lastName); bool isSlotFree(int accountId, int slotNum); /// returns ID, 0 if something failed - int createCharacter(sP_CL2LS_REQ_SAVE_CHAR_NAME* save, int AccountID); + int createCharacter(int slot, int accountId, const char* firstName, const char* lastName, int nameCheck); /// returns true if query succeeded bool finishCharacter(sP_CL2LS_REQ_CHAR_CREATE* character, int accountId); /// returns true if query succeeded @@ -85,7 +85,7 @@ namespace Database { }; void evaluateCustomName(int characterID, CustomName decision); /// returns true if query succeeded - bool changeName(sP_CL2LS_REQ_CHANGE_CHAR_NAME* save, int accountId); + bool changeName(int playerId, int accountId, const char* firstName, const char* lastName, int nameCheck); // getting players void getPlayer(Player* plr, int id); diff --git a/src/db/login.cpp b/src/db/login.cpp index fd80a17..55b2e83 100644 --- a/src/db/login.cpp +++ b/src/db/login.cpp @@ -2,6 +2,8 @@ #include "db/internal.hpp" +#include "servers/CNLoginServer.hpp" + #include "bcrypt/BCrypt.hpp" void Database::findAccount(Account* account, std::string login) { @@ -291,7 +293,7 @@ bool Database::isSlotFree(int accountId, int slotNum) { return result; } -int Database::createCharacter(sP_CL2LS_REQ_SAVE_CHAR_NAME* save, int AccountID) { +int Database::createCharacter(int slot, int accountId, const char* firstName, const char* lastName, int nameCheck) { std::lock_guard lock(dbCrit); sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL); @@ -304,22 +306,17 @@ int Database::createCharacter(sP_CL2LS_REQ_SAVE_CHAR_NAME* save, int AccountID) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); )"; sqlite3_stmt* stmt; - std::string firstName = AUTOU16TOU8(save->szFirstName); - std::string lastName = AUTOU16TOU8(save->szLastName); sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); - sqlite3_bind_int(stmt, 1, AccountID); - sqlite3_bind_int(stmt, 2, save->iSlotNum); - sqlite3_bind_text(stmt, 3, firstName.c_str(), -1, NULL); - sqlite3_bind_text(stmt, 4, lastName.c_str(), -1, NULL); + sqlite3_bind_int(stmt, 1, accountId); + sqlite3_bind_int(stmt, 2, slot); + sqlite3_bind_text(stmt, 3, firstName, -1, NULL); + sqlite3_bind_text(stmt, 4, lastName, -1, NULL); sqlite3_bind_int(stmt, 5, settings::SPAWN_X); sqlite3_bind_int(stmt, 6, settings::SPAWN_Y); sqlite3_bind_int(stmt, 7, settings::SPAWN_Z); sqlite3_bind_int(stmt, 8, settings::SPAWN_ANGLE); sqlite3_bind_int(stmt, 9, PC_MAXHEALTH(1)); - - // if FNCode isn't 0, it's a wheel name - int nameCheck = (settings::APPROVEALLNAMES || save->iFNCode) ? 1 : 0; sqlite3_bind_int(stmt, 10, nameCheck); // blobs @@ -648,7 +645,7 @@ void Database::evaluateCustomName(int characterID, CustomName decision) { sqlite3_finalize(stmt); } -bool Database::changeName(sP_CL2LS_REQ_CHANGE_CHAR_NAME* save, int accountId) { +bool Database::changeName(int playerId, int accountId, const char* firstName, const char* lastName, int nameCheck) { std::lock_guard lock(dbCrit); const char* sql = R"( @@ -662,15 +659,10 @@ bool Database::changeName(sP_CL2LS_REQ_CHANGE_CHAR_NAME* save, int accountId) { sqlite3_stmt* stmt; sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); - std::string firstName = AUTOU16TOU8(save->szFirstName); - std::string lastName = AUTOU16TOU8(save->szLastName); - - sqlite3_bind_text(stmt, 1, firstName.c_str(), -1, NULL); - sqlite3_bind_text(stmt, 2, lastName.c_str(), -1, NULL); - // if FNCode isn't 0, it's a wheel name - int nameCheck = (settings::APPROVEALLNAMES || save->iFNCode) ? 1 : 0; + sqlite3_bind_text(stmt, 1, firstName, -1, NULL); + sqlite3_bind_text(stmt, 2, lastName, -1, NULL); sqlite3_bind_int(stmt, 3, nameCheck); - sqlite3_bind_int(stmt, 4, save->iPCUID); + sqlite3_bind_int(stmt, 4, playerId); sqlite3_bind_int(stmt, 5, accountId); int rc = sqlite3_step(stmt); diff --git a/src/servers/CNLoginServer.cpp b/src/servers/CNLoginServer.cpp index ab73b0f..fda4387 100644 --- a/src/servers/CNLoginServer.cpp +++ b/src/servers/CNLoginServer.cpp @@ -13,6 +13,12 @@ std::map CNLoginServer::loginSessions; +namespace LoginServer { + std::vector WheelFirstNames; + std::vector WheelMiddleNames; + std::vector WheelLastNames; +} + CNLoginServer::CNLoginServer(uint16_t p) { serverType = "login"; port = p; @@ -286,10 +292,29 @@ void CNLoginServer::nameSave(CNSocket* sock, CNPacketData* data) { INITSTRUCT(sP_LS2CL_REP_SAVE_CHAR_NAME_SUCC, resp); int errorCode = 0; - if (!CNLoginServer::isCharacterNameGood(AUTOU16TOU8(save->szFirstName), AUTOU16TOU8(save->szLastName))) { - errorCode = 4; - } else if (!Database::isNameFree(AUTOU16TOU8(save->szFirstName), AUTOU16TOU8(save->szLastName))) { - errorCode = 1; + + std::string firstName = AUTOU16TOU8(save->szFirstName); + std::string lastName = AUTOU16TOU8(save->szLastName); + int nameCheck = 0; + + // if FNCode isn't 0, it's a wheel name + if (save->iFNCode != 0) { + if (!CNLoginServer::isNameWheelNameGood(save->iFNCode, save->iMNCode, save->iLNCode, firstName, lastName)) { + errorCode = 4; + } else { + nameCheck = settings:: APPROVEWHEELNAMES ? 1 : 0; + } + } else { + // custom name + nameCheck = settings::APPROVECUSTOMNAMES ? 1 : 0; + } + + if (errorCode == 0) { + if (!CNLoginServer::isCharacterNameGood(firstName, lastName)) { + errorCode = 4; + } else if (!Database::isNameFree(firstName, lastName)) { + errorCode = 1; + } } if (errorCode != 0) { @@ -307,19 +332,16 @@ void CNLoginServer::nameSave(CNSocket* sock, CNPacketData* data) { if (!Database::isSlotFree(loginSessions[sock].userID, save->iSlotNum)) return invalidCharacter(sock); - resp.iPC_UID = Database::createCharacter(save, loginSessions[sock].userID); + resp.iPC_UID = Database::createCharacter(save->iSlotNum, loginSessions[sock].userID, firstName.c_str(), lastName.c_str(), nameCheck); // if query somehow failed if (resp.iPC_UID == 0) { std::cout << "[WARN] Login Server: Database failed to create new character!" << std::endl; return invalidCharacter(sock); } - Player plr; - Database::getPlayer(&plr, (int)resp.iPC_UID); - // fire name check event if needed - if (plr.PCStyle.iNameCheck != 1) { - std::string namereq = std::to_string(resp.iPC_UID) + " " + AUTOU16TOU8(save->szFirstName) + " " + AUTOU16TOU8(save->szLastName); + if (nameCheck != 1) { + std::string namereq = std::to_string(resp.iPC_UID) + " " + firstName + " " + lastName; Monitor::namereqs.push_back(namereq); } @@ -338,8 +360,8 @@ void CNLoginServer::nameSave(CNSocket* sock, CNPacketData* data) { DEBUGLOG( std::cout << "Login Server: new character created" << std::endl; std::cout << "\tSlot: " << (int)save->iSlotNum << std::endl; - std::cout << "\tName: " << AUTOU16TOU8(save->szFirstName) << " " << AUTOU16TOU8(save->szLastName); - if (plr.PCStyle.iNameCheck != 1) std::cout << " (pending approval)"; + std::cout << "\tName: " << firstName << " " << lastName; + if (nameCheck != 1) std::cout << " (pending approval)"; std::cout << std::endl; ) } @@ -507,11 +529,30 @@ void CNLoginServer::changeName(CNSocket* sock, CNPacketData* data) { auto save = (sP_CL2LS_REQ_CHANGE_CHAR_NAME*)data->buf; int errorCode = 0; - if (!CNLoginServer::isCharacterNameGood(AUTOU16TOU8(save->szFirstName), AUTOU16TOU8(save->szLastName))) { - errorCode = 4; + + std::string firstName = AUTOU16TOU8(save->szFirstName); + std::string lastName = AUTOU16TOU8(save->szLastName); + int nameCheck = 0; + + // if FNCode isn't 0, it's a wheel name + if (save->iFNCode != 0) { + if (!CNLoginServer::isNameWheelNameGood(save->iFNCode, save->iMNCode, save->iLNCode, firstName, lastName)) { + errorCode = 4; + } else { + nameCheck = settings::APPROVEWHEELNAMES ? 1 : 0; + } + } else { + // custom name + nameCheck = settings::APPROVECUSTOMNAMES ? 1 : 0; } - else if (!Database::isNameFree(AUTOU16TOU8(save->szFirstName), AUTOU16TOU8(save->szLastName))) { - errorCode = 1; + + if (errorCode == 0) { + if (!CNLoginServer::isCharacterNameGood(firstName, lastName)) { + errorCode = 4; + } + else if (!Database::isNameFree(firstName, lastName)) { + errorCode = 1; + } } if (errorCode != 0) { @@ -526,15 +567,12 @@ void CNLoginServer::changeName(CNSocket* sock, CNPacketData* data) { return; } - if (!Database::changeName(save, loginSessions[sock].userID)) + if (!Database::changeName(save->iPCUID, loginSessions[sock].userID, firstName.c_str(), lastName.c_str(), nameCheck)) return invalidCharacter(sock); - Player plr; - Database::getPlayer(&plr, (int)save->iPCUID); - // fire name check event if needed - if (plr.PCStyle.iNameCheck != 1) { - std::string namereq = std::to_string(save->iPCUID) + " " + AUTOU16TOU8(save->szFirstName) + " " + AUTOU16TOU8(save->szLastName); + if (nameCheck != 1) { + std::string namereq = std::to_string(save->iPCUID) + " " + firstName + " " + lastName; Monitor::namereqs.push_back(namereq); } @@ -550,8 +588,8 @@ void CNLoginServer::changeName(CNSocket* sock, CNPacketData* data) { DEBUGLOG( std::cout << "Login Server: Name change request for character [" << save->iPCUID << "]" << std::endl; - std::cout << "\tNew name: " << AUTOU16TOU8(save->szFirstName) << " " << AUTOU16TOU8(save->szLastName); - if (plr.PCStyle.iNameCheck != 1) std::cout << " (pending approval)"; + std::cout << "\tNew name: " << firstName << " " << lastName; + if (nameCheck != 1) std::cout << " (pending approval)"; std::cout << std::endl; ) } @@ -645,6 +683,42 @@ bool CNLoginServer::isPasswordCorrect(std::string actualPassword, std::string tr return BCrypt::validatePassword(tryPassword, actualPassword); } +bool CNLoginServer::isNameWheelNameGood(int fnCode, int mnCode, int lnCode, std::string& firstName, std::string& lastName) { + if (fnCode >= LoginServer::WheelFirstNames.size() + || mnCode >= LoginServer::WheelMiddleNames.size() + || lnCode >= LoginServer::WheelLastNames.size()) { + std::cout << "[WARN] Login Server: Invalid name codes received: " << fnCode << " " << mnCode << " " << lnCode << std::endl; + return false; + } + + // client sends 1 if not selected for these. they point to a single blank space. why. + // just change them to 0, which points to an empty string; keeps the code much cleaner + if (mnCode == 1) mnCode = 0; + if (lnCode == 1) lnCode = 0; + + std::string firstNameFromWheel = LoginServer::WheelFirstNames[fnCode]; + + std::string middleNamePart = LoginServer::WheelMiddleNames[mnCode]; + std::string lastNamePart = LoginServer::WheelLastNames[lnCode]; + if (mnCode != 0 && middleNamePart[middleNamePart.size() - 1] != ' ') { + // If there's a middle name, we need to lowercase the last name + std::transform(lastNamePart.begin(), lastNamePart.end(), lastNamePart.begin(), ::tolower); + } + std::string lastNameFromWheel = middleNamePart + lastNamePart; + + if (firstNameFromWheel.empty() || lastNameFromWheel.empty()) { + std::cout << "[WARN] Login Server: Invalid wheel name combo: " << fnCode << " " << mnCode << " " << lnCode << std::endl; + return false; + } + + if (firstName != firstNameFromWheel || lastName != lastNameFromWheel) { + std::cout << "[WARN] Login Server: Name wheel mismatch. Expected " << firstNameFromWheel << " " << lastNameFromWheel << ", got " << firstName << " " << lastName << std::endl; + return false; + } + + return true; +} + bool CNLoginServer::isCharacterNameGood(std::string Firstname, std::string Lastname) { //Allow alphanumeric and dot characters in names(disallows dot and space characters at the beginning of a name) std::regex firstnamecheck(R"(((?! )(?!\.)[a-zA-Z0-9]*\.{0,1}(?!\.+ +)[a-zA-Z0-9]* {0,1}(?! +))*$)"); diff --git a/src/servers/CNLoginServer.hpp b/src/servers/CNLoginServer.hpp index 091db10..af19e38 100644 --- a/src/servers/CNLoginServer.hpp +++ b/src/servers/CNLoginServer.hpp @@ -7,6 +7,12 @@ #include +namespace LoginServer { + extern std::vector WheelFirstNames; + extern std::vector WheelMiddleNames; + extern std::vector WheelLastNames; +} + struct CNLoginData { int userID; time_t lastHeartbeat; @@ -51,6 +57,7 @@ private: static bool isPasswordGood(std::string& password); static bool isPasswordCorrect(std::string actualPassword, std::string tryPassword); static bool isAccountInUse(int accountId); + static bool isNameWheelNameGood(int fnCode, int mnCode, int lnCode, std::string& firstName, std::string& lastName); static bool isCharacterNameGood(std::string Firstname, std::string Lastname); static bool isAuthMethodAllowed(AuthMethod authMethod); static bool checkUsername(CNSocket* sock, std::string& username); diff --git a/src/settings.cpp b/src/settings.cpp index 4445f8b..cf54242 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -12,7 +12,8 @@ bool settings::SANDBOX = true; std::string settings::SANDBOXEXTRAPATH = ""; int settings::LOGINPORT = 23000; -bool settings::APPROVEALLNAMES = true; +bool settings::APPROVEWHEELNAMES = true; +bool settings::APPROVECUSTOMNAMES = true; bool settings::AUTOCREATEACCOUNTS = true; std::string settings::AUTHMETHODS = "password"; int settings::DBSAVEINTERVAL = 240; @@ -89,7 +90,8 @@ void settings::init() { SANDBOX = reader.GetBoolean("", "sandbox", SANDBOX); SANDBOXEXTRAPATH = reader.Get("", "sandboxextrapath", SANDBOXEXTRAPATH); LOGINPORT = reader.GetInteger("login", "port", LOGINPORT); - APPROVEALLNAMES = reader.GetBoolean("login", "acceptallcustomnames", APPROVEALLNAMES); + APPROVEWHEELNAMES = reader.GetBoolean("login", "acceptallwheelnames", APPROVEWHEELNAMES); + APPROVECUSTOMNAMES = reader.GetBoolean("login", "acceptallcustomnames", APPROVECUSTOMNAMES); AUTOCREATEACCOUNTS = reader.GetBoolean("login", "autocreateaccounts", AUTOCREATEACCOUNTS); AUTHMETHODS = reader.Get("login", "authmethods", AUTHMETHODS); DBSAVEINTERVAL = reader.GetInteger("login", "dbsaveinterval", DBSAVEINTERVAL); diff --git a/src/settings.hpp b/src/settings.hpp index 6ae74f4..affe045 100644 --- a/src/settings.hpp +++ b/src/settings.hpp @@ -9,7 +9,8 @@ namespace settings { extern bool SANDBOX; extern std::string SANDBOXEXTRAPATH; extern int LOGINPORT; - extern bool APPROVEALLNAMES; + extern bool APPROVEWHEELNAMES; + extern bool APPROVECUSTOMNAMES; extern bool AUTOCREATEACCOUNTS; extern std::string AUTHMETHODS; extern int DBSAVEINTERVAL;