From 35b65ed9979385e63179520e6eaa556559dedb3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kyle=20=F0=9F=90=86?= Date: Thu, 12 Feb 2026 13:36:09 -0500 Subject: [PATCH 1/3] Add null pointer safety and replace assertions with runtime checks --- src/Abilities.cpp | 8 ++++++-- src/Buddies.cpp | 13 +++++++++++++ src/Buffs.cpp | 3 ++- src/BuiltinCommands.cpp | 18 +++++++++++++++++- src/Chat.cpp | 16 +++++++++++++--- src/Chunking.cpp | 10 +++++++--- src/Combat.cpp | 13 ++++++++++++- src/CustomCommands.cpp | 32 +++++++++++++++++++++++++++++++- src/Eggs.cpp | 7 ++++--- src/Email.cpp | 7 ++++++- src/Entities.cpp | 5 ++--- src/Groups.cpp | 18 +++++++++++++++--- src/Groups.hpp | 4 ++-- src/Items.cpp | 19 ++++++++++++++----- src/Missions.cpp | 14 +++++++++++++- src/MobAI.cpp | 26 ++++++++++++++++++++++---- src/NPCManager.cpp | 9 ++++++++- src/Nanos.cpp | 10 ++++++++++ src/PlayerManager.cpp | 31 ++++++++++++++++++++++--------- src/PlayerManager.hpp | 1 + src/PlayerMovement.cpp | 9 +++++++++ src/Racing.cpp | 2 ++ src/Trading.cpp | 17 ++++++++++++++++- src/Transport.cpp | 2 ++ src/Vendors.cpp | 7 +++++++ src/core/CNProtocol.cpp | 15 ++++++--------- src/db/shard.cpp | 8 +++++--- 27 files changed, 267 insertions(+), 57 deletions(-) diff --git a/src/Abilities.cpp b/src/Abilities.cpp index fc5e61575..d908122b0 100644 --- a/src/Abilities.cpp +++ b/src/Abilities.cpp @@ -309,6 +309,7 @@ static void attachSkillResults(std::vector results, uint8_t* pivot) void Abilities::useNanoSkill(CNSocket* sock, SkillData* skill, sNano& nano, std::vector affected) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; ICombatant* combatant = dynamic_cast(plr); int boost = 0; @@ -401,8 +402,11 @@ void Abilities::useNPCSkill(EntityRef npc, int skillID, std::vector static std::vector entityRefsToCombatants(std::vector refs) { std::vector combatants; for(EntityRef ref : refs) { - if(ref.kind == EntityKind::PLAYER) - combatants.push_back(dynamic_cast(PlayerManager::getPlayer(ref.sock))); + if(ref.kind == EntityKind::PLAYER) { + Player* p = PlayerManager::getPlayer(ref.sock); + if (p != nullptr) + combatants.push_back(dynamic_cast(p)); + } else if(ref.kind == EntityKind::COMBAT_NPC || ref.kind == EntityKind::MOB) combatants.push_back(dynamic_cast(ref.getEntity())); } diff --git a/src/Buddies.cpp b/src/Buddies.cpp index 1fbf31d5e..ea00e2891 100644 --- a/src/Buddies.cpp +++ b/src/Buddies.cpp @@ -32,6 +32,7 @@ static bool playerHasBuddyWithID(Player* plr, int buddyID) { // Refresh buddy list void Buddies::sendBuddyList(CNSocket* sock) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; int buddyCnt = Database::getNumBuddies(plr); if (!validOutVarPacket(sizeof(sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC), buddyCnt, sizeof(sBuddyBaseInfo))) { @@ -85,6 +86,7 @@ static void requestBuddy(CNSocket* sock, CNPacketData* data) { auto req = (sP_CL2FE_REQ_REQUEST_MAKE_BUDDY*)data->buf; Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; Player* otherPlr = PlayerManager::getPlayerFromID(req->iBuddyID); if (otherPlr == nullptr) @@ -123,6 +125,7 @@ static void reqBuddyByName(CNSocket* sock, CNPacketData* data) { auto pkt = (sP_CL2FE_REQ_PC_FIND_NAME_MAKE_BUDDY*)data->buf; Player* plrReq = PlayerManager::getPlayer(sock); + if (plrReq == nullptr) return; INITSTRUCT(sP_FE2CL_REP_PC_FIND_NAME_MAKE_BUDDY_SUCC, resp); CNSocket* otherSock = PlayerManager::getSockFromName(AUTOU16TOU8(pkt->szFirstName), AUTOU16TOU8(pkt->szLastName)); @@ -130,6 +133,7 @@ static void reqBuddyByName(CNSocket* sock, CNPacketData* data) { return; // no player found Player *otherPlr = PlayerManager::getPlayer(otherSock); + if (otherPlr == nullptr) return; if (playerHasBuddyWithID(plrReq, otherPlr->iID)) return; @@ -145,6 +149,7 @@ static void reqBuddyByName(CNSocket* sock, CNPacketData* data) { static void reqAcceptBuddy(CNSocket* sock, CNPacketData* data) { auto req = (sP_CL2FE_REQ_ACCEPT_MAKE_BUDDY*)data->buf; Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; Player* otherPlr = PlayerManager::getPlayerFromID(req->iBuddyID); if (otherPlr == nullptr) @@ -213,6 +218,7 @@ static void reqFindNameBuddyAccept(CNSocket* sock, CNPacketData* data) { Player* plrReq = PlayerManager::getPlayer(sock); + if (plrReq == nullptr) return; INITSTRUCT(sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC, resp); Player* otherPlr = PlayerManager::getPlayerFromID(pkt->iBuddyPCUID); @@ -278,6 +284,7 @@ static void reqFindNameBuddyAccept(CNSocket* sock, CNPacketData* data) { static void reqPktGetBuddyState(CNSocket* sock, CNPacketData* data) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; INITSTRUCT(sP_FE2CL_REP_GET_BUDDY_STATE_SUCC, resp); for (int slot = 0; slot < 50; slot++) { @@ -293,6 +300,7 @@ static void reqBuddyBlock(CNSocket* sock, CNPacketData* data) { auto pkt = (sP_CL2FE_REQ_SET_BUDDY_BLOCK*)data->buf; Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; // sanity checks if (pkt->iBuddySlot < 0 || pkt->iBuddySlot >= 50 || plr->buddyIDs[pkt->iBuddySlot] != pkt->iBuddyPCUID) return; @@ -317,6 +325,7 @@ static void reqBuddyBlock(CNSocket* sock, CNPacketData* data) { if (otherSock == nullptr) return; // other player isn't online, no broadcast needed Player* otherPlr = PlayerManager::getPlayer(otherSock); + if (otherPlr == nullptr) return; // search for the slot with the requesting player's ID otherResp.iBuddyPCUID = plr->PCStyle.iPC_UID; for (int i = 0; i < 50; i++) { @@ -336,6 +345,7 @@ static void reqPlayerBlock(CNSocket* sock, CNPacketData* data) { auto pkt = (sP_CL2FE_REQ_SET_PC_BLOCK*)data->buf; Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; int buddySlot = getAvailableBuddySlot(plr); if (buddySlot == -1) return; @@ -363,6 +373,7 @@ static void reqBuddyDelete(CNSocket* sock, CNPacketData* data) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; // remove buddy on our side INITSTRUCT(sP_FE2CL_REP_REMOVE_BUDDY_SUCC, resp); resp.iBuddyPCUID = pkt->iBuddyPCUID; @@ -389,6 +400,7 @@ static void reqBuddyDelete(CNSocket* sock, CNPacketData* data) { if (otherSock == nullptr) return; // other player isn't online, no broadcast needed Player* otherPlr = PlayerManager::getPlayer(otherSock); + if (otherPlr == nullptr) return; // search for the slot with the requesting player's ID resp.iBuddyPCUID = plr->PCStyle.iPC_UID; for (int i = 0; i < 50; i++) { @@ -407,6 +419,7 @@ static void reqBuddyDelete(CNSocket* sock, CNPacketData* data) { static void reqBuddyWarp(CNSocket* sock, CNPacketData* data) { Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; auto pkt = (sP_CL2FE_REQ_PC_BUDDY_WARP*)data->buf; if (pkt->iSlotNum < 0 || pkt->iSlotNum >= 50) diff --git a/src/Buffs.cpp b/src/Buffs.cpp index d225c94bf..50fb92fda 100644 --- a/src/Buffs.cpp +++ b/src/Buffs.cpp @@ -178,7 +178,8 @@ void Buffs::tickDrain(EntityRef self, Buff* buff, int mult) { int dealt = combatant->takeDamage(buff->getLastSource(), damage); size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_Damage); - assert(resplen < CN_PACKET_BODY_SIZE); + if (resplen >= CN_PACKET_BODY_SIZE) + return; uint8_t respbuf[CN_PACKET_BODY_SIZE]; memset(respbuf, 0, CN_PACKET_BODY_SIZE); diff --git a/src/BuiltinCommands.cpp b/src/BuiltinCommands.cpp index 6ea442737..c9f265768 100644 --- a/src/BuiltinCommands.cpp +++ b/src/BuiltinCommands.cpp @@ -14,6 +14,7 @@ void BuiltinCommands::setSpecialState(CNSocket* sock, CNPacketData* data) { auto setData = (sP_CL2FE_GM_REQ_PC_SPECIAL_STATE_SWITCH*)data->buf; Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; // HACK: work around the invisible weapon bug if (setData->iSpecialStateFlag == CN_SPECIAL_STATE_FLAG__FULL_UI) Items::updateEquips(sock, plr); @@ -31,7 +32,8 @@ void BuiltinCommands::setSpecialState(CNSocket* sock, CNPacketData* data) { } static void setGMSpecialSwitchPlayer(CNSocket* sock, CNPacketData* data) { - if (PlayerManager::getPlayer(sock)->accountLevel > 30) + Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr || plr->accountLevel > 30) return; BuiltinCommands::setSpecialState(sock, data); @@ -39,6 +41,7 @@ static void setGMSpecialSwitchPlayer(CNSocket* sock, CNPacketData* data) { static void gotoPlayer(CNSocket* sock, CNPacketData* data) { Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; if (plr->accountLevel > 50) return; @@ -56,6 +59,7 @@ static void gotoPlayer(CNSocket* sock, CNPacketData* data) { static void setValuePlayer(CNSocket* sock, CNPacketData* data) { Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; if (plr->accountLevel > 50) return; @@ -126,6 +130,7 @@ static void setValuePlayer(CNSocket* sock, CNPacketData* data) { static void setGMSpecialOnOff(CNSocket *sock, CNPacketData *data) { Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; // access check if (plr->accountLevel > 30) return; @@ -140,6 +145,7 @@ static void setGMSpecialOnOff(CNSocket *sock, CNPacketData *data) { } Player *otherPlr = PlayerManager::getPlayer(otherSock); + if (otherPlr == nullptr) return; if (req->iONOFF) otherPlr->iSpecialState |= req->iSpecialStateFlag; else @@ -151,6 +157,7 @@ static void setGMSpecialOnOff(CNSocket *sock, CNPacketData *data) { static void locatePlayer(CNSocket *sock, CNPacketData *data) { Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; // access check if (plr->accountLevel > 30) return; @@ -167,6 +174,7 @@ static void locatePlayer(CNSocket *sock, CNPacketData *data) { INITSTRUCT(sP_FE2CL_GM_REP_PC_LOCATION, resp); Player *otherPlr = PlayerManager::getPlayer(otherSock); + if (otherPlr == nullptr) return; resp.iTargetPC_UID = otherPlr->accountId; resp.iTargetPC_ID = otherPlr->iID; resp.iShardID = 0; // sharding is unsupported @@ -186,6 +194,7 @@ static void locatePlayer(CNSocket *sock, CNPacketData *data) { static void kickPlayer(CNSocket *sock, CNPacketData *data) { Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; // access check if (plr->accountLevel > 30) return; @@ -201,6 +210,7 @@ static void kickPlayer(CNSocket *sock, CNPacketData *data) { Player *otherPlr = PlayerManager::getPlayer(otherSock); + if (otherPlr == nullptr) return; if (plr->accountLevel > otherPlr->accountLevel) { Chat::sendServerMessage(sock, "player has higher access level"); return; @@ -221,6 +231,7 @@ static void kickPlayer(CNSocket *sock, CNPacketData *data) { static void warpToPlayer(CNSocket *sock, CNPacketData *data) { Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; // access check if (plr->accountLevel > 30) return; @@ -240,6 +251,7 @@ static void warpToPlayer(CNSocket *sock, CNPacketData *data) { static void teleportPlayer(CNSocket *sock, CNPacketData *data) { Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; // access check if (plr->accountLevel > 30) return; @@ -279,11 +291,13 @@ static void teleportPlayer(CNSocket *sock, CNPacketData *data) { return; } goalPlr = PlayerManager::getPlayer(goalSock); + if (goalPlr == nullptr) return; PlayerManager::sendPlayerTo(targetSock, goalPlr->x, goalPlr->y, goalPlr->z, goalPlr->instanceID); break; case eCN_GM_TeleportType::Unstick: targetPlr = PlayerManager::getPlayer(targetSock); + if (targetPlr == nullptr) return; PlayerManager::sendPlayerTo(targetSock, targetPlr->x - unstickRange/2 + Rand::rand(unstickRange), targetPlr->y - unstickRange/2 + Rand::rand(unstickRange), targetPlr->z + 80); @@ -295,6 +309,7 @@ static void itemGMGiveHandler(CNSocket* sock, CNPacketData* data) { auto itemreq = (sP_CL2FE_REQ_PC_GIVE_ITEM*)data->buf; Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; if (plr->accountLevel > 50) { // TODO: send fail packet return; @@ -352,6 +367,7 @@ static void nanoGMGiveHandler(CNSocket* sock, CNPacketData* data) { auto nano = (sP_CL2FE_REQ_PC_GIVE_NANO*)data->buf; Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; if (plr->accountLevel > 50) return; diff --git a/src/Chat.cpp b/src/Chat.cpp index c4eeb36da..0abf744a9 100644 --- a/src/Chat.cpp +++ b/src/Chat.cpp @@ -7,14 +7,13 @@ #include "PlayerManager.hpp" #include "CustomCommands.hpp" -#include - using namespace Chat; static void chatHandler(CNSocket* sock, CNPacketData* data) { auto chat = (sP_CL2FE_REQ_SEND_FREECHAT_MESSAGE*)data->buf; Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; std::string fullChat = sanitizeText(AUTOU16TOU8(chat->szFreeChat)); if (fullChat.length() > 1 && fullChat[0] == CMD_PREFIX) { // PREFIX CustomCommands::runCmd(fullChat, sock); @@ -46,6 +45,7 @@ static void menuChatHandler(CNSocket* sock, CNPacketData* data) { auto chat = (sP_CL2FE_REQ_SEND_MENUCHAT_MESSAGE*)data->buf; Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; std::string fullChat = sanitizeText(AUTOU16TOU8(chat->szFreeChat)); std::string logLine = "[MenuChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat; @@ -56,7 +56,7 @@ static void menuChatHandler(CNSocket* sock, CNPacketData* data) { INITSTRUCT(sP_FE2CL_REP_SEND_MENUCHAT_MESSAGE_SUCC, resp); U8toU16(fullChat, (char16_t*)&resp.szFreeChat, sizeof(resp.szFreeChat)); - resp.iPC_ID = PlayerManager::getPlayer(sock)->iID; + resp.iPC_ID = plr->iID; resp.iEmoteCode = chat->iEmoteCode; sock->sendPacket(resp, P_FE2CL_REP_SEND_MENUCHAT_MESSAGE_SUCC); @@ -69,6 +69,7 @@ static void emoteHandler(CNSocket* sock, CNPacketData* data) { auto emote = (sP_CL2FE_REQ_PC_AVATAR_EMOTES_CHAT*)data->buf; Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; // send to client INITSTRUCT(sP_FE2CL_REP_PC_AVATAR_EMOTES_CHAT, resp); resp.iEmoteCode = emote->iEmoteCode; @@ -92,6 +93,7 @@ void Chat::sendServerMessage(CNSocket* sock, std::string msg) { static void announcementHandler(CNSocket* sock, CNPacketData* data) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; if (plr->accountLevel > 30) return; // only players with account level less than 30 (GM) are allowed to use this command auto announcement = (sP_CL2FE_GM_REQ_PC_ANNOUNCE*)data->buf; @@ -132,6 +134,7 @@ static void buddyChatHandler(CNSocket* sock, CNPacketData* data) { auto pkt = (sP_CL2FE_REQ_SEND_BUDDY_FREECHAT_MESSAGE*)data->buf; Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; INITSTRUCT(sP_FE2CL_REP_SEND_BUDDY_FREECHAT_MESSAGE_SUCC, resp); CNSocket* otherSock = PlayerManager::getSockFromID(pkt->iBuddyPCUID); @@ -141,6 +144,7 @@ static void buddyChatHandler(CNSocket* sock, CNPacketData* data) { Player *otherPlr = PlayerManager::getPlayer(otherSock); + if (otherPlr == nullptr) return; resp.iFromPCUID = plr->PCStyle.iPC_UID; resp.iToPCUID = pkt->iBuddyPCUID; resp.iEmoteCode = pkt->iEmoteCode; @@ -170,6 +174,7 @@ static void buddyMenuChatHandler(CNSocket* sock, CNPacketData* data) { auto pkt = (sP_CL2FE_REQ_SEND_BUDDY_MENUCHAT_MESSAGE*)data->buf; Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; INITSTRUCT(sP_FE2CL_REP_SEND_BUDDY_MENUCHAT_MESSAGE_SUCC, resp); CNSocket* otherSock = PlayerManager::getSockFromID(pkt->iBuddyPCUID); @@ -179,6 +184,7 @@ static void buddyMenuChatHandler(CNSocket* sock, CNPacketData* data) { Player *otherPlr = PlayerManager::getPlayer(otherSock); + if (otherPlr == nullptr) return; resp.iFromPCUID = plr->PCStyle.iPC_UID; resp.iToPCUID = pkt->iBuddyPCUID; resp.iEmoteCode = pkt->iEmoteCode; @@ -209,8 +215,10 @@ static void tradeChatHandler(CNSocket* sock, CNPacketData* data) { Player *otherPlr = PlayerManager::getPlayer(otherSock); + if (otherPlr == nullptr) return; INITSTRUCT(sP_FE2CL_REP_PC_TRADE_EMOTES_CHAT, resp); Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; resp.iID_Request = pacdat->iID_Request; resp.iID_From = pacdat->iID_From; resp.iID_To = pacdat->iID_To; @@ -231,6 +239,7 @@ static void groupChatHandler(CNSocket* sock, CNPacketData* data) { sP_CL2FE_REQ_SEND_ALL_GROUP_FREECHAT_MESSAGE* chat = (sP_CL2FE_REQ_SEND_ALL_GROUP_FREECHAT_MESSAGE*)data->buf; Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; std::string fullChat = sanitizeText(AUTOU16TOU8(chat->szFreeChat)); if (fullChat.length() > 1 && fullChat[0] == CMD_PREFIX) { // PREFIX @@ -262,6 +271,7 @@ static void groupMenuChatHandler(CNSocket* sock, CNPacketData* data) { sP_CL2FE_REQ_SEND_ALL_GROUP_MENUCHAT_MESSAGE* chat = (sP_CL2FE_REQ_SEND_ALL_GROUP_MENUCHAT_MESSAGE*)data->buf; Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; std::string fullChat = sanitizeText(AUTOU16TOU8(chat->szFreeChat)); std::string logLine = "[GroupMenuChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat; diff --git a/src/Chunking.cpp b/src/Chunking.cpp index a3facaad8..cad7030a7 100644 --- a/src/Chunking.cpp +++ b/src/Chunking.cpp @@ -76,7 +76,10 @@ void Chunking::untrackEntity(ChunkPos chunkPos, const EntityRef ref) { if (ref.kind == EntityKind::PLAYER) chunks[chunkPos]->nplayers--; - assert(chunks[chunkPos]->nplayers >= 0); + if (chunks[chunkPos]->nplayers < 0) { + std::cout << "[WARN] Chunk player count went negative" << std::endl; + chunks[chunkPos]->nplayers = 0; + } // if chunk is completely empty, free it if (chunk->entities.size() == 0) @@ -85,7 +88,8 @@ void Chunking::untrackEntity(ChunkPos chunkPos, const EntityRef ref) { template static void sendAroundPackets(const EntityRef recipient, std::vector>& buckets, uint32_t packetId) { - assert(recipient.kind == EntityKind::PLAYER); + if (recipient.kind != EntityKind::PLAYER) + return; uint8_t pktBuf[CN_PACKET_BODY_SIZE]; for (const auto& bucket : buckets) { @@ -311,7 +315,7 @@ static void emptyChunk(ChunkPos chunkPos) { std::set refs(chunk->entities); for (const EntityRef ref : refs) { if (ref.kind == EntityKind::PLAYER) - assert(0); + continue; // every call of this will check if the chunk is empty and delete it if so NPCManager::destroyNPC(ref.id); diff --git a/src/Combat.cpp b/src/Combat.cpp index 2820e8c82..220fdc960 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -367,6 +367,7 @@ static std::pair getDamage(int attackPower, int defensePower, bool shou static bool checkRapidFire(CNSocket *sock, int targetCount, bool allowManyTargets) { Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return true; time_t currTime = getTime(); if (currTime - plr->lastShot < plr->fireRate * 80) @@ -393,6 +394,7 @@ static bool checkRapidFire(CNSocket *sock, int targetCount, bool allowManyTarget static void pcAttackNpcs(CNSocket *sock, CNPacketData *data) { auto pkt = (sP_CL2FE_REQ_PC_ATTACK_NPCs*)data->buf; Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; auto targets = (int32_t*)data->trailers; // kick the player if firing too rapidly @@ -514,6 +516,7 @@ void Combat::genQItemRolls(std::vector players, std::map& rol static void combatBegin(CNSocket *sock, CNPacketData *data) { Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; plr->inCombat = true; // HACK: make sure the player has the right weapon out for combat @@ -529,17 +532,20 @@ static void combatBegin(CNSocket *sock, CNPacketData *data) { static void combatEnd(CNSocket *sock, CNPacketData *data) { Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; plr->inCombat = false; plr->healCooldown = 4000; } static void dealGooDamage(CNSocket *sock) { Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; if(plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE) return; // ignore completely size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_DotDamage); - assert(resplen < CN_PACKET_BODY_SIZE); + if (resplen >= CN_PACKET_BODY_SIZE) + return; uint8_t respbuf[CN_PACKET_BODY_SIZE]; memset(respbuf, 0, CN_PACKET_BODY_SIZE); @@ -587,6 +593,7 @@ static void dotDamageOnOff(CNSocket *sock, CNPacketData *data) { sP_CL2FE_DOT_DAMAGE_ONOFF *pkt = (sP_CL2FE_DOT_DAMAGE_ONOFF*)data->buf; Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; // infection debuff toggles as the client asks it to, // so we add and remove a permanent debuff if (pkt->iFlag && !plr->hasBuff(ECSB_INFECTION)) { @@ -614,6 +621,7 @@ static void pcAttackChars(CNSocket *sock, CNPacketData *data) { sP_CL2FE_REQ_PC_ATTACK_CHARs* pkt = (sP_CL2FE_REQ_PC_ATTACK_CHARs*)data->buf; Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; // only GMs can use this variant if (plr->accountLevel > 30) return; @@ -753,6 +761,7 @@ static void grenadeFire(CNSocket* sock, CNPacketData* data) { sP_CL2FE_REQ_PC_GRENADE_STYLE_FIRE* grenade = (sP_CL2FE_REQ_PC_GRENADE_STYLE_FIRE*)data->buf; Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; INITSTRUCT(sP_FE2CL_REP_PC_GRENADE_STYLE_FIRE_SUCC, resp); resp.iToX = grenade->iToX; resp.iToY = grenade->iToY; @@ -781,6 +790,7 @@ static void rocketFire(CNSocket* sock, CNPacketData* data) { sP_CL2FE_REQ_PC_ROCKET_STYLE_FIRE* rocket = (sP_CL2FE_REQ_PC_ROCKET_STYLE_FIRE*)data->buf; Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; // We should be sending back rocket succ packet, but it doesn't work, and this one works INITSTRUCT(sP_FE2CL_REP_PC_GRENADE_STYLE_FIRE_SUCC, resp); resp.iToX = rocket->iToX; @@ -811,6 +821,7 @@ static void projectileHit(CNSocket* sock, CNPacketData* data) { sP_CL2FE_REQ_PC_ROCKET_STYLE_HIT* pkt = (sP_CL2FE_REQ_PC_ROCKET_STYLE_HIT*)data->buf; Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; if (pkt->iTargetCnt == 0) { Bullets[plr->iID].erase(pkt->iBulletID); // no targets hit, don't send response diff --git a/src/CustomCommands.cpp b/src/CustomCommands.cpp index 693485daa..96bc0338c 100644 --- a/src/CustomCommands.cpp +++ b/src/CustomCommands.cpp @@ -44,10 +44,11 @@ bool CustomCommands::runCmd(std::string full, CNSocket* sock) { // check if the command exists if (commands.find(cmd) != commands.end()) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return false; ChatCommand command = commands[cmd]; // sanity check + does the player have the required account level to use the command? - if (plr != nullptr && plr->accountLevel <= command.requiredAccLevel) { + if (plr->accountLevel <= command.requiredAccLevel) { command.handlr(full, args, sock); return true; } else { @@ -64,6 +65,7 @@ bool CustomCommands::runCmd(std::string full, CNSocket* sock) { static void updatePathMarkers(CNSocket* sock) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; if (TableData::RunningNPCPaths.find(plr->iID) != TableData::RunningNPCPaths.end()) { for (BaseNPC* marker : TableData::RunningNPCPaths[plr->iID].second) marker->enterIntoViewOf(sock); @@ -76,6 +78,7 @@ static void helpCommand(std::string full, std::vector& args, CNSock Chat::sendServerMessage(sock, "Commands available to you:"); Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; for (auto& cmd : commands) { if (cmd.second.requiredAccLevel >= plr->accountLevel) Chat::sendServerMessage(sock, "/" + cmd.first + (cmd.second.help.length() > 0 ? " - " + cmd.second.help : "")); @@ -92,6 +95,7 @@ static void accessCommand(std::string full, std::vector& args, CNSo char *tmp; Player* self = PlayerManager::getPlayer(sock); + if (self == nullptr) return; int selfAccess = self->accountLevel; Player* player; @@ -168,6 +172,7 @@ static void levelCommand(std::string full, std::vector& args, CNSoc Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; char *tmp; int level = std::strtol(args[1].c_str(), &tmp, 10); if (*tmp) @@ -230,6 +235,7 @@ static void mssCommand(std::string full, std::vector& args, CNSocke } Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; route->push_back({ plr->x, plr->y, height }); // add point Chat::sendServerMessage(sock, "[MSS] Added point (" + std::to_string(plr->x) + ", " + std::to_string(plr->y) + ", " + std::to_string(height) + ") to route " + std::to_string(routeNum)); return; @@ -298,6 +304,7 @@ static void summonWCommand(std::string full, std::vector& args, CNS } Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; char *rest; int type = std::strtol(args[1].c_str(), &rest, 10); if (*rest) { @@ -333,6 +340,7 @@ static void summonWCommand(std::string full, std::vector& args, CNS static void unsummonWCommand(std::string full, std::vector& args, CNSocket* sock) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; BaseNPC* npc = NPCManager::getNearestNPC(&plr->viewableChunks, plr->x, plr->y, plr->z); if (npc == nullptr) { @@ -419,6 +427,7 @@ static void toggleAiCommand(std::string full, std::vector& args, CN static void npcRotateCommand(std::string full, std::vector& args, CNSocket* sock) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; BaseNPC* npc = NPCManager::getNearestNPC(&plr->viewableChunks, plr->x, plr->y, plr->z); if (npc == nullptr) { @@ -447,12 +456,14 @@ static void npcRotateCommand(std::string full, std::vector& args, C static void refreshCommand(std::string full, std::vector& args, CNSocket* sock) { Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; PlayerManager::updatePlayerPositionForWarp(sock, plr->x, plr->y, plr->z, plr->instanceID); } static void instanceCommand(std::string full, std::vector& args, CNSocket* sock) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; // no additional arguments: report current instance ID if (args.size() < 2) { Chat::sendServerMessage(sock, "[INST] Current instance ID: " + std::to_string(plr->instanceID)); @@ -489,6 +500,7 @@ static void instanceCommand(std::string full, std::vector& args, CN static void npcInstanceCommand(std::string full, std::vector& args, CNSocket* sock) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; if (args.size() < 2) { Chat::sendServerMessage(sock, "[NPCI] Instance ID must be specified"); Chat::sendServerMessage(sock, "[NPCI] Usage: /npci "); @@ -517,6 +529,7 @@ static void npcInstanceCommand(std::string full, std::vector& args, static void minfoCommand(std::string full, std::vector& args, CNSocket* sock) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; Chat::sendServerMessage(sock, "[MINFO] Current mission ID: " + std::to_string(plr->CurrentMissionID)); for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) { @@ -545,6 +558,7 @@ static void tasksCommand(std::string full, std::vector& args, CNSoc Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) { if (plr->tasks[i] != 0) { TaskData& task = *Missions::Tasks[plr->tasks[i]]; @@ -597,6 +611,7 @@ static void eggCommand(std::string full, std::vector& args, CNSocke Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; // some math to place egg nicely in front of the player // temporarly disabled for sake of gruntwork int addX = 0; //-500.0f * sin(plr->angle / 180.0f * M_PI); @@ -613,6 +628,7 @@ static void eggCommand(std::string full, std::vector& args, CNSocke static void notifyCommand(std::string full, std::vector& args, CNSocket* sock) { Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; if (plr->notify) { plr->notify = false; Chat::sendServerMessage(sock, "[ADMIN] No longer receiving join notifications"); @@ -635,6 +651,7 @@ static void summonGroupCommand(std::string full, std::vector& args, } Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; char *rest; bool wCommand = (args[0] == "/summonGroupW"); @@ -734,6 +751,7 @@ static void flushCommand(std::string full, std::vector& args, CNSoc static void whoisCommand(std::string full, std::vector& args, CNSocket* sock) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; BaseNPC* npc = NPCManager::getNearestNPC(&plr->viewableChunks, plr->x, plr->y, plr->z); if (npc == nullptr) { @@ -758,6 +776,7 @@ static void whoisCommand(std::string full, std::vector& args, CNSoc static void lairUnlockCommand(std::string full, std::vector& args, CNSocket* sock) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; int taskID = -1; int missionID = -1; int lastDist = INT_MAX; @@ -803,6 +822,7 @@ static void lairUnlockCommand(std::string full, std::vector& args, static void hideCommand(std::string full, std::vector& args, CNSocket* sock) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; if (plr->hidden) { Chat::sendServerMessage(sock, "[HIDE] You're already hidden from the map."); return; @@ -814,6 +834,7 @@ static void hideCommand(std::string full, std::vector& args, CNSock static void unhideCommand(std::string full, std::vector& args, CNSocket* sock) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; if (!plr->hidden) { Chat::sendServerMessage(sock, "[HIDE] You're already visible from the map."); return; @@ -848,6 +869,7 @@ static void redeemCommand(std::string full, std::vector& args, CNSo Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; if (Database::isCodeRedeemed(plr->iID, code)) { Chat::sendServerMessage(sock, "/redeem: You have already redeemed this code item"); return; @@ -895,17 +917,20 @@ static void redeemCommand(std::string full, std::vector& args, CNSo static void unwarpableCommand(std::string full, std::vector& args, CNSocket* sock) { Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; plr->unwarpable = true; } static void warpableCommand(std::string full, std::vector& args, CNSocket* sock) { Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; plr->unwarpable = false; } static void registerallCommand(std::string full, std::vector& args, CNSocket* sock) { Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; plr->iWarpLocationFlag = UINT32_MAX; plr->aSkywayLocationFlag[0] = UINT64_MAX; plr->aSkywayLocationFlag[1] = UINT64_MAX; @@ -923,6 +948,7 @@ static void registerallCommand(std::string full, std::vector& args, static void unregisterallCommand(std::string full, std::vector& args, CNSocket* sock) { Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; plr->iWarpLocationFlag = 0; plr->aSkywayLocationFlag[0] = 0; plr->aSkywayLocationFlag[1] = 0; @@ -940,6 +966,7 @@ static void unregisterallCommand(std::string full, std::vector& arg static void banCommand(std::string full, std::vector& args, CNSocket *sock) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; if (args.size() < 2) { Chat::sendServerMessage(sock, "Usage: /ban PlayerID [reason...]"); return; @@ -980,6 +1007,7 @@ static void banCommand(std::string full, std::vector& args, CNSocke Player *otherPlr = PlayerManager::getPlayer(otherSock); + if (otherPlr == nullptr) return; INITSTRUCT(sP_FE2CL_REP_PC_EXIT_SUCC, pkt); pkt.iID = otherPlr->iID; @@ -997,6 +1025,7 @@ static void banCommand(std::string full, std::vector& args, CNSocke static void unbanCommand(std::string full, std::vector& args, CNSocket *sock) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; if (args.size() < 2) { Chat::sendServerMessage(sock, "Usage: /unban PlayerID"); return; @@ -1020,6 +1049,7 @@ static void unbanCommand(std::string full, std::vector& args, CNSoc static void pathCommand(std::string full, std::vector& args, CNSocket* sock) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; if (args.size() < 2) { Chat::sendServerMessage(sock, "[PATH] Too few arguments"); Chat::sendServerMessage(sock, "[PATH] Usage: /path "); diff --git a/src/Eggs.cpp b/src/Eggs.cpp index fe5cf7372..427b5698f 100644 --- a/src/Eggs.cpp +++ b/src/Eggs.cpp @@ -9,8 +9,6 @@ #include "Entities.hpp" #include "Items.hpp" -#include - using namespace Eggs; std::unordered_map Eggs::EggTypes; @@ -18,6 +16,7 @@ std::unordered_map Eggs::EggTypes; void Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; // eggId might be 0 if the buff is made by the /buff command EntityRef src = eggId == 0 ? sock : EntityRef(eggId); @@ -130,6 +129,7 @@ static void eggPickup(CNSocket* sock, CNPacketData* data) { auto pickup = (sP_CL2FE_REQ_SHINY_PICKUP*)data->buf; Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; EntityRef eggRef = {pickup->iShinyID}; if (!eggRef.isValid()) { @@ -183,7 +183,8 @@ static void eggPickup(CNSocket* sock, CNPacketData* data) { // drop if (type->dropCrateId != 0) { const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward); - assert(resplen < CN_PACKET_BODY_SIZE); + if (resplen >= CN_PACKET_BODY_SIZE) + return; // we know it's only one trailing struct, so we can skip full validation uint8_t respbuf[resplen]; // not a variable length array, don't worry diff --git a/src/Email.cpp b/src/Email.cpp index a7fffadea..17c206101 100644 --- a/src/Email.cpp +++ b/src/Email.cpp @@ -51,6 +51,7 @@ static void emailRead(CNSocket* sock, CNPacketData* data) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; Database::EmailData email = Database::getEmail(plr->iID, pkt->iEmailIndex); sItemBase* attachments = Database::getEmailAttachments(plr->iID, pkt->iEmailIndex); email.ReadFlag = 1; // mark as read @@ -129,6 +130,7 @@ static void emailReceiveItemAll(CNSocket* sock, CNPacketData* data) { // move items to player inventory Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; sItemBase* itemsFrom = Database::getEmailAttachments(plr->iID, pkt->iEmailIndex); for (int i = 0; i < 4; i++) { int slot = Items::findFreeSlot(plr); @@ -169,7 +171,9 @@ static void emailReceiveItemAll(CNSocket* sock, CNPacketData* data) { static void emailDelete(CNSocket* sock, CNPacketData* data) { auto pkt = (sP_CL2FE_REQ_PC_DELETE_EMAIL*)data->buf; - Database::deleteEmails(PlayerManager::getPlayer(sock)->iID, pkt->iEmailIndexArray); + Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; + Database::deleteEmails(plr->iID, pkt->iEmailIndexArray); INITSTRUCT(sP_FE2CL_REP_PC_DELETE_EMAIL_SUCC, resp); for (int i = 0; i < 5; i++) { @@ -184,6 +188,7 @@ static void emailSend(CNSocket* sock, CNPacketData* data) { auto pkt = (sP_CL2FE_REQ_PC_SEND_EMAIL*)data->buf; Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; // sanity checks bool invalid = false; int itemCount = 0; diff --git a/src/Entities.cpp b/src/Entities.cpp index c3532ded1..2989ee0d8 100644 --- a/src/Entities.cpp +++ b/src/Entities.cpp @@ -3,8 +3,6 @@ #include "NPCManager.hpp" #include "PlayerManager.hpp" -#include - static_assert(std::is_standard_layout::value); static_assert(std::is_trivially_copyable::value); @@ -29,7 +27,8 @@ bool EntityRef::isValid() const { } Entity *EntityRef::getEntity() const { - assert(isValid()); + if (!isValid()) + return nullptr; if (kind == EntityKind::PLAYER) return PlayerManager::getPlayer(sock); diff --git a/src/Groups.cpp b/src/Groups.cpp index fc649ea1d..dd8a835f8 100644 --- a/src/Groups.cpp +++ b/src/Groups.cpp @@ -25,6 +25,7 @@ static void attachGroupData(std::vector& pcs, std::vector& sPCGroupMemberInfo* info = (sPCGroupMemberInfo*)pivot; Player* plr = PlayerManager::getPlayer(pcRef.sock); + if (plr == nullptr) continue; info->iPC_ID = plr->iID; info->iPCUID = plr->PCStyle.iPC_UID; info->iNameCheck = plr->PCStyle.iNameCheck; @@ -69,6 +70,7 @@ void Groups::addToGroup(Group* group, EntityRef member) { if (member.kind == EntityKind::PLAYER) { Player* plr = PlayerManager::getPlayer(member.sock); + if (plr == nullptr) return; plr->group = group; } else if (member.kind == EntityKind::COMBAT_NPC) { @@ -91,7 +93,9 @@ void Groups::addToGroup(Group* group, EntityRef member) { memset(respbuf, 0, CN_PACKET_BODY_SIZE); sP_FE2CL_PC_GROUP_JOIN* pkt = (sP_FE2CL_PC_GROUP_JOIN*)respbuf; - pkt->iID_NewMember = PlayerManager::getPlayer(member.sock)->iID; + Player* memberPlr = PlayerManager::getPlayer(member.sock); + if (memberPlr == nullptr) return; + pkt->iID_NewMember = memberPlr->iID; pkt->iMemberPCCnt = (int32_t)pcCount; pkt->iMemberNPCCnt = (int32_t)npcCount; @@ -117,7 +121,8 @@ bool Groups::removeFromGroup(Group* group, EntityRef member) { if (member.kind == EntityKind::PLAYER) { Player* plr = PlayerManager::getPlayer(member.sock); - plr->group = nullptr; // no dangling pointers here muahaahahah + if (plr == nullptr) return false; + plr->group = nullptr; INITSTRUCT(sP_FE2CL_PC_GROUP_LEAVE_SUCC, leavePkt); member.sock->sendPacket(leavePkt, P_FE2CL_PC_GROUP_LEAVE_SUCC); @@ -147,7 +152,9 @@ bool Groups::removeFromGroup(Group* group, EntityRef member) { memset(respbuf, 0, CN_PACKET_BODY_SIZE); sP_FE2CL_PC_GROUP_LEAVE* pkt = (sP_FE2CL_PC_GROUP_LEAVE*)respbuf; - pkt->iID_LeaveMember = PlayerManager::getPlayer(member.sock)->iID; + Player* leavePlr = PlayerManager::getPlayer(member.sock); + if (leavePlr == nullptr) return false; + pkt->iID_LeaveMember = leavePlr->iID; pkt->iMemberPCCnt = (int32_t)pcCount; pkt->iMemberNPCCnt = (int32_t)npcCount; @@ -189,6 +196,7 @@ static void requestGroup(CNSocket* sock, CNPacketData* data) { sP_CL2FE_REQ_PC_GROUP_INVITE* recv = (sP_CL2FE_REQ_PC_GROUP_INVITE*)data->buf; Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; Player* otherPlr = PlayerManager::getPlayerFromID(recv->iID_To); if (otherPlr == nullptr) @@ -223,6 +231,7 @@ static void refuseGroup(CNSocket* sock, CNPacketData* data) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; INITSTRUCT(sP_FE2CL_PC_GROUP_INVITE_REFUSE, resp); resp.iID_To = plr->iID; @@ -233,6 +242,7 @@ static void refuseGroup(CNSocket* sock, CNPacketData* data) { static void joinGroup(CNSocket* sock, CNPacketData* data) { sP_CL2FE_REQ_PC_GROUP_JOIN* recv = (sP_CL2FE_REQ_PC_GROUP_JOIN*)data->buf; Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; Player* otherPlr = PlayerManager::getPlayerFromID(recv->iID_From); if (otherPlr == nullptr) @@ -257,6 +267,7 @@ static void joinGroup(CNSocket* sock, CNPacketData* data) { static void leaveGroup(CNSocket* sock, CNPacketData* data) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; groupKick(plr->group, sock); } @@ -282,6 +293,7 @@ void Groups::sendToGroup(Group* group, EntityRef excluded, void* buf, uint32_t t void Groups::groupTickInfo(CNSocket* sock) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; Group* group = plr->group; std::vector pcs = group->filter(EntityKind::PLAYER); std::vector npcs = group->filter(EntityKind::COMBAT_NPC); diff --git a/src/Groups.hpp b/src/Groups.hpp index 4fe40f53e..d3bc589f8 100644 --- a/src/Groups.hpp +++ b/src/Groups.hpp @@ -3,7 +3,6 @@ #include "EntityRef.hpp" #include -#include struct Group { std::vector members; @@ -16,7 +15,8 @@ struct Group { return filtered; } EntityRef getLeader() { - assert(members.size() > 0); + if (members.empty()) + return EntityRef((int32_t)0); return members[0]; } diff --git a/src/Items.cpp b/src/Items.cpp index b11c08628..08699a433 100644 --- a/src/Items.cpp +++ b/src/Items.cpp @@ -12,7 +12,6 @@ #include "Buffs.hpp" #include // for memset() -#include #include using namespace Items; @@ -38,6 +37,7 @@ std::map Items::NanoCapsules; // crate id -> nano id static void nanoCapsuleHandler(CNSocket* sock, int slot, sItemBase *chest) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; int32_t nanoId = NanoCapsules[chest->iID]; // chest opening acknowledgement packet @@ -46,7 +46,8 @@ static void nanoCapsuleHandler(CNSocket* sock, int slot, sItemBase *chest) { // in order to remove capsule form inventory, we have to send item reward packet with empty item const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward); - assert(resplen < CN_PACKET_BODY_SIZE); + if (resplen >= CN_PACKET_BODY_SIZE) + return; // we know it's only one trailing struct, so we can skip full validation uint8_t respbuf[resplen]; // not a variable length array, don't worry @@ -258,6 +259,7 @@ static void itemMoveHandler(CNSocket* sock, CNPacketData* data) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; // sanity check if (itemmove->iToSlotNum < 0 || itemmove->iFromSlotNum < 0) return; @@ -416,6 +418,7 @@ static void itemDeleteHandler(CNSocket* sock, CNPacketData* data) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; if (itemdel->iSlotNum < 0 || itemdel->iSlotNum >= AINVEN_COUNT) return; // sanity check @@ -518,6 +521,7 @@ static void itemUseHandler(CNSocket* sock, CNPacketData* data) { static void itemBankOpenHandler(CNSocket* sock, CNPacketData* data) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; // just send bank inventory INITSTRUCT(sP_FE2CL_REP_PC_BANK_OPEN_SUCC, resp); for (int i = 0; i < ABANK_COUNT; i++) { @@ -536,6 +540,7 @@ static void chestOpenHandler(CNSocket *sock, CNPacketData *data) { Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; sItemBase *chest = &plr->Inven[pkt->iSlotNum]; // we could reject the packet if the client thinks the item is different, but eh @@ -556,7 +561,8 @@ static void chestOpenHandler(CNSocket *sock, CNPacketData *data) { // item giving packet const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward); - assert(resplen < CN_PACKET_BODY_SIZE); + if (resplen >= CN_PACKET_BODY_SIZE) + return; // we know it's only one trailing struct, so we can skip full validation uint8_t respbuf[resplen]; // not a variable length array, don't worry @@ -645,7 +651,8 @@ void Items::checkItemExpire(CNSocket* sock, Player* player) { */ const size_t resplen = sizeof(sP_FE2CL_PC_DELETE_TIME_LIMIT_ITEM) + sizeof(sTimeLimitItemDeleteInfo2CL); - assert(resplen < CN_PACKET_BODY_SIZE); + if (resplen >= CN_PACKET_BODY_SIZE) + return; // we know it's only one trailing struct, so we can skip full validation uint8_t respbuf[resplen]; // not a variable length array, don't worry auto packet = (sP_FE2CL_PC_DELETE_TIME_LIMIT_ITEM*)respbuf; @@ -714,8 +721,10 @@ static void getMobDrop(sItemBase* reward, const std::vector& weights, const static void giveSingleDrop(CNSocket *sock, Mob* mob, int mobDropId, const DropRoll& rolled) { Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward); - assert(resplen < CN_PACKET_BODY_SIZE); + if (resplen >= CN_PACKET_BODY_SIZE) + return; // we know it's only one trailing struct, so we can skip full validation uint8_t respbuf[resplen]; // not a variable length array, don't worry diff --git a/src/Missions.cpp b/src/Missions.cpp index 67c1161d4..d7c5af1ba 100644 --- a/src/Missions.cpp +++ b/src/Missions.cpp @@ -51,6 +51,7 @@ int Missions::findQSlot(Player *plr, int id) { static bool isQuestItemFull(CNSocket* sock, int itemId, int itemCount) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return true; int slot = Missions::findQSlot(plr, itemId); if (slot == -1) { // this should never happen @@ -64,11 +65,13 @@ static bool isQuestItemFull(CNSocket* sock, int itemId, int itemCount) { static void dropQuestItem(CNSocket *sock, int task, int count, int id, int mobid) { std::cout << "Altered item id " << id << " by " << count << " for task id " << task << std::endl; const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward); - assert(resplen < CN_PACKET_BODY_SIZE); + if (resplen >= CN_PACKET_BODY_SIZE) + return; // we know it's only one trailing struct, so we can skip full validation Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; uint8_t respbuf[resplen]; // not a variable length array, don't worry sP_FE2CL_REP_REWARD_ITEM *reward = (sP_FE2CL_REP_REWARD_ITEM *)respbuf; sItemReward *item = (sItemReward *)(respbuf + sizeof(sP_FE2CL_REP_REWARD_ITEM)); @@ -120,6 +123,7 @@ static int giveMissionReward(CNSocket *sock, int task, int choice=0) { Reward *reward = Rewards[task]; Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return -1; int nrewards = 0; for (int i = 0; i < 4; i++) { if (reward->itemIds[i] != 0) @@ -212,6 +216,7 @@ static int giveMissionReward(CNSocket *sock, int task, int choice=0) { static bool endTask(CNSocket *sock, int32_t taskNum, int choice=0) { Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return false; if (Tasks.find(taskNum) == Tasks.end()) return false; @@ -346,6 +351,7 @@ static void taskStart(CNSocket* sock, CNPacketData* data) { INITSTRUCT(sP_FE2CL_REP_PC_TASK_START_SUCC, response); Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; if (!startTask(plr, missionData->iTaskNum)) { INITSTRUCT(sP_FE2CL_REP_PC_TASK_START_FAIL, failresp); failresp.iTaskNum = missionData->iTaskNum; @@ -395,6 +401,7 @@ static void taskEnd(CNSocket* sock, CNPacketData* data) { if (task->task["m_iSTGrantTimer"] > 0 && missionData->iNPC_ID == 0) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; /* * Enemy killing missions * this is gross and should be cleaned up later @@ -442,6 +449,7 @@ static void taskEnd(CNSocket* sock, CNPacketData* data) { static void setMission(CNSocket* sock, CNPacketData* data) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; sP_CL2FE_REQ_PC_SET_CURRENT_MISSION_ID* missionData = (sP_CL2FE_REQ_PC_SET_CURRENT_MISSION_ID*)data->buf; INITSTRUCT(sP_FE2CL_REP_PC_SET_CURRENT_MISSION_ID, response); response.iCurrentMissionID = missionData->iCurrentMissionID; @@ -458,6 +466,7 @@ static void quitMission(CNSocket* sock, CNPacketData* data) { void Missions::quitTask(CNSocket* sock, int32_t taskNum, bool manual) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; if (Tasks.find(taskNum) == Tasks.end()) return; // sanity check @@ -514,6 +523,7 @@ void Missions::quitTask(CNSocket* sock, int32_t taskNum, bool manual) { void Missions::updateFusionMatter(CNSocket* sock, int fusion) { Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; plr->fusionmatter += fusion; // there's a much lower FM cap in the Future @@ -572,6 +582,7 @@ void Missions::updateFusionMatter(CNSocket* sock, int fusion) { void Missions::mobKilled(CNSocket *sock, int mobid, std::map& rolls) { Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; bool missionmob = false; for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) { @@ -637,6 +648,7 @@ void Missions::mobKilled(CNSocket *sock, int mobid, std::map& rolls) { void Missions::failInstancedMissions(CNSocket* sock) { // loop through all tasks; if the required instance is being left, "fail" the task Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; for (int i = 0; i < 6; i++) { int taskNum = plr->tasks[i]; if (Missions::Tasks.find(taskNum) == Missions::Tasks.end()) diff --git a/src/MobAI.cpp b/src/MobAI.cpp index 50c87422e..6c968f63d 100644 --- a/src/MobAI.cpp +++ b/src/MobAI.cpp @@ -43,7 +43,8 @@ int Mob::takeDamage(EntityRef src, int amt) { return 0; // don't hurt a mob casting corruption if (state == AIState::ROAMING) { - assert(target == nullptr && src.kind == EntityKind::PLAYER); // TODO: players only for now + if (target != nullptr || src.kind != EntityKind::PLAYER) + return 0; transition(AIState::COMBAT, src); if (groupLeader != 0) @@ -181,6 +182,7 @@ bool MobAI::aggroCheck(Mob *mob, time_t currTime) { CNSocket *s = ref.sock; Player *plr = PlayerManager::getPlayer(s); + if (plr == nullptr) continue; if (plr->HP <= 0 || plr->onMonkey) continue; @@ -229,6 +231,7 @@ bool MobAI::aggroCheck(Mob *mob, time_t currTime) { static void dealCorruption(Mob *mob, std::vector targetData, int skillID, int mobStyle) { Player *plr = PlayerManager::getPlayer(mob->target); + if (plr == nullptr) return; size_t resplen = sizeof(sP_FE2CL_NPC_SKILL_CORRUPTION_HIT) + targetData[0] * sizeof(sCAttackResult); @@ -339,6 +342,7 @@ static void dealCorruption(Mob *mob, std::vector targetData, int skillID, i static void useAbilities(Mob *mob, time_t currTime) { Player *plr = PlayerManager::getPlayer(mob->target); + if (plr == nullptr) return; if (mob->skillStyle >= 0) { // corruption hit int skillID = (int)mob->data["m_iCorruptionType"]; @@ -365,6 +369,7 @@ static void useAbilities(Mob *mob, time_t currTime) { CNSocket *s = ref.sock; Player *plr = PlayerManager::getPlayer(s); + if (plr == nullptr) continue; if (!plr->isAlive()) continue; @@ -515,7 +520,10 @@ void MobAI::deadStep(CombatNPC* npc, time_t currTime) { void MobAI::combatStep(CombatNPC* npc, time_t currTime) { Mob* self = (Mob*)npc; - assert(self->target != nullptr); + if (self->target == nullptr) { + self->transition(AIState::RETREAT, self->id); + return; + } // lose aggro if the player lost connection if (PlayerManager::players.find(self->target) == PlayerManager::players.end()) { @@ -525,6 +533,10 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) { } Player *plr = PlayerManager::getPlayer(self->target); + if (plr == nullptr) { + self->transition(AIState::RETREAT, self->target); + return; + } // lose aggro if the player became invulnerable or died if (plr->HP <= 0 @@ -770,7 +782,8 @@ void MobAI::onRoamStart(CombatNPC* npc, EntityRef src) { void MobAI::onCombatStart(CombatNPC* npc, EntityRef src) { Mob* self = (Mob*)npc; - assert(src.kind == EntityKind::PLAYER); + if (src.kind != EntityKind::PLAYER) + return; self->target = src.sock; self->nextMovement = getTime(); self->nextAttack = 0; @@ -804,6 +817,7 @@ void MobAI::onDeath(CombatNPC* npc, EntityRef src) { // check for the edge case where hitting the mob did not aggro it if (src.kind == EntityKind::PLAYER && src.isValid()) { Player* plr = PlayerManager::getPlayer(src.sock); + if (plr == nullptr) return; Items::DropRoll rolled; Items::DropRoll eventRolled; @@ -818,12 +832,16 @@ void MobAI::onDeath(CombatNPC* npc, EntityRef src) { } else { auto players = plr->group->filter(EntityKind::PLAYER); - for (EntityRef pRef : players) playerRefs.push_back(PlayerManager::getPlayer(pRef.sock)); + for (EntityRef pRef : players) { + Player* pRefPlr = PlayerManager::getPlayer(pRef.sock); + if (pRefPlr != nullptr) playerRefs.push_back(pRefPlr); + } Combat::genQItemRolls(playerRefs, qitemRolls); for (int i = 0; i < players.size(); i++) { CNSocket* sockTo = players[i].sock; Player* otherPlr = PlayerManager::getPlayer(sockTo); + if (otherPlr == nullptr) continue; // only contribute to group members' kills if they're close enough int dist = std::hypot(plr->x - otherPlr->x + 1, plr->y - otherPlr->y + 1); if (dist > 5000) diff --git a/src/NPCManager.cpp b/src/NPCManager.cpp index bd72fb034..e426d9d32 100644 --- a/src/NPCManager.cpp +++ b/src/NPCManager.cpp @@ -106,6 +106,7 @@ static void npcBarkHandler(CNSocket* sock, CNPacketData* data) { auto& barks = td->task["m_iHBarkerTextID"]; Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; std::vector> npcLines; for (Chunk* chunk : plr->viewableChunks) { @@ -143,6 +144,7 @@ static void npcBarkHandler(CNSocket* sock, CNPacketData* data) { static void npcUnsummonHandler(CNSocket* sock, CNPacketData* data) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; if (plr->accountLevel > 30) return; @@ -175,6 +177,7 @@ static void npcSummonHandler(CNSocket* sock, CNPacketData* data) { auto req = (sP_CL2FE_REQ_NPC_SUMMON*)data->buf; Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; int limit = NPCData.back()["m_iNpcNumber"]; // permission & sanity check @@ -189,6 +192,7 @@ static void npcSummonHandler(CNSocket* sock, CNPacketData* data) { static void handleWarp(CNSocket* sock, int32_t warpId) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; // sanity check if (Warps.find(warpId) == Warps.end()) return; @@ -213,7 +217,10 @@ static void handleWarp(CNSocket* sock, int32_t warpId) { uint64_t instanceID = Warps[warpId].instanceID; Player* leader = plr; - if (plr->group != nullptr) leader = PlayerManager::getPlayer(plr->group->filter(EntityKind::PLAYER)[0].sock); + if (plr->group != nullptr) { + Player* groupLeader = PlayerManager::getPlayer(plr->group->filter(EntityKind::PLAYER)[0].sock); + if (groupLeader != nullptr) leader = groupLeader; + } // if warp requires you to be on a mission, it's gotta be a unique instance if (Warps[warpId].limitTaskID != 0 || instanceID == 14) { // 14 is a special case for the Time Lab diff --git a/src/Nanos.cpp b/src/Nanos.cpp index 73082be8e..c142efd57 100644 --- a/src/Nanos.cpp +++ b/src/Nanos.cpp @@ -21,6 +21,7 @@ void Nanos::addNano(CNSocket* sock, int16_t nanoID, int16_t slot, bool spendfm) Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; int level = plr->level; #ifndef ACADEMY @@ -74,6 +75,7 @@ void Nanos::summonNano(CNSocket *sock, int slot, bool silent) { resp.iActiveNanoSlotNum = slot; Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; if (slot > 2 || slot < -1) return; // sanity check @@ -115,6 +117,7 @@ static void setNanoSkill(CNSocket* sock, sP_CL2FE_REQ_NANO_TUNE* skill) { Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; if (plr->activeNano > 0 && plr->activeNano == skill->iNanoID) summonNano(sock, -1); // just unsummon the nano to prevent infinite buffs @@ -203,6 +206,7 @@ static void nanoEquipHandler(CNSocket* sock, CNPacketData* data) { INITSTRUCT(sP_FE2CL_REP_NANO_EQUIP_SUCC, resp); Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; // sanity checks if (nano->iNanoSlotNum > 2 || nano->iNanoSlotNum < 0) return; @@ -228,6 +232,7 @@ static void nanoUnEquipHandler(CNSocket* sock, CNPacketData* data) { INITSTRUCT(sP_FE2CL_REP_NANO_UNEQUIP_SUCC, resp); Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; // sanity check if (nano->iNanoSlotNum > 2 || nano->iNanoSlotNum < 0) return; @@ -248,6 +253,7 @@ static void nanoSummonHandler(CNSocket* sock, CNPacketData* data) { auto pkt = (sP_CL2FE_REQ_NANO_ACTIVE*)data->buf; Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; summonNano(sock, pkt->iNanoSlotNum); DEBUGLOG( @@ -258,6 +264,7 @@ static void nanoSummonHandler(CNSocket* sock, CNPacketData* data) { static void nanoSkillUseHandler(CNSocket* sock, CNPacketData* data) { Player *plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; // validate request check sP_CL2FE_REQ_NANO_SKILL_USE* pkt = (sP_CL2FE_REQ_NANO_SKILL_USE*)data->buf; if (!validInVarPacket(sizeof(sP_CL2FE_REQ_NANO_SKILL_USE), pkt->iTargetCnt, sizeof(int32_t), data->size)) { @@ -299,6 +306,7 @@ static void nanoRecallRegisterHandler(CNSocket* sock, CNPacketData* data) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; BaseNPC *npc = NPCManager::NPCs[recallData->iNPCID]; INITSTRUCT(sP_FE2CL_REP_REGIST_RXCOM, response); @@ -313,6 +321,7 @@ static void nanoRecallHandler(CNSocket* sock, CNPacketData* data) { auto recallData = (sP_CL2FE_REQ_WARP_USE_RECALL*)data->buf; Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; Player* otherPlr = PlayerManager::getPlayerFromID(recallData->iGroupMemberID); if (otherPlr == nullptr) return; @@ -336,6 +345,7 @@ static void nanoRecallHandler(CNSocket* sock, CNPacketData* data) { static void nanoPotionHandler(CNSocket* sock, CNPacketData* data) { Player* player = PlayerManager::getPlayer(sock); + if (player == nullptr) return; // sanity checks if (player->activeNano == -1 || player->batteryN == 0) return; diff --git a/src/PlayerManager.cpp b/src/PlayerManager.cpp index 7f06ed4d0..95f3de042 100644 --- a/src/PlayerManager.cpp +++ b/src/PlayerManager.cpp @@ -14,7 +14,6 @@ #include "Buddies.hpp" #include "BuiltinCommands.hpp" -#include #include #include #include @@ -71,6 +70,7 @@ void PlayerManager::removePlayer(CNSocket* key) { void PlayerManager::updatePlayerPosition(CNSocket* sock, int X, int Y, int Z, uint64_t I, int angle) { Player* plr = getPlayer(sock); + if (plr == nullptr) return; plr->angle = angle; ChunkPos oldChunk = plr->chunkPos; ChunkPos newChunk = Chunking::chunkPosAt(X, Y, I); @@ -93,6 +93,7 @@ void PlayerManager::updatePlayerPosition(CNSocket* sock, int X, int Y, int Z, ui */ void PlayerManager::updatePlayerPositionForWarp(CNSocket* sock, int X, int Y, int Z, uint64_t inst) { Player *plr = getPlayer(sock); + if (plr == nullptr) return; // force player to reload chunks Chunking::updateEntityChunk({sock}, plr->chunkPos, Chunking::INVALID_CHUNK); @@ -101,6 +102,7 @@ void PlayerManager::updatePlayerPositionForWarp(CNSocket* sock, int X, int Y, in void PlayerManager::sendPlayerTo(CNSocket* sock, int X, int Y, int Z, uint64_t I) { Player* plr = getPlayer(sock); + if (plr == nullptr) return; plr->onMonkey = false; if (plr->instanceID == INSTANCE_OVERWORLD) { @@ -144,7 +146,9 @@ void PlayerManager::sendPlayerTo(CNSocket* sock, int X, int Y, int Z, uint64_t I } void PlayerManager::sendPlayerTo(CNSocket* sock, int X, int Y, int Z) { - sendPlayerTo(sock, X, Y, Z, getPlayer(sock)->instanceID); + Player* plr = getPlayer(sock); + if (plr == nullptr) return; + sendPlayerTo(sock, X, Y, Z, plr->instanceID); } /* @@ -325,7 +329,7 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) { void PlayerManager::sendToGroup(CNSocket* sock, void* buf, uint32_t type, size_t size) { Player* plr = getPlayer(sock); - if (plr->group == nullptr) + if (plr == nullptr || plr->group == nullptr) return; for(const EntityRef& ref : plr->group->filter(EntityKind::PLAYER)) ref.sock->sendPacket(buf, type, size); @@ -333,6 +337,7 @@ void PlayerManager::sendToGroup(CNSocket* sock, void* buf, uint32_t type, size_t void PlayerManager::sendToViewable(CNSocket* sock, void* buf, uint32_t type, size_t size) { Player* plr = getPlayer(sock); + if (plr == nullptr) return; for (auto it = plr->viewableChunks.begin(); it != plr->viewableChunks.end(); it++) { Chunk* chunk = *it; for (const EntityRef& ref : chunk->entities) { @@ -348,6 +353,7 @@ static void loadPlayer(CNSocket* sock, CNPacketData* data) { sP_CL2FE_REQ_PC_LOADING_COMPLETE* complete = (sP_CL2FE_REQ_PC_LOADING_COMPLETE*)data->buf; INITSTRUCT(sP_FE2CL_REP_PC_LOADING_COMPLETE_SUCC, response); Player *plr = getPlayer(sock); + if (plr == nullptr) return; DEBUGLOG( std::cout << "P_CL2FE_REQ_PC_LOADING_COMPLETE:" << std::endl; @@ -391,7 +397,9 @@ static void loadPlayer(CNSocket* sock, CNPacketData* data) { } static void heartbeatPlayer(CNSocket* sock, CNPacketData* data) { - getPlayer(sock)->lastHeartbeat = getTime(); + Player* plr = getPlayer(sock); + if (plr == nullptr) return; + plr->lastHeartbeat = getTime(); } static void exitGame(CNSocket* sock, CNPacketData* data) { @@ -417,6 +425,7 @@ static void exitGame(CNSocket* sock, CNPacketData* data) { static void revivePlayer(CNSocket* sock, CNPacketData* data) { Player *plr = getPlayer(sock); + if (plr == nullptr) return; WarpLocation* target = getRespawnPoint(plr); auto reviveData = (sP_CL2FE_REQ_PC_REGEN*)data->buf; @@ -512,6 +521,7 @@ static void revivePlayer(CNSocket* sock, CNPacketData* data) { static void enterPlayerVehicle(CNSocket* sock, CNPacketData* data) { Player* plr = getPlayer(sock); + if (plr == nullptr) return; // vehicles are only allowed in the overworld if (plr->instanceID != 0) @@ -545,6 +555,7 @@ static void enterPlayerVehicle(CNSocket* sock, CNPacketData* data) { static void exitPlayerVehicle(CNSocket* sock, CNPacketData* data) { Player* plr = getPlayer(sock); + if (plr == nullptr) return; if (plr->iPCState & 8) { INITSTRUCT(sP_FE2CL_PC_VEHICLE_OFF_SUCC, response); @@ -568,6 +579,7 @@ static void changePlayerGuide(CNSocket *sock, CNPacketData *data) { auto pkt = (sP_CL2FE_REQ_PC_CHANGE_MENTOR*)data->buf; INITSTRUCT(sP_FE2CL_REP_PC_CHANGE_MENTOR_SUCC, resp); Player *plr = getPlayer(sock); + if (plr == nullptr) return; resp.iMentor = pkt->iMentor; resp.iMentorCnt = 1; @@ -594,6 +606,7 @@ static void changePlayerGuide(CNSocket *sock, CNPacketData *data) { static void setFirstUseFlag(CNSocket* sock, CNPacketData* data) { auto flag = (sP_CL2FE_REQ_PC_FIRST_USE_FLAG_SET*)data->buf; Player* plr = getPlayer(sock); + if (plr == nullptr) return; if (flag->iFlagCode < 1 || flag->iFlagCode > 128) { std::cout << "[WARN] Client submitted invalid first use flag number?!" << std::endl; @@ -611,8 +624,7 @@ Player *PlayerManager::getPlayer(CNSocket* key) { if (players.find(key) != players.end()) return players[key]; - // this should never happen - assert(false); + return nullptr; } std::string PlayerManager::getPlayerName(Player *plr, bool id) { @@ -693,15 +705,16 @@ CNSocket *PlayerManager::getSockFromName(std::string firstname, std::string last CNSocket *PlayerManager::getSockFromAny(int by, int id, int uid, std::string firstname, std::string lastname) { switch ((eCN_GM_TargetSearchBy)by) { case eCN_GM_TargetSearchBy::PC_ID: - assert(id != 0); + if (id == 0) return nullptr; return getSockFromID(id); case eCN_GM_TargetSearchBy::PC_UID: // account id; not player id - assert(uid != 0); + if (uid == 0) return nullptr; for (auto& pair : players) if (pair.second->accountId == uid) return pair.first; + return nullptr; case eCN_GM_TargetSearchBy::PC_Name: - assert(firstname != "" && lastname != ""); // XXX: remove this if we start messing around with edited names? + if (firstname.empty() || lastname.empty()) return nullptr; return getSockFromName(firstname, lastname); } diff --git a/src/PlayerManager.hpp b/src/PlayerManager.hpp index ad9c76712..5c08f17e4 100644 --- a/src/PlayerManager.hpp +++ b/src/PlayerManager.hpp @@ -40,6 +40,7 @@ namespace PlayerManager { template void sendToViewable(CNSocket *sock, T& pkt, uint32_t type) { Player* plr = getPlayer(sock); + if (plr == nullptr) return; for (auto it = plr->viewableChunks.begin(); it != plr->viewableChunks.end(); it++) { Chunk* chunk = *it; for (const EntityRef& ref : chunk->entities) { diff --git a/src/PlayerMovement.cpp b/src/PlayerMovement.cpp index c4d5150ad..10b1f5f48 100644 --- a/src/PlayerMovement.cpp +++ b/src/PlayerMovement.cpp @@ -9,6 +9,7 @@ static void movePlayer(CNSocket* sock, CNPacketData* data) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; auto* moveData = (sP_CL2FE_REQ_PC_MOVE*)data->buf; PlayerManager::updatePlayerPosition(sock, moveData->iX, moveData->iY, moveData->iZ, plr->instanceID, moveData->iAngle); @@ -55,6 +56,7 @@ static void movePlayer(CNSocket* sock, CNPacketData* data) { static void stopPlayer(CNSocket* sock, CNPacketData* data) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; auto stopData = (sP_CL2FE_REQ_PC_STOP*)data->buf; PlayerManager::updatePlayerPosition(sock, stopData->iX, stopData->iY, stopData->iZ, plr->instanceID, plr->angle); @@ -77,6 +79,7 @@ static void stopPlayer(CNSocket* sock, CNPacketData* data) { static void jumpPlayer(CNSocket* sock, CNPacketData* data) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; auto jumpData = (sP_CL2FE_REQ_PC_JUMP*)data->buf; PlayerManager::updatePlayerPosition(sock, jumpData->iX, jumpData->iY, jumpData->iZ, plr->instanceID, jumpData->iAngle); @@ -105,6 +108,7 @@ static void jumpPlayer(CNSocket* sock, CNPacketData* data) { static void jumppadPlayer(CNSocket* sock, CNPacketData* data) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; auto jumppadData = (sP_CL2FE_REQ_PC_JUMPPAD*)data->buf; PlayerManager::updatePlayerPosition(sock, jumppadData->iX, jumppadData->iY, jumppadData->iZ, plr->instanceID, jumppadData->iAngle); @@ -131,6 +135,7 @@ static void jumppadPlayer(CNSocket* sock, CNPacketData* data) { static void launchPlayer(CNSocket* sock, CNPacketData* data) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; auto launchData = (sP_CL2FE_REQ_PC_LAUNCHER*)data->buf; PlayerManager::updatePlayerPosition(sock, launchData->iX, launchData->iY, launchData->iZ, plr->instanceID, launchData->iAngle); @@ -158,6 +163,7 @@ static void launchPlayer(CNSocket* sock, CNPacketData* data) { static void ziplinePlayer(CNSocket* sock, CNPacketData* data) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; sP_CL2FE_REQ_PC_ZIPLINE* ziplineData = (sP_CL2FE_REQ_PC_ZIPLINE*)data->buf; PlayerManager::updatePlayerPosition(sock, ziplineData->iX, ziplineData->iY, ziplineData->iZ, plr->instanceID, ziplineData->iAngle); @@ -192,6 +198,7 @@ static void ziplinePlayer(CNSocket* sock, CNPacketData* data) { static void movePlatformPlayer(CNSocket* sock, CNPacketData* data) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; auto platformData = (sP_CL2FE_REQ_PC_MOVEPLATFORM*)data->buf; PlayerManager::updatePlayerPosition(sock, platformData->iX, platformData->iY, platformData->iZ, plr->instanceID, platformData->iAngle); @@ -223,6 +230,7 @@ static void movePlatformPlayer(CNSocket* sock, CNPacketData* data) { static void moveSliderPlayer(CNSocket* sock, CNPacketData* data) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; auto sliderData = (sP_CL2FE_REQ_PC_MOVETRANSPORTATION*)data->buf; PlayerManager::updatePlayerPosition(sock, sliderData->iX, sliderData->iY, sliderData->iZ, plr->instanceID, sliderData->iAngle); @@ -253,6 +261,7 @@ static void moveSliderPlayer(CNSocket* sock, CNPacketData* data) { static void moveSlopePlayer(CNSocket* sock, CNPacketData* data) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; sP_CL2FE_REQ_PC_SLOPE* slopeData = (sP_CL2FE_REQ_PC_SLOPE*)data->buf; PlayerManager::updatePlayerPosition(sock, slopeData->iX, slopeData->iY, slopeData->iZ, plr->instanceID, slopeData->iAngle); diff --git a/src/Racing.cpp b/src/Racing.cpp index 844038bc3..3573f5b72 100644 --- a/src/Racing.cpp +++ b/src/Racing.cpp @@ -61,6 +61,7 @@ static void racingCancel(CNSocket* sock, CNPacketData* data) { return; // race not found Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; EPRaces.erase(sock); INITSTRUCT(sP_FE2CL_REP_EP_RACE_CANCEL_SUCC, resp); @@ -92,6 +93,7 @@ static void racingEnd(CNSocket* sock, CNPacketData* data) { auto req = (sP_CL2FE_REQ_EP_RACE_END*)data->buf; Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; if (NPCManager::NPCs.find(req->iEndEcomID) == NPCManager::NPCs.end()) return; // finish line agent not found diff --git a/src/Trading.cpp b/src/Trading.cpp index ab8ce7bbd..9ee18427f 100644 --- a/src/Trading.cpp +++ b/src/Trading.cpp @@ -5,6 +5,8 @@ #include "PlayerManager.hpp" #include "db/Database.hpp" +#include + using namespace Trading; static bool doTrade(Player* plr, Player* plr2) { @@ -111,6 +113,7 @@ static void tradeOffer(CNSocket* sock, CNPacketData* data) { Player* plr = PlayerManager::getPlayer(otherSock); + if (plr == nullptr) return; if (plr->isTrading) { INITSTRUCT(sP_FE2CL_REP_PC_TRADE_OFFER_REFUSAL, resp); resp.iID_Request = pacdat->iID_To; @@ -136,8 +139,10 @@ static void tradeOfferAccept(CNSocket* sock, CNPacketData* data) { return; Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; Player* plr2 = PlayerManager::getPlayer(otherSock); + if (plr2 == nullptr) return; if (plr2->isTrading) { INITSTRUCT(sP_FE2CL_REP_PC_TRADE_OFFER_REFUSAL, resp); resp.iID_Request = pacdat->iID_From; @@ -229,8 +234,10 @@ static void tradeConfirm(CNSocket* sock, CNPacketData* data) { return; Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; Player* plr2 = PlayerManager::getPlayer(otherSock); + if (plr2 == nullptr) return; if (!(plr->isTrading && plr2->isTrading)) { // both players must be trading INITSTRUCT(sP_FE2CL_REP_PC_TRADE_CONFIRM_ABORT, resp); resp.iID_Request = plr2->iID; @@ -320,8 +327,10 @@ static void tradeConfirmCancel(CNSocket* sock, CNPacketData* data) { return; Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; Player* plr2 = PlayerManager::getPlayer(otherSock); + if (plr2 == nullptr) return; // both players are not trading nor are in a confirmed state plr->isTrading = false; plr->isTradeConfirm = false; @@ -351,14 +360,16 @@ static void tradeRegisterItem(CNSocket* sock, CNPacketData* data) { return; Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; Player* plr2 = PlayerManager::getPlayer(otherSock); + if (plr2 == nullptr) return; plr->Trade[pacdat->Item.iSlotNum] = pacdat->Item; plr->isTradeConfirm = false; plr2->isTradeConfirm = false; // since you can spread items like gumballs over multiple slots, we need to count them all // to make sure the inventory shows the right value during trade. - int count = 0; + int count = 0; for (int i = 0; i < 5; i++) { if (plr->Trade[i].iInvenNum == pacdat->Item.iInvenNum) count += plr->Trade[i].iOpt; @@ -395,7 +406,9 @@ static void tradeUnregisterItem(CNSocket* sock, CNPacketData* data) { return; Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; Player* plr2 = PlayerManager::getPlayer(otherSock); + if (plr2 == nullptr) return; plr->isTradeConfirm = false; plr2->isTradeConfirm = false; @@ -427,6 +440,7 @@ static void tradeRegisterCash(CNSocket* sock, CNPacketData* data) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; if (pacdat->iCandy < 0 || pacdat->iCandy > plr->money) return; // famous glitch, begone @@ -440,6 +454,7 @@ static void tradeRegisterCash(CNSocket* sock, CNPacketData* data) { return; Player* plr2 = PlayerManager::getPlayer(otherSock); + if (plr2 == nullptr) return; plr->isTradeConfirm = false; plr2->isTradeConfirm = false; diff --git a/src/Transport.cpp b/src/Transport.cpp index db249c813..4f0c36f97 100644 --- a/src/Transport.cpp +++ b/src/Transport.cpp @@ -25,6 +25,7 @@ static void transportRegisterLocationHandler(CNSocket* sock, CNPacketData* data) auto transport = (sP_CL2FE_REQ_REGIST_TRANSPORTATION_LOCATION*)data->buf; Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; bool newReg = false; // this is a new registration //std::cout << "request to register transport, eTT " << transport->eTT << ", locID " << transport->iLocationID << ", npc " << transport->iNPC_ID << std::endl; if (transport->eTT == 1) { // S.C.A.M.P.E.R. @@ -98,6 +99,7 @@ static void transportWarpHandler(CNSocket* sock, CNPacketData* data) { auto req = (sP_CL2FE_REQ_PC_WARP_USE_TRANSPORTATION*)data->buf; Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; /* * req: * eIL -- inventory type diff --git a/src/Vendors.cpp b/src/Vendors.cpp index 5e2a7a338..e0bede570 100644 --- a/src/Vendors.cpp +++ b/src/Vendors.cpp @@ -6,6 +6,8 @@ #include "Items.hpp" #include "Rand.hpp" +#include + // 7 days #define VEHICLE_EXPIRY_DURATION 604800 @@ -17,6 +19,7 @@ static void vendorBuy(CNSocket* sock, CNPacketData* data) { auto req = (sP_CL2FE_REQ_PC_VENDOR_ITEM_BUY*)data->buf; Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; // prepare fail packet INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL, failResp); failResp.iErrorCode = 0; @@ -86,6 +89,7 @@ static void vendorSell(CNSocket* sock, CNPacketData* data) { auto req = (sP_CL2FE_REQ_PC_VENDOR_ITEM_SELL*)data->buf; Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; // prepare a fail packet INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_SELL_FAIL, failResp); failResp.iErrorCode = 0; @@ -149,6 +153,7 @@ static void vendorBuyback(CNSocket* sock, CNPacketData* data) { auto req = (sP_CL2FE_REQ_PC_VENDOR_ITEM_RESTORE_BUY*)data->buf; Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; // prepare fail packet INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_FAIL, failResp); failResp.iErrorCode = 0; @@ -272,6 +277,7 @@ static void vendorBuyBattery(CNSocket* sock, CNPacketData* data) { auto req = (sP_CL2FE_REQ_PC_VENDOR_BATTERY_BUY*)data->buf; Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; int cost = req->Item.iOpt * 100; if ((req->Item.iID == 3 ? (plr->batteryW >= 9999) : (plr->batteryN >= 9999)) || plr->money < cost || req->Item.iOpt < 0) { // sanity check INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_BATTERY_BUY_FAIL, failResp); @@ -306,6 +312,7 @@ static void vendorCombineItems(CNSocket* sock, CNPacketData* data) { auto req = (sP_CL2FE_REQ_PC_ITEM_COMBINATION*)data->buf; Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; // prepare fail packet INITSTRUCT(sP_FE2CL_REP_PC_ITEM_COMBINATION_FAIL, failResp); failResp.iCostumeItemSlot = req->iCostumeItemSlot; diff --git a/src/core/CNProtocol.cpp b/src/core/CNProtocol.cpp index c96916da0..6e1edd6ed 100644 --- a/src/core/CNProtocol.cpp +++ b/src/core/CNProtocol.cpp @@ -125,21 +125,18 @@ void CNSocket::kill() { } void CNSocket::validatingSendPacket(void *pkt, uint32_t packetType) { - assert(isOutboundPacketID(packetType)); - assert(Packets::packets.find(packetType) != Packets::packets.end()); + if (!isOutboundPacketID(packetType)) + return; + if (Packets::packets.find(packetType) == Packets::packets.end()) + return; PacketDesc& desc = Packets::packets[packetType]; size_t resplen = desc.size; - /* - * Note that this validation doesn't happen on time to prevent a buffer - * overflow if it would have taken place, but we do it anyway so the - * assertion failure at least makes it clear that something isn't being - * validated properly. - */ if (desc.variadic) { int32_t ntrailers = *(int32_t*)(((uint8_t*)pkt) + desc.cntMembOfs); - assert(validOutVarPacket(desc.size, ntrailers, desc.trailerSize)); + if (!validOutVarPacket(desc.size, ntrailers, desc.trailerSize)) + return; resplen = desc.size + ntrailers * desc.trailerSize; } diff --git a/src/db/shard.cpp b/src/db/shard.cpp index 2b6a05fd0..895c251f4 100644 --- a/src/db/shard.cpp +++ b/src/db/shard.cpp @@ -1,7 +1,5 @@ #include "db/internal.hpp" -#include - // Miscellanious in-game database interactions static int getAccountIDFromPlayerID(int playerId, int *accountLevel=nullptr) { @@ -254,7 +252,11 @@ RaceRanking Database::getTopRaceRanking(int epID, int playerID) { return ranking; } - assert(epID == sqlite3_column_int(stmt, 0)); // EPIDs should always match + if (epID != sqlite3_column_int(stmt, 0)) { + std::cout << "[WARN] Race ranking EPID mismatch" << std::endl; + sqlite3_finalize(stmt); + return ranking; + } ranking.EPID = epID; ranking.PlayerID = sqlite3_column_int(stmt, 1); From 1f8b63ddecbba342c731e863bccabfe4669e5c65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kyle=20=F0=9F=90=86?= Date: Thu, 12 Feb 2026 13:36:51 -0500 Subject: [PATCH 2/3] Add input validation, overflow protection, and buffer safety --- src/BuiltinCommands.cpp | 3 +++ src/Chat.cpp | 6 +++-- src/Eggs.cpp | 6 ++--- src/Email.cpp | 44 +++++++++++++++++++----------- src/Items.cpp | 7 ++++- src/Missions.cpp | 12 ++++++--- src/Racing.cpp | 10 +++++-- src/Trading.cpp | 35 ++++++++++++++++++++++-- src/Vendors.cpp | 12 ++++++--- src/core/CNProtocol.cpp | 2 +- src/db/login.cpp | 3 ++- src/db/player.cpp | 2 +- src/main.cpp | 17 +++++++----- src/servers/CNLoginServer.cpp | 50 ++++++++++++++++++++++++++--------- 14 files changed, 155 insertions(+), 54 deletions(-) diff --git a/src/BuiltinCommands.cpp b/src/BuiltinCommands.cpp index c9f265768..e98282e0b 100644 --- a/src/BuiltinCommands.cpp +++ b/src/BuiltinCommands.cpp @@ -326,6 +326,9 @@ static void itemGMGiveHandler(CNSocket* sock, CNPacketData* data) { return; } + if (itemreq->iSlotNum < 0 || itemreq->iSlotNum >= AINVEN_COUNT) + return; + if (itemreq->Item.iType == 10) { // item is vehicle, set expiration date // set time limit: current time + 7days diff --git a/src/Chat.cpp b/src/Chat.cpp index 0abf744a9..2e5fc54a8 100644 --- a/src/Chat.cpp +++ b/src/Chat.cpp @@ -287,7 +287,8 @@ static void groupMenuChatHandler(CNSocket* sock, CNPacketData* data) { if (plr->group == nullptr) sock->sendPacket((void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC)); - Groups::sendToGroup(plr->group, (void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC)); + else + Groups::sendToGroup(plr->group, (void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC)); } // we only allow plain ascii, at least for now @@ -296,7 +297,8 @@ std::string Chat::sanitizeText(std::string text, bool allowNewlines) { const int BUFSIZE = 512; char buf[BUFSIZE]; - assert(text.size() < BUFSIZE); + if (text.size() >= BUFSIZE) + text.resize(BUFSIZE - 1); i = 0; for (char c : text) { diff --git a/src/Eggs.cpp b/src/Eggs.cpp index 427b5698f..eca10d50d 100644 --- a/src/Eggs.cpp +++ b/src/Eggs.cpp @@ -147,12 +147,10 @@ static void eggPickup(CNSocket* sock, CNPacketData* data) { return; } - /* this has some issues with position desync, leaving it out for now - if (abs(egg->x - plr->x)>500 || abs(egg->y - plr->y) > 500) { - std::cout << "[WARN] Player tried to open an egg isn't nearby?!" << std::endl; + if (abs(egg->x - plr->x) > 10000 || abs(egg->y - plr->y) > 10000) { + std::cout << "[WARN] Player tried to open an egg that isn't nearby" << std::endl; return; } - */ int typeId = egg->type; if (EggTypes.find(typeId) == EggTypes.end()) { diff --git a/src/Email.cpp b/src/Email.cpp index 17c206101..65165eedf 100644 --- a/src/Email.cpp +++ b/src/Email.cpp @@ -9,12 +9,16 @@ #include "Items.hpp" #include "Chat.hpp" +#include + using namespace Email; // New email notification static void emailUpdateCheck(CNSocket* sock, CNPacketData* data) { INITSTRUCT(sP_FE2CL_REP_PC_NEW_EMAIL, resp); - resp.iNewEmailCnt = Database::getUnreadEmailCount(PlayerManager::getPlayer(sock)->iID); + Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; + resp.iNewEmailCnt = Database::getUnreadEmailCount(plr->iID); sock->sendPacket(resp, P_FE2CL_REP_PC_NEW_EMAIL); } @@ -25,21 +29,23 @@ static void emailReceivePageList(CNSocket* sock, CNPacketData* data) { INITSTRUCT(sP_FE2CL_REP_PC_RECV_EMAIL_PAGE_LIST_SUCC, resp); resp.iPageNum = pkt->iPageNum; - std::vector emails = Database::getEmails(PlayerManager::getPlayer(sock)->iID, pkt->iPageNum); + Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; + std::vector emails = Database::getEmails(plr->iID, pkt->iPageNum); for (int i = 0; i < emails.size(); i++) { // convert each email and load them into the packet - Database::EmailData* email = &emails.at(i); - sEmailInfo* emailInfo = new sEmailInfo(); - emailInfo->iEmailIndex = email->MsgIndex; - emailInfo->iReadFlag = email->ReadFlag; - emailInfo->iItemCandyFlag = email->ItemFlag; - emailInfo->iFromPCUID = email->SenderId; - emailInfo->SendTime = timeStampToStruct(email->SendTime); - emailInfo->DeleteTime = timeStampToStruct(email->DeleteTime); - U8toU16(email->SenderFirstName, emailInfo->szFirstName, sizeof(emailInfo->szFirstName)); - U8toU16(email->SenderLastName, emailInfo->szLastName, sizeof(emailInfo->szLastName)); - U8toU16(email->SubjectLine, emailInfo->szSubject, sizeof(emailInfo->szSubject)); - resp.aEmailInfo[i] = *emailInfo; + Database::EmailData* email = &emails[i]; + sEmailInfo emailInfo = {}; + emailInfo.iEmailIndex = email->MsgIndex; + emailInfo.iReadFlag = email->ReadFlag; + emailInfo.iItemCandyFlag = email->ItemFlag; + emailInfo.iFromPCUID = email->SenderId; + emailInfo.SendTime = timeStampToStruct(email->SendTime); + emailInfo.DeleteTime = timeStampToStruct(email->DeleteTime); + U8toU16(email->SenderFirstName, emailInfo.szFirstName, sizeof(emailInfo.szFirstName)); + U8toU16(email->SenderLastName, emailInfo.szLastName, sizeof(emailInfo.szLastName)); + U8toU16(email->SubjectLine, emailInfo.szSubject, sizeof(emailInfo.szSubject)); + resp.aEmailInfo[i] = emailInfo; } sock->sendPacket(resp, P_FE2CL_REP_PC_RECV_EMAIL_PAGE_LIST_SUCC); @@ -63,6 +69,7 @@ static void emailRead(CNSocket* sock, CNPacketData* data) { for (int i = 0; i < 4; i++) { resp.aItem[i] = attachments[i]; } + delete[] attachments; U8toU16(email.MsgBody, (char16_t*)resp.szContent, sizeof(resp.szContent)); sock->sendPacket(resp, P_FE2CL_REP_PC_READ_EMAIL_SUCC); @@ -74,8 +81,11 @@ static void emailReceiveTaros(CNSocket* sock, CNPacketData* data) { Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; Database::EmailData email = Database::getEmail(plr->iID, pkt->iEmailIndex); // money transfer + if (email.Taros < 0 || plr->money > INT32_MAX - email.Taros) + return; plr->money += email.Taros; email.Taros = 0; // update Taros in email @@ -93,12 +103,14 @@ static void emailReceiveItemSingle(CNSocket* sock, CNPacketData* data) { auto pkt = (sP_CL2FE_REQ_PC_RECV_EMAIL_ITEM*)data->buf; Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; if (pkt->iSlotNum < 0 || pkt->iSlotNum >= AINVEN_COUNT || pkt->iEmailItemSlot < 1 || pkt->iEmailItemSlot > 4) return; // sanity check // get email item from db and delete it sItemBase* attachments = Database::getEmailAttachments(plr->iID, pkt->iEmailIndex); sItemBase itemFrom = attachments[pkt->iEmailItemSlot - 1]; + delete[] attachments; Database::deleteEmailAttachments(plr->iID, pkt->iEmailIndex, pkt->iEmailItemSlot); // move item to player inventory @@ -157,6 +169,7 @@ static void emailReceiveItemAll(CNSocket* sock, CNPacketData* data) { sock->sendPacket(resp2, P_FE2CL_REP_PC_GIVE_ITEM_SUCC); } + delete[] itemsFrom; // delete all items from db Database::deleteEmailAttachments(plr->iID, pkt->iEmailIndex, -1); @@ -222,7 +235,8 @@ static void emailSend(CNSocket* sock, CNPacketData* data) { } } - if (pkt->iCash < 0 || pkt->iCash > plr->money + 50 + 20 * itemCount || invalid) { + int64_t totalCost = (int64_t)pkt->iCash + 50 + 20 * itemCount; + if (pkt->iCash < 0 || totalCost > plr->money || invalid) { INITSTRUCT(sP_FE2CL_REP_PC_SEND_EMAIL_FAIL, errResp); errResp.iErrorCode = 1; errResp.iTo_PCUID = pkt->iTo_PCUID; diff --git a/src/Items.cpp b/src/Items.cpp index 08699a433..db705e665 100644 --- a/src/Items.cpp +++ b/src/Items.cpp @@ -83,7 +83,7 @@ static void nanoCapsuleHandler(CNSocket* sock, int slot, sItemBase *chest) { INITSTRUCT(sP_FE2CL_GM_REP_PC_ANNOUNCE, msg); msg.iDuringTime = 4; std::string text = "You have already acquired this nano!"; - U8toU16(text, msg.szAnnounceMsg, sizeof(text)); + U8toU16(text, msg.szAnnounceMsg, sizeof(msg.szAnnounceMsg)); sock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE); return; } @@ -437,11 +437,16 @@ static void itemUseHandler(CNSocket* sock, CNPacketData* data) { auto request = (sP_CL2FE_REQ_ITEM_USE*)data->buf; Player* player = PlayerManager::getPlayer(sock); + if (player == nullptr) return; if (request->iSlotNum < 0 || request->iSlotNum >= AINVEN_COUNT) return; // sanity check // gumball can only be used from inventory, so we ignore eIL sItemBase gumball = player->Inven[request->iSlotNum]; + + if (request->iNanoSlot < 0 || request->iNanoSlot > 2) + return; + sNano nano = player->Nanos[player->equippedNanos[request->iNanoSlot]]; // sanity check, check if gumball exists diff --git a/src/Missions.cpp b/src/Missions.cpp index d7c5af1ba..c7e75ffa6 100644 --- a/src/Missions.cpp +++ b/src/Missions.cpp @@ -6,6 +6,8 @@ #include "Items.hpp" #include "Nanos.hpp" +#include + using namespace Missions; std::map Missions::Rewards; @@ -158,21 +160,23 @@ static int giveMissionReward(CNSocket *sock, int task, int choice=0) { uint8_t respbuf[CN_PACKET_BODY_SIZE]; size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + nrewards * sizeof(sItemReward); - assert(resplen < CN_PACKET_BODY_SIZE); + if (resplen >= CN_PACKET_BODY_SIZE) + return -1; sP_FE2CL_REP_REWARD_ITEM *resp = (sP_FE2CL_REP_REWARD_ITEM *)respbuf; sItemReward *item = (sItemReward *)(respbuf + sizeof(sP_FE2CL_REP_REWARD_ITEM)); // don't forget to zero the buffer! memset(respbuf, 0, CN_PACKET_BODY_SIZE); - // update player - plr->money += reward->money; + // update player (overflow-safe) + int64_t newMoney = (int64_t)plr->money + reward->money; if (plr->hasBuff(ECSB_REWARD_CASH)) { // nano boost for taros int boost = 0; if (Nanos::getNanoBoost(plr)) // for gumballs boost = 1; - plr->money += reward->money * (5 + boost) / 25; + newMoney += (int64_t)reward->money * (5 + boost) / 25; } + plr->money = (int)std::min(std::max(newMoney, (int64_t)0), (int64_t)INT32_MAX); if (plr->hasBuff(ECSB_REWARD_BLOB)) { // nano boost for fm int boost = 0; diff --git a/src/Racing.cpp b/src/Racing.cpp index 3573f5b72..5e1cf037f 100644 --- a/src/Racing.cpp +++ b/src/Racing.cpp @@ -44,8 +44,14 @@ static void racingGetPod(CNSocket* sock, CNPacketData* data) { if (EPRaces[sock].collectedRings.count(req->iRingLID)) return; // can't collect the same ring twice - // without an anticheat system, we really don't have a choice but to honor the request - // TODO: proximity check so players can't cheat the race by replaying packets + Player* plr = PlayerManager::getPlayer(sock); + if (plr == nullptr) return; + + if (plr->instanceID == 0) { + std::cout << "[WARN] Player tried to collect ring outside IZ" << std::endl; + return; + } + EPRaces[sock].collectedRings.insert(req->iRingLID); INITSTRUCT(sP_FE2CL_REP_EP_GET_RING_SUCC, resp); diff --git a/src/Trading.cpp b/src/Trading.cpp index 9ee18427f..2f654074c 100644 --- a/src/Trading.cpp +++ b/src/Trading.cpp @@ -19,6 +19,8 @@ static bool doTrade(Player* plr, Player* plr2) { for (int i = 0; i < 5; i++) { // remove items offered by us if (plr->Trade[i].iID != 0) { + if (plr->Trade[i].iInvenNum < 0 || plr->Trade[i].iInvenNum >= AINVEN_COUNT) + return false; if (plrInven[plr->Trade[i].iInvenNum].iID == 0 || plr->Trade[i].iID != plrInven[plr->Trade[i].iInvenNum].iID || plr->Trade[i].iType != plrInven[plr->Trade[i].iInvenNum].iType) // pulling a fast one on us @@ -42,6 +44,8 @@ static bool doTrade(Player* plr, Player* plr2) { } if (plr2->Trade[i].iID != 0) { + if (plr2->Trade[i].iInvenNum < 0 || plr2->Trade[i].iInvenNum >= AINVEN_COUNT) + return false; if (plr2Inven[plr2->Trade[i].iInvenNum].iID == 0 || plr2->Trade[i].iID != plr2Inven[plr2->Trade[i].iInvenNum].iID || plr2->Trade[i].iType != plr2Inven[plr2->Trade[i].iInvenNum].iType) // pulling a fast one on us @@ -275,19 +279,40 @@ static void tradeConfirm(CNSocket* sock, CNPacketData* data) { plr->isTradeConfirm = false; plr2->isTradeConfirm = false; + int64_t newMoney1 = (int64_t)plr->money + plr2->moneyInTrade - plr->moneyInTrade; + int64_t newMoney2 = (int64_t)plr2->money + plr->moneyInTrade - plr2->moneyInTrade; + if (plr->moneyInTrade > plr->money || plr2->moneyInTrade > plr2->money + || newMoney1 > INT32_MAX || newMoney1 < 0 + || newMoney2 > INT32_MAX || newMoney2 < 0) { + INITSTRUCT(sP_FE2CL_REP_PC_TRADE_CONFIRM_ABORT, fail); + fail.iID_Request = plr->iID; + fail.iID_From = pacdat->iID_From; + fail.iID_To = pacdat->iID_To; + sock->sendPacket((void*)&fail, P_FE2CL_REP_PC_TRADE_CONFIRM_ABORT, sizeof(sP_FE2CL_REP_PC_TRADE_CONFIRM_ABORT)); + fail.iID_Request = plr2->iID; + otherSock->sendPacket((void*)&fail, P_FE2CL_REP_PC_TRADE_CONFIRM_ABORT, sizeof(sP_FE2CL_REP_PC_TRADE_CONFIRM_ABORT)); + + memset(&plr->Trade, 0, sizeof(plr->Trade)); + memset(&plr2->Trade, 0, sizeof(plr2->Trade)); + plr->moneyInTrade = 0; + plr2->moneyInTrade = 0; + return; + } + if (doTrade(plr, plr2)) { // returns false if not enough slots INITSTRUCT(sP_FE2CL_REP_PC_TRADE_CONFIRM_SUCC, resp2); resp2.iID_Request = pacdat->iID_Request; resp2.iID_From = pacdat->iID_From; resp2.iID_To = pacdat->iID_To; - plr->money = plr->money + plr2->moneyInTrade - plr->moneyInTrade; + + plr->money = (int32_t)newMoney1; resp2.iCandy = plr->money; memcpy(resp2.Item, plr2->Trade, sizeof(plr2->Trade)); memcpy(resp2.ItemStay, plr->Trade, sizeof(plr->Trade)); sock->sendPacket((void*)&resp2, P_FE2CL_REP_PC_TRADE_CONFIRM_SUCC, sizeof(sP_FE2CL_REP_PC_TRADE_CONFIRM_SUCC)); - plr2->money = plr2->money + plr->moneyInTrade - plr2->moneyInTrade; + plr2->money = (int32_t)newMoney2; resp2.iCandy = plr2->money; memcpy(resp2.Item, plr->Trade, sizeof(plr->Trade)); memcpy(resp2.ItemStay, plr2->Trade, sizeof(plr2->Trade)); @@ -350,6 +375,9 @@ static void tradeRegisterItem(CNSocket* sock, CNPacketData* data) { if (pacdat->Item.iSlotNum < 0 || pacdat->Item.iSlotNum > 4) return; // sanity check, there are only 5 trade slots + if (pacdat->Item.iInvenNum < 0 || pacdat->Item.iInvenNum >= AINVEN_COUNT) + return; + CNSocket* otherSock; // weird flip flop because we need to know who the other player is if (pacdat->iID_Request == pacdat->iID_From) otherSock = PlayerManager::getSockFromID(pacdat->iID_To); @@ -419,6 +447,9 @@ static void tradeUnregisterItem(CNSocket* sock, CNPacketData* data) { resp.TradeItem = pacdat->Item; resp.InvenItem = plr->Trade[pacdat->Item.iSlotNum]; + if (resp.InvenItem.iInvenNum < 0 || resp.InvenItem.iInvenNum >= AINVEN_COUNT) + return; + memset(&plr->Trade[pacdat->Item.iSlotNum], 0, sizeof(plr->Trade[pacdat->Item.iSlotNum])); // clean up item slot // since you can spread items like gumballs over multiple slots, we need to count them all diff --git a/src/Vendors.cpp b/src/Vendors.cpp index e0bede570..f8f9e206c 100644 --- a/src/Vendors.cpp +++ b/src/Vendors.cpp @@ -49,7 +49,12 @@ static void vendorBuy(CNSocket* sock, CNPacketData* data) { return; } - int itemCost = itemDat->buyPrice * (itemDat->stackSize > 1 ? req->Item.iOpt : 1); + int64_t itemCost64 = (int64_t)itemDat->buyPrice * (itemDat->stackSize > 1 ? req->Item.iOpt : 1); + if (itemCost64 > INT32_MAX || itemCost64 < 0) { + sock->sendPacket(failResp, P_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL); + return; + } + int itemCost = (int)itemCost64; int slot = Items::findFreeSlot(plr); if (itemCost > plr->money || slot == -1) { sock->sendPacket(failResp, P_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL); @@ -120,8 +125,9 @@ static void vendorSell(CNSocket* sock, CNPacketData* data) { INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_SELL_SUCC, resp); - // increment taros - plr->money += itemData->sellPrice * req->iItemCnt; + // increment taros (overflow-safe) + int64_t newMoney = (int64_t)plr->money + (int64_t)itemData->sellPrice * req->iItemCnt; + plr->money = (int)std::min(newMoney, (int64_t)INT32_MAX); // modify item if (item->iOpt - req->iItemCnt > 0) { // selling part of a stack diff --git a/src/core/CNProtocol.cpp b/src/core/CNProtocol.cpp index 6e1edd6ed..7ac67518b 100644 --- a/src/core/CNProtocol.cpp +++ b/src/core/CNProtocol.cpp @@ -251,7 +251,7 @@ void CNSocket::step() { // we got our packet size!!!! readSize = *((int32_t*)readBuffer); // sanity check - if (readSize > CN_PACKET_BUFFER_SIZE) { + if (readSize <= 0 || readSize > CN_PACKET_BUFFER_SIZE) { kill(); return; } diff --git a/src/db/login.cpp b/src/db/login.cpp index 55b2e83fe..7c94c13f4 100644 --- a/src/db/login.cpp +++ b/src/db/login.cpp @@ -26,7 +26,8 @@ void Database::findAccount(Account* account, std::string login) { account->Password = std::string(reinterpret_cast(sqlite3_column_text(stmt, 1))); account->Selected = sqlite3_column_int(stmt, 2); account->BannedUntil = sqlite3_column_int64(stmt, 3); - account->BanReason = reinterpret_cast(sqlite3_column_text(stmt, 4)); + const char* reason = reinterpret_cast(sqlite3_column_text(stmt, 4)); + account->BanReason = reason ? reason : ""; } sqlite3_finalize(stmt); } diff --git a/src/db/player.cpp b/src/db/player.cpp index 8396e1a37..6484366e1 100644 --- a/src/db/player.cpp +++ b/src/db/player.cpp @@ -177,7 +177,7 @@ void Database::getPlayer(Player* plr, int id) { int slot = sqlite3_column_int(stmt, 0); // for extra safety - if (slot < 0) + if (slot < 0 || slot >= AQINVEN_COUNT) continue; sItemBase* item = &plr->QInven[slot]; diff --git a/src/main.cpp b/src/main.cpp index e57faa19f..47b2a8258 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -192,16 +192,21 @@ std::string U16toU8(char16_t* src, size_t max) { // returns number of char16_t that was written at des size_t U8toU16(std::string src, char16_t* des, size_t max) { + size_t maxChars = max / sizeof(char16_t); + if (maxChars == 0) + return 0; + std::wstring_convert,char16_t> convert; std::u16string tmp = convert.from_bytes(src); - // copy utf16 string to buffer - if (sizeof(char16_t) * tmp.length() > max) // make sure we don't write outside the buffer - memcpy(des, tmp.c_str(), sizeof(char16_t) * max); - else - memcpy(des, tmp.c_str(), sizeof(char16_t) * tmp.length()); - des[tmp.length()] = '\0'; + if (tmp.length() >= maxChars) { + memcpy(des, tmp.c_str(), sizeof(char16_t) * (maxChars - 1)); + des[maxChars - 1] = '\0'; + return maxChars - 1; + } + memcpy(des, tmp.c_str(), sizeof(char16_t) * tmp.length()); + des[tmp.length()] = '\0'; return tmp.length(); } diff --git a/src/servers/CNLoginServer.cpp b/src/servers/CNLoginServer.cpp index fda4387f1..dde7b38e8 100644 --- a/src/servers/CNLoginServer.cpp +++ b/src/servers/CNLoginServer.cpp @@ -375,10 +375,10 @@ bool validateCharacterCreation(sP_CL2LS_REQ_CHAR_CREATE* character) { if (!(style->iBody >= 0 && style->iBody <= 2 && style->iEyeColor >= 1 && style->iEyeColor <= 5 && style->iGender >= 1 && style->iGender <= 2 && - style->iHairColor >= 1 && style->iHairColor <= 18) && + style->iHairColor >= 1 && style->iHairColor <= 18 && style->iHeight >= 0 && style->iHeight <= 4 && style->iNameCheck >= 0 && style->iNameCheck <= 2 && - style->iSkinColor >= 1 && style->iSkinColor <= 12) + style->iSkinColor >= 1 && style->iSkinColor <= 12)) return false; // facestyle and hairstyle are gender dependent @@ -595,17 +595,15 @@ void CNLoginServer::changeName(CNSocket* sock, CNPacketData* data) { } void CNLoginServer::duplicateExit(CNSocket* sock, CNPacketData* data) { - // TODO: FIX THIS PACKET - sP_CL2LS_REQ_PC_EXIT_DUPLICATE* exit = (sP_CL2LS_REQ_PC_EXIT_DUPLICATE*)data->buf; Database::Account account = {}; Database::findAccount(&account, AUTOU16TOU8(exit->szID)); - // sanity check - if (account.AccountID == 0) { - std::cout << "[WARN] P_CL2LS_REQ_PC_EXIT_DUPLICATE submitted unknown username: " << exit->szID << std::endl; + if (account.AccountID == 0) + return; + + if (loginSessions.find(sock) == loginSessions.end() || loginSessions[sock].userID != account.AccountID) return; - } exitDuplicate(account.AccountID); } @@ -719,11 +717,39 @@ bool CNLoginServer::isNameWheelNameGood(int fnCode, int mnCode, int lnCode, std: return true; } +static bool isNamePartValid(const std::string& name) { + if (name.empty() || name.size() > 32) + return false; + + for (size_t i = 0; i < name.size(); i++) { + char c = name[i]; + + if (std::isalnum(c)) + continue; + + if (c == '.') { + if (i == 0 || i == name.size() - 1) + return false; + if (name[i - 1] == '.' || name[i - 1] == ' ') + return false; + continue; + } + + if (c == ' ') { + if (i == 0 || i == name.size() - 1) + return false; + if (name[i - 1] == ' ' || name[i - 1] == '.') + return false; + continue; + } + + 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}(?! +))*$)"); - std::regex lastnamecheck(R"(((?! )(?!\.)[a-zA-Z0-9]*\.{0,1}(?!\.+ +)[a-zA-Z0-9]* {0,1}(?! +))*$)"); - return (std::regex_match(Firstname, firstnamecheck) && std::regex_match(Lastname, lastnamecheck)); + return isNamePartValid(Firstname) && isNamePartValid(Lastname); } bool CNLoginServer::isAuthMethodAllowed(AuthMethod authMethod) { From e9683fdc40509a93a2af104dda31a05a6b78d90e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kyle=20=F0=9F=90=86?= Date: Thu, 12 Feb 2026 13:38:14 -0500 Subject: [PATCH 3/3] Add network security: rate limiting, connection limits, monitor auth --- src/core/CNProtocol.cpp | 22 ++++++++++++ src/servers/CNLoginServer.cpp | 54 +++++++++++++++++++++++++++++ src/servers/Monitor.cpp | 65 +++++++++++++++++++++++++++++------ src/settings.cpp | 14 +++++++- src/settings.hpp | 3 ++ 5 files changed, 146 insertions(+), 12 deletions(-) diff --git a/src/core/CNProtocol.cpp b/src/core/CNProtocol.cpp index 7ac67518b..27ccbafbd 100644 --- a/src/core/CNProtocol.cpp +++ b/src/core/CNProtocol.cpp @@ -2,6 +2,9 @@ #include "CNStructs.hpp" #include +#include + +static std::map connectionsPerIP; // ========================================================[[ CNSocketEncryption ]]======================================================== @@ -462,6 +465,21 @@ void CNServer::start() { if (!setSockNonblocking(sock, newConnectionSocket)) continue; + uint32_t connIP = address.sin_addr.s_addr; + auto ipIt = connectionsPerIP.find(connIP); + if (settings::MAXPERIP > 0 && ipIt != connectionsPerIP.end() && ipIt->second >= settings::MAXPERIP) { + std::cout << "[WARN] Rejecting connection from " << inet_ntoa(address.sin_addr) << " (too many connections)" << std::endl; +#ifdef _WIN32 + shutdown(newConnectionSocket, SD_BOTH); + closesocket(newConnectionSocket); +#else + shutdown(newConnectionSocket, SHUT_RDWR); + close(newConnectionSocket); +#endif + continue; + } + connectionsPerIP[connIP]++; + std::cout << "New " << serverType << " connection! " << inet_ntoa(address.sin_addr) << std::endl; addPollFD(newConnectionSocket); @@ -507,6 +525,10 @@ void CNServer::start() { CNSocket *cSock = it->second; if (!cSock->isAlive()) { + uint32_t deadIP = cSock->sockaddr.sin_addr.s_addr; + if (connectionsPerIP.count(deadIP) && --connectionsPerIP[deadIP] <= 0) + connectionsPerIP.erase(deadIP); + killConnection(cSock); it = connections.erase(it); diff --git a/src/servers/CNLoginServer.cpp b/src/servers/CNLoginServer.cpp index dde7b38e8..837ccc00f 100644 --- a/src/servers/CNLoginServer.cpp +++ b/src/servers/CNLoginServer.cpp @@ -13,6 +13,52 @@ std::map CNLoginServer::loginSessions; +struct LoginAttempt { + int failCount; + time_t firstFail; + time_t blockedUntil; +}; +static std::map loginAttempts; + +static bool isLoginRateLimited(uint32_t ip) { + if (settings::LOGINRATELIMIT <= 0) + return false; + + auto it = loginAttempts.find(ip); + if (it == loginAttempts.end()) + return false; + + time_t now = getTime(); + if (it->second.blockedUntil > now) + return true; + + // reset if window expired + if (now - it->second.firstFail > 60000) { + loginAttempts.erase(it); + return false; + } + + return false; +} + +static void recordLoginFailure(uint32_t ip) { + if (settings::LOGINRATELIMIT <= 0) + return; + + time_t now = getTime(); + auto it = loginAttempts.find(ip); + if (it == loginAttempts.end() || now - it->second.firstFail > 60000) { + loginAttempts[ip] = { 1, now, 0 }; + return; + } + + it->second.failCount++; + if (it->second.failCount >= settings::LOGINRATELIMIT) { + it->second.blockedUntil = now + 60000; + std::cout << "[WARN] Rate limiting login attempts from IP" << std::endl; + } +} + namespace LoginServer { std::vector WheelFirstNames; std::vector WheelMiddleNames; @@ -113,6 +159,12 @@ void loginFail(LoginError errorCode, std::string userLogin, CNSocket* sock) { void CNLoginServer::login(CNSocket* sock, CNPacketData* data) { auto login = (sP_CL2LS_REQ_LOGIN*)data->buf; + uint32_t ip = sock->sockaddr.sin_addr.s_addr; + if (isLoginRateLimited(ip)) { + loginFail(LoginError::LOGIN_ERROR, "", sock); + return; + } + std::string userLogin; std::string userToken; // could be password or auth cookie @@ -159,11 +211,13 @@ void CNLoginServer::login(CNSocket* sock, CNPacketData* data) { return newAccount(sock, userLogin, userToken, login->iClientVerC); } + recordLoginFailure(ip); return loginFail(LoginError::ID_DOESNT_EXIST, userLogin, sock); } // make sure either a valid cookie or password was sent if (!CNLoginServer::checkToken(sock, findUser, userToken, isCookieAuth)) { + recordLoginFailure(ip); return loginFail(LoginError::ID_AND_PASSWORD_DO_NOT_MATCH, userLogin, sock); } diff --git a/src/servers/Monitor.cpp b/src/servers/Monitor.cpp index 7a434801f..5a16d56ca 100644 --- a/src/servers/Monitor.cpp +++ b/src/servers/Monitor.cpp @@ -8,10 +8,12 @@ #include "settings.hpp" #include +#include static SOCKET listener; static std::mutex sockLock; // guards socket list static std::list sockets; +static std::set pendingAuth; // sockets that haven't authenticated yet static sockaddr_in address; std::vector Monitor::chats; @@ -21,28 +23,33 @@ std::vector Monitor::namereqs; using namespace Monitor; +static void closeSock(SOCKET sock) { +#ifdef _WIN32 + shutdown(sock, SD_BOTH); + closesocket(sock); +#else + shutdown(sock, SHUT_RDWR); + close(sock); +#endif +} + static bool transmit(std::list::iterator& it, char *buff, int len) { int n = 0; int sock = *it; while (n < len) { - n += send(sock, buff+n, len-n, 0); - if (SOCKETERROR(n)) { + int ret = send(sock, buff+n, len-n, 0); + if (SOCKETERROR(ret)) { printSocketError("send"); -#ifdef _WIN32 - shutdown(sock, SD_BOTH); - closesocket(sock); -#else - shutdown(sock, SHUT_RDWR); - close(sock); -#endif + closeSock(sock); std::cout << "[INFO] Disconnected a monitor" << std::endl; it = sockets.erase(it); return false; } + n += ret; } return true; @@ -67,7 +74,7 @@ static int process_email(char *buff, std::string email) { int i = 6; for (char c : email) { - if (i == BUFSIZE-2) + if (i >= BUFSIZE-3) break; buff[i++] = c; @@ -86,6 +93,38 @@ static void tick(CNServer *serv, time_t delta) { char buff[BUFSIZE]; int n; + // check pending auth sockets for incoming password + for (auto it = pendingAuth.begin(); it != pendingAuth.end(); ) { + SOCKET s = *it; + char authbuf[256] = {}; + int recved = recv(s, authbuf, sizeof(authbuf) - 1, 0); + if (recved > 0) { + // strip trailing newline/whitespace + std::string pass(authbuf, recved); + while (!pass.empty() && (pass.back() == '\n' || pass.back() == '\r' || pass.back() == ' ')) + pass.pop_back(); + if (timingSafeStrcmp(pass.c_str(), settings::MONITORPASS.c_str()) == 0) { + sockets.push_back(s); + std::cout << "[INFO] Monitor client authenticated" << std::endl; + } else { + std::cout << "[WARN] Monitor client failed authentication" << std::endl; + closeSock(s); + } + it = pendingAuth.erase(it); + } else if (recved == 0) { + closeSock(s); + it = pendingAuth.erase(it); + } else { + // EWOULDBLOCK is fine, just wait + if (OF_ERRNO != OF_EWOULD) { + closeSock(s); + it = pendingAuth.erase(it); + } else { + ++it; + } + } + } + auto it = sockets.begin(); outer: while (it != sockets.end()) { @@ -179,7 +218,11 @@ bool Monitor::acceptConnection(SOCKET fd, uint16_t revents) { { std::lock_guard lock(sockLock); - sockets.push_back(sock); + if (settings::MONITORPASS.empty()) { + sockets.push_back(sock); + } else { + pendingAuth.insert(sock); + } } return true; diff --git a/src/settings.cpp b/src/settings.cpp index 1c505a327..8272c4bcf 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -14,7 +14,7 @@ std::string settings::SANDBOXEXTRAPATH = ""; int settings::LOGINPORT = 23000; bool settings::APPROVEWHEELNAMES = true; bool settings::APPROVECUSTOMNAMES = true; -bool settings::AUTOCREATEACCOUNTS = true; +bool settings::AUTOCREATEACCOUNTS = false; std::string settings::AUTHMETHODS = "password"; int settings::DBSAVEINTERVAL = 240; @@ -77,6 +77,15 @@ bool settings::IZRACESCORECAPPED = true; // drop fixes enabled bool settings::DROPFIXESENABLED = false; +// monitor auth +std::string settings::MONITORPASS = ""; + +// per-IP connection limit +int settings::MAXPERIP = 5; + +// login rate limit (max failed attempts per 60s) +int settings::LOGINRATELIMIT = 5; + void settings::init() { INIReader reader("config.ini"); @@ -130,6 +139,9 @@ void settings::init() { MONITORPORT = reader.GetInteger("monitor", "port", MONITORPORT); MONITORLISTENIP = reader.Get("monitor", "listenip", MONITORLISTENIP); MONITORINTERVAL = reader.GetInteger("monitor", "interval", MONITORINTERVAL); + MONITORPASS = reader.Get("monitor", "password", MONITORPASS); + MAXPERIP = reader.GetInteger("shard", "maxperip", MAXPERIP); + LOGINRATELIMIT = reader.GetInteger("login", "ratelimit", LOGINRATELIMIT); if (DROPFIXESENABLED) { std::cout << "[INFO] Drop fixes enabled" << std::endl; diff --git a/src/settings.hpp b/src/settings.hpp index d74c54df7..f64f026cf 100644 --- a/src/settings.hpp +++ b/src/settings.hpp @@ -46,6 +46,9 @@ namespace settings { extern bool DISABLEFIRSTUSEFLAG; extern bool IZRACESCORECAPPED; extern bool DROPFIXESENABLED; + extern std::string MONITORPASS; + extern int MAXPERIP; + extern int LOGINRATELIMIT; void init(); }