Validate name wheel names

This commit is contained in:
Gent Semaj 2025-01-06 23:43:37 -05:00
parent d06c324aa3
commit 6b9ae4c325
Signed by untrusted user: ycc
GPG Key ID: 2D76C57BF6BEADC4
8 changed files with 147 additions and 48 deletions

View File

@ -12,6 +12,8 @@ sandbox=true
[login] [login]
# must be kept in sync with loginInfo.php # must be kept in sync with loginInfo.php
port=23000 port=23000
# will all name wheel names be approved instantly?
acceptallwheelnames=true
# will all custom names be approved instantly? # will all custom names be approved instantly?
acceptallcustomnames=true acceptallcustomnames=true
# should attempts to log into non-existent accounts # should attempts to log into non-existent accounts

View File

@ -1,5 +1,7 @@
#include "TableData.hpp" #include "TableData.hpp"
#include "servers/CNLoginServer.hpp"
#include "NPCManager.hpp" #include "NPCManager.hpp"
#include "Missions.hpp" #include "Missions.hpp"
#include "Items.hpp" #include "Items.hpp"
@ -79,6 +81,25 @@ static void loadXDT(json& xdtData) {
NPCManager::NPCData = xdtData["m_pNpcTable"]["m_pNpcData"]; NPCManager::NPCData = xdtData["m_pNpcTable"]["m_pNpcData"];
try { 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 // load warps
json warpData = xdtData["m_pInstanceTable"]["m_pWarpData"]; json warpData = xdtData["m_pInstanceTable"]["m_pWarpData"];

View File

@ -69,7 +69,7 @@ namespace Database {
bool isNameFree(std::string firstName, std::string lastName); bool isNameFree(std::string firstName, std::string lastName);
bool isSlotFree(int accountId, int slotNum); bool isSlotFree(int accountId, int slotNum);
/// returns ID, 0 if something failed /// 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 /// returns true if query succeeded
bool finishCharacter(sP_CL2LS_REQ_CHAR_CREATE* character, int accountId); bool finishCharacter(sP_CL2LS_REQ_CHAR_CREATE* character, int accountId);
/// returns true if query succeeded /// returns true if query succeeded
@ -85,7 +85,7 @@ namespace Database {
}; };
void evaluateCustomName(int characterID, CustomName decision); void evaluateCustomName(int characterID, CustomName decision);
/// returns true if query succeeded /// 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 // getting players
void getPlayer(Player* plr, int id); void getPlayer(Player* plr, int id);

View File

@ -2,6 +2,8 @@
#include "db/internal.hpp" #include "db/internal.hpp"
#include "servers/CNLoginServer.hpp"
#include "bcrypt/BCrypt.hpp" #include "bcrypt/BCrypt.hpp"
void Database::findAccount(Account* account, std::string login) { void Database::findAccount(Account* account, std::string login) {
@ -291,7 +293,7 @@ bool Database::isSlotFree(int accountId, int slotNum) {
return result; 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<std::mutex> lock(dbCrit); std::lock_guard<std::mutex> lock(dbCrit);
sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL); 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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
)"; )";
sqlite3_stmt* stmt; sqlite3_stmt* stmt;
std::string firstName = AUTOU16TOU8(save->szFirstName);
std::string lastName = AUTOU16TOU8(save->szLastName);
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, AccountID); sqlite3_bind_int(stmt, 1, accountId);
sqlite3_bind_int(stmt, 2, save->iSlotNum); sqlite3_bind_int(stmt, 2, slot);
sqlite3_bind_text(stmt, 3, firstName.c_str(), -1, NULL); sqlite3_bind_text(stmt, 3, firstName, -1, NULL);
sqlite3_bind_text(stmt, 4, lastName.c_str(), -1, NULL); sqlite3_bind_text(stmt, 4, lastName, -1, NULL);
sqlite3_bind_int(stmt, 5, settings::SPAWN_X); sqlite3_bind_int(stmt, 5, settings::SPAWN_X);
sqlite3_bind_int(stmt, 6, settings::SPAWN_Y); sqlite3_bind_int(stmt, 6, settings::SPAWN_Y);
sqlite3_bind_int(stmt, 7, settings::SPAWN_Z); sqlite3_bind_int(stmt, 7, settings::SPAWN_Z);
sqlite3_bind_int(stmt, 8, settings::SPAWN_ANGLE); sqlite3_bind_int(stmt, 8, settings::SPAWN_ANGLE);
sqlite3_bind_int(stmt, 9, PC_MAXHEALTH(1)); 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); sqlite3_bind_int(stmt, 10, nameCheck);
// blobs // blobs
@ -648,7 +645,7 @@ void Database::evaluateCustomName(int characterID, CustomName decision) {
sqlite3_finalize(stmt); 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<std::mutex> lock(dbCrit); std::lock_guard<std::mutex> lock(dbCrit);
const char* sql = R"( const char* sql = R"(
@ -662,15 +659,10 @@ bool Database::changeName(sP_CL2LS_REQ_CHANGE_CHAR_NAME* save, int accountId) {
sqlite3_stmt* stmt; sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
std::string firstName = AUTOU16TOU8(save->szFirstName); sqlite3_bind_text(stmt, 1, firstName, -1, NULL);
std::string lastName = AUTOU16TOU8(save->szLastName); sqlite3_bind_text(stmt, 2, lastName, -1, NULL);
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_int(stmt, 3, nameCheck); 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); sqlite3_bind_int(stmt, 5, accountId);
int rc = sqlite3_step(stmt); int rc = sqlite3_step(stmt);

View File

@ -13,6 +13,12 @@
std::map<CNSocket*, CNLoginData> CNLoginServer::loginSessions; std::map<CNSocket*, CNLoginData> CNLoginServer::loginSessions;
namespace LoginServer {
std::vector<std::string> WheelFirstNames;
std::vector<std::string> WheelMiddleNames;
std::vector<std::string> WheelLastNames;
}
CNLoginServer::CNLoginServer(uint16_t p) { CNLoginServer::CNLoginServer(uint16_t p) {
serverType = "login"; serverType = "login";
port = p; port = p;
@ -286,10 +292,29 @@ void CNLoginServer::nameSave(CNSocket* sock, CNPacketData* data) {
INITSTRUCT(sP_LS2CL_REP_SAVE_CHAR_NAME_SUCC, resp); INITSTRUCT(sP_LS2CL_REP_SAVE_CHAR_NAME_SUCC, resp);
int errorCode = 0; int errorCode = 0;
if (!CNLoginServer::isCharacterNameGood(AUTOU16TOU8(save->szFirstName), AUTOU16TOU8(save->szLastName))) {
errorCode = 4; std::string firstName = AUTOU16TOU8(save->szFirstName);
} else if (!Database::isNameFree(AUTOU16TOU8(save->szFirstName), AUTOU16TOU8(save->szLastName))) { std::string lastName = AUTOU16TOU8(save->szLastName);
errorCode = 1; 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) { if (errorCode != 0) {
@ -307,19 +332,16 @@ void CNLoginServer::nameSave(CNSocket* sock, CNPacketData* data) {
if (!Database::isSlotFree(loginSessions[sock].userID, save->iSlotNum)) if (!Database::isSlotFree(loginSessions[sock].userID, save->iSlotNum))
return invalidCharacter(sock); 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 query somehow failed
if (resp.iPC_UID == 0) { if (resp.iPC_UID == 0) {
std::cout << "[WARN] Login Server: Database failed to create new character!" << std::endl; std::cout << "[WARN] Login Server: Database failed to create new character!" << std::endl;
return invalidCharacter(sock); return invalidCharacter(sock);
} }
Player plr;
Database::getPlayer(&plr, (int)resp.iPC_UID);
// fire name check event if needed // fire name check event if needed
if (plr.PCStyle.iNameCheck != 1) { if (nameCheck != 1) {
std::string namereq = std::to_string(resp.iPC_UID) + " " + AUTOU16TOU8(save->szFirstName) + " " + AUTOU16TOU8(save->szLastName); std::string namereq = std::to_string(resp.iPC_UID) + " " + firstName + " " + lastName;
Monitor::namereqs.push_back(namereq); Monitor::namereqs.push_back(namereq);
} }
@ -338,8 +360,8 @@ void CNLoginServer::nameSave(CNSocket* sock, CNPacketData* data) {
DEBUGLOG( DEBUGLOG(
std::cout << "Login Server: new character created" << std::endl; std::cout << "Login Server: new character created" << std::endl;
std::cout << "\tSlot: " << (int)save->iSlotNum << std::endl; std::cout << "\tSlot: " << (int)save->iSlotNum << std::endl;
std::cout << "\tName: " << AUTOU16TOU8(save->szFirstName) << " " << AUTOU16TOU8(save->szLastName); std::cout << "\tName: " << firstName << " " << lastName;
if (plr.PCStyle.iNameCheck != 1) std::cout << " (pending approval)"; if (nameCheck != 1) std::cout << " (pending approval)";
std::cout << std::endl; 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; auto save = (sP_CL2LS_REQ_CHANGE_CHAR_NAME*)data->buf;
int errorCode = 0; 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) { if (errorCode != 0) {
@ -526,15 +567,12 @@ void CNLoginServer::changeName(CNSocket* sock, CNPacketData* data) {
return; 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); return invalidCharacter(sock);
Player plr;
Database::getPlayer(&plr, (int)save->iPCUID);
// fire name check event if needed // fire name check event if needed
if (plr.PCStyle.iNameCheck != 1) { if (nameCheck != 1) {
std::string namereq = std::to_string(save->iPCUID) + " " + AUTOU16TOU8(save->szFirstName) + " " + AUTOU16TOU8(save->szLastName); std::string namereq = std::to_string(save->iPCUID) + " " + firstName + " " + lastName;
Monitor::namereqs.push_back(namereq); Monitor::namereqs.push_back(namereq);
} }
@ -550,8 +588,8 @@ void CNLoginServer::changeName(CNSocket* sock, CNPacketData* data) {
DEBUGLOG( DEBUGLOG(
std::cout << "Login Server: Name change request for character [" << save->iPCUID << "]" << std::endl; std::cout << "Login Server: Name change request for character [" << save->iPCUID << "]" << std::endl;
std::cout << "\tNew name: " << AUTOU16TOU8(save->szFirstName) << " " << AUTOU16TOU8(save->szLastName); std::cout << "\tNew name: " << firstName << " " << lastName;
if (plr.PCStyle.iNameCheck != 1) std::cout << " (pending approval)"; if (nameCheck != 1) std::cout << " (pending approval)";
std::cout << std::endl; std::cout << std::endl;
) )
} }
@ -645,6 +683,42 @@ bool CNLoginServer::isPasswordCorrect(std::string actualPassword, std::string tr
return BCrypt::validatePassword(tryPassword, actualPassword); 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) { 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) //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}(?! +))*$)"); std::regex firstnamecheck(R"(((?! )(?!\.)[a-zA-Z0-9]*\.{0,1}(?!\.+ +)[a-zA-Z0-9]* {0,1}(?! +))*$)");

View File

@ -7,6 +7,12 @@
#include <map> #include <map>
namespace LoginServer {
extern std::vector<std::string> WheelFirstNames;
extern std::vector<std::string> WheelMiddleNames;
extern std::vector<std::string> WheelLastNames;
}
struct CNLoginData { struct CNLoginData {
int userID; int userID;
time_t lastHeartbeat; time_t lastHeartbeat;
@ -51,6 +57,7 @@ private:
static bool isPasswordGood(std::string& password); static bool isPasswordGood(std::string& password);
static bool isPasswordCorrect(std::string actualPassword, std::string tryPassword); static bool isPasswordCorrect(std::string actualPassword, std::string tryPassword);
static bool isAccountInUse(int accountId); 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 isCharacterNameGood(std::string Firstname, std::string Lastname);
static bool isAuthMethodAllowed(AuthMethod authMethod); static bool isAuthMethodAllowed(AuthMethod authMethod);
static bool checkUsername(CNSocket* sock, std::string& username); static bool checkUsername(CNSocket* sock, std::string& username);

View File

@ -12,7 +12,8 @@ bool settings::SANDBOX = true;
std::string settings::SANDBOXEXTRAPATH = ""; std::string settings::SANDBOXEXTRAPATH = "";
int settings::LOGINPORT = 23000; int settings::LOGINPORT = 23000;
bool settings::APPROVEALLNAMES = true; bool settings::APPROVEWHEELNAMES = true;
bool settings::APPROVECUSTOMNAMES = true;
bool settings::AUTOCREATEACCOUNTS = true; bool settings::AUTOCREATEACCOUNTS = true;
std::string settings::AUTHMETHODS = "password"; std::string settings::AUTHMETHODS = "password";
int settings::DBSAVEINTERVAL = 240; int settings::DBSAVEINTERVAL = 240;
@ -89,7 +90,8 @@ void settings::init() {
SANDBOX = reader.GetBoolean("", "sandbox", SANDBOX); SANDBOX = reader.GetBoolean("", "sandbox", SANDBOX);
SANDBOXEXTRAPATH = reader.Get("", "sandboxextrapath", SANDBOXEXTRAPATH); SANDBOXEXTRAPATH = reader.Get("", "sandboxextrapath", SANDBOXEXTRAPATH);
LOGINPORT = reader.GetInteger("login", "port", LOGINPORT); 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); AUTOCREATEACCOUNTS = reader.GetBoolean("login", "autocreateaccounts", AUTOCREATEACCOUNTS);
AUTHMETHODS = reader.Get("login", "authmethods", AUTHMETHODS); AUTHMETHODS = reader.Get("login", "authmethods", AUTHMETHODS);
DBSAVEINTERVAL = reader.GetInteger("login", "dbsaveinterval", DBSAVEINTERVAL); DBSAVEINTERVAL = reader.GetInteger("login", "dbsaveinterval", DBSAVEINTERVAL);

View File

@ -9,7 +9,8 @@ namespace settings {
extern bool SANDBOX; extern bool SANDBOX;
extern std::string SANDBOXEXTRAPATH; extern std::string SANDBOXEXTRAPATH;
extern int LOGINPORT; extern int LOGINPORT;
extern bool APPROVEALLNAMES; extern bool APPROVEWHEELNAMES;
extern bool APPROVECUSTOMNAMES;
extern bool AUTOCREATEACCOUNTS; extern bool AUTOCREATEACCOUNTS;
extern std::string AUTHMETHODS; extern std::string AUTHMETHODS;
extern int DBSAVEINTERVAL; extern int DBSAVEINTERVAL;