diff --git a/data/sql/updates/pending_db_auth/rev_1546018964238773800.sql b/data/sql/updates/pending_db_auth/rev_1546018964238773800.sql new file mode 100644 index 000000000..54603e52e --- /dev/null +++ b/data/sql/updates/pending_db_auth/rev_1546018964238773800.sql @@ -0,0 +1,17 @@ +INSERT INTO version_db_auth (`sql_rev`) VALUES ('1546018964238773800'); + +CREATE TABLE `logs_ip_actions` ( +`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'Unique Identifier', +`account_id` INT(10) UNSIGNED NOT NULL COMMENT 'Account ID', +`character_guid` INT(10) UNSIGNED NOT NULL COMMENT 'Character Guid', +`type` TINYINT(3) UNSIGNED NOT NULL, +`ip` VARCHAR(15) NOT NULL DEFAULT '127.0.0.1', +`systemnote` TEXT NULL COMMENT 'Notes inserted by system', +`unixtime` INT(10) UNSIGNED NOT NULL COMMENT 'Unixtime', +`time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Timestamp', +`comment` TEXT NULL COMMENT 'Allows users to add a comment', +PRIMARY KEY (`id`) +) +COMMENT='Used to log ips of individual actions' +COLLATE='utf8_general_ci' +ENGINE=InnoDB; diff --git a/src/common/Database/Implementation/LoginDatabase.cpp b/src/common/Database/Implementation/LoginDatabase.cpp index 597c3f222..a2548d0a3 100644 --- a/src/common/Database/Implementation/LoginDatabase.cpp +++ b/src/common/Database/Implementation/LoginDatabase.cpp @@ -55,6 +55,7 @@ void LoginDatabaseConnection::DoPrepareStatements() PrepareStatement(LOGIN_UPD_MUTE_TIME, "UPDATE account SET mutetime = ? , mutereason = ? , muteby = ? WHERE id = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_UPD_MUTE_TIME_LOGIN, "UPDATE account SET mutetime = ? WHERE id = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_UPD_LAST_IP, "UPDATE account SET last_ip = ? WHERE username = ?", CONNECTION_ASYNC); + PrepareStatement(LOGIN_UPD_LAST_ATTEMPT_IP, "UPDATE account SET last_attempt_ip = ? WHERE username = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_UPD_ACCOUNT_ONLINE, "UPDATE account SET online = online | (1<<(?-1)) WHERE id = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_UPD_UPTIME_PLAYERS, "UPDATE uptime SET uptime = ?, maxplayers = ? WHERE realmid = ? AND starttime = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_DEL_OLD_LOGS, "DELETE FROM logs WHERE (time + ?) < ?", CONNECTION_ASYNC); @@ -76,10 +77,20 @@ void LoginDatabaseConnection::DoPrepareStatements() PrepareStatement(LOGIN_SEL_ACCOUNT_RECRUITER, "SELECT 1 FROM account WHERE recruiter = ?", CONNECTION_SYNCH); PrepareStatement(LOGIN_SEL_BANS, "SELECT 1 FROM account_banned WHERE id = ? AND active = 1 UNION SELECT 1 FROM ip_banned WHERE ip = ?", CONNECTION_SYNCH); PrepareStatement(LOGIN_SEL_ACCOUNT_WHOIS, "SELECT username, email, last_ip FROM account WHERE id = ?", CONNECTION_SYNCH); + PrepareStatement(LOGIN_SEL_LAST_ATTEMPT_IP, "SELECT last_attempt_ip FROM account WHERE id = ?", CONNECTION_SYNCH); + PrepareStatement(LOGIN_SEL_LAST_IP, "SELECT last_ip FROM account WHERE id = ?", CONNECTION_SYNCH); PrepareStatement(LOGIN_SEL_REALMLIST_SECURITY_LEVEL, "SELECT allowedSecurityLevel from realmlist WHERE id = ?", CONNECTION_SYNCH); PrepareStatement(LOGIN_DEL_ACCOUNT, "DELETE FROM account WHERE id = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_SEL_IP2NATION_COUNTRY, "SELECT c.country FROM ip2nationCountries c, ip2nation i WHERE i.ip < ? AND c.code = i.country ORDER BY i.ip DESC LIMIT 0,1", CONNECTION_SYNCH); PrepareStatement(LOGIN_SEL_AUTOBROADCAST, "SELECT id, weight, text FROM autobroadcast WHERE realmid = ? OR realmid = -1", CONNECTION_SYNCH); PrepareStatement(LOGIN_INS_ACCOUNT_MUTE, "INSERT INTO account_muted VALUES (?, UNIX_TIMESTAMP(), ?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(LOGIN_SEL_ACCOUNT_MUTE_INFO, "SELECT mutedate, mutetime, mutereason, mutedby FROM account_muted WHERE guid = ? ORDER BY mutedate ASC", CONNECTION_SYNCH); + // 0: uint32, 1: uint32, 2: uint8, 3: uint32, 4: string // Complete name: "Login_Insert_AccountLoginDeLete_IP_Logging" + PrepareStatement(LOGIN_INS_ALDL_IP_LOGGING, "INSERT INTO logs_ip_actions (account_id,character_guid,type,ip,systemnote,unixtime,time) VALUES (?, ?, ?, (SELECT last_ip FROM account WHERE id = ?), ?, unix_timestamp(NOW()), NOW())", CONNECTION_ASYNC); + // 0: uint32, 1: uint32, 2: uint8, 3: uint32, 4: string // Complete name: "Login_Insert_FailedAccountLogin_IP_Logging" + PrepareStatement(LOGIN_INS_FACL_IP_LOGGING, "INSERT INTO logs_ip_actions (account_id,character_guid,type,ip,systemnote,unixtime,time) VALUES (?, ?, ?, (SELECT last_attempt_ip FROM account WHERE id = ?), ?, unix_timestamp(NOW()), NOW())", CONNECTION_ASYNC); + // 0: uint32, 1: uint32, 2: uint8, 3: string, 4: string // Complete name: "Login_Insert_CharacterDelete_IP_Logging" + PrepareStatement(LOGIN_INS_CHAR_IP_LOGGING, "INSERT INTO logs_ip_actions (account_id,character_guid,type,ip,systemnote,unixtime,time) VALUES (?, ?, ?, ?, ?, unix_timestamp(NOW()), NOW())", CONNECTION_ASYNC); + // 0: string, 1: string, 2: string // Complete name: "Login_Insert_Failed_Account_Login_due_password_IP_Logging" + PrepareStatement(LOGIN_INS_FALP_IP_LOGGING, "INSERT INTO logs_ip_actions (account_id,character_guid,type,ip,systemnote,unixtime,time) VALUES ((SELECT id FROM account WHERE username = ?), 0, 1, ?, ?, unix_timestamp(NOW()), NOW())", CONNECTION_ASYNC); } diff --git a/src/common/Database/Implementation/LoginDatabase.h b/src/common/Database/Implementation/LoginDatabase.h index c9b9edc65..603fe2af3 100644 --- a/src/common/Database/Implementation/LoginDatabase.h +++ b/src/common/Database/Implementation/LoginDatabase.h @@ -75,6 +75,7 @@ enum LoginDatabaseStatements LOGIN_UPD_MUTE_TIME, LOGIN_UPD_MUTE_TIME_LOGIN, LOGIN_UPD_LAST_IP, + LOGIN_UPD_LAST_ATTEMPT_IP, LOGIN_UPD_ACCOUNT_ONLINE, LOGIN_UPD_UPTIME_PLAYERS, LOGIN_DEL_OLD_LOGS, @@ -100,6 +101,12 @@ enum LoginDatabaseStatements LOGIN_DEL_ACCOUNT, LOGIN_SEL_IP2NATION_COUNTRY, LOGIN_SEL_AUTOBROADCAST, + LOGIN_SEL_LAST_ATTEMPT_IP, + LOGIN_SEL_LAST_IP, + LOGIN_INS_ALDL_IP_LOGGING, + LOGIN_INS_FACL_IP_LOGGING, + LOGIN_INS_CHAR_IP_LOGGING, + LOGIN_INS_FALP_IP_LOGGING, LOGIN_INS_ACCOUNT_MUTE, LOGIN_SEL_ACCOUNT_MUTE_INFO, diff --git a/src/server/authserver/Server/AuthSocket.cpp b/src/server/authserver/Server/AuthSocket.cpp index 85094992b..fef4e5f49 100644 --- a/src/server/authserver/Server/AuthSocket.cpp +++ b/src/server/authserver/Server/AuthSocket.cpp @@ -760,14 +760,26 @@ bool AuthSocket::_HandleLogonProof() socket().send(data, sizeof(data)); #if defined(ENABLE_EXTRAS) && defined(ENABLE_EXTRA_LOGS) - sLog->outDebug(LOG_FILTER_NETWORKIO, "'%s:%d' [AuthChallenge] account %s tried to login with invalid password!", socket().getRemoteAddress().c_str(), socket().getRemotePort(), _login.c_str ()); + sLog->outDebug(LOG_FILTER_NETWORKIO, "'%s:%d' [AuthChallenge] account %s tried to login with invalid password!", socket().getRemoteAddress().c_str(), socket().getRemotePort(), _login.c_str()); #endif uint32 MaxWrongPassCount = sConfigMgr->GetIntDefault("WrongPass.MaxCount", 0); + + // We can not include the failed account login hook. However, this is a workaround to still log this. + if (sConfigMgr->GetBoolDefault("WrongPass.Logging", false)) + { + PreparedStatement* logstmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_FALP_IP_LOGGING); + logstmt->setString(0, _login); + logstmt->setString(1, socket().getRemoteAddress()); + logstmt->setString(2, "Logged on failed AccountLogin due wrong password"); + + LoginDatabase.Execute(logstmt); + } + if (MaxWrongPassCount > 0) { //Increment number of failed logins by one and if it reaches the limit temporarily ban that account or IP - PreparedStatement *stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_FAILEDLOGINS); + PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_FAILEDLOGINS); stmt->setString(0, _login); LoginDatabase.Execute(stmt); diff --git a/src/server/authserver/authserver.conf.dist b/src/server/authserver/authserver.conf.dist index fba05578c..dfa99e160 100644 --- a/src/server/authserver/authserver.conf.dist +++ b/src/server/authserver/authserver.conf.dist @@ -233,6 +233,15 @@ WrongPass.BanTime = 600 WrongPass.BanType = 0 +# +# WrongPass.Logging +# Description: Additionally log attempted wrong password logging +# Default: 0 - (Disabled) +# 1 - (Enabled) + +WrongPass.Logging = 0 + + # ################################################################################################### diff --git a/src/server/game/Accounts/AccountMgr.cpp b/src/server/game/Accounts/AccountMgr.cpp index dffdcc793..0f62f61e9 100644 --- a/src/server/game/Accounts/AccountMgr.cpp +++ b/src/server/game/Accounts/AccountMgr.cpp @@ -8,6 +8,7 @@ #include "DatabaseEnv.h" #include "ObjectAccessor.h" #include "Player.h" +#include "ScriptMgr.h" #include "Util.h" #include "SHA1.h" #include "WorldSession.h" @@ -149,10 +150,16 @@ namespace AccountMgr std::string username; if (!GetName(accountId, username)) + { + sScriptMgr->OnFailedPasswordChange(accountId); return AOR_NAME_NOT_EXIST; // account doesn't exist + } if (utf8length(newPassword) > MAX_ACCOUNT_STR) + { + sScriptMgr->OnFailedEmailChange(accountId); return AOR_PASS_TOO_LONG; + } normalizeString(username); normalizeString(newPassword); @@ -164,6 +171,7 @@ namespace AccountMgr LoginDatabase.Execute(stmt); + sScriptMgr->OnPasswordChange(accountId); return AOR_OK; } diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 3cc02b1f5..d3b1f8fd1 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -1,4 +1,4 @@ -/* +/* * Copyright (C) 2016+ AzerothCore , released under GNU GPL v2 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-GPL2 * Copyright (C) 2008-2016 TrinityCore * Copyright (C) 2005-2009 MaNGOS @@ -2943,8 +2943,8 @@ class Player : public Unit, public GridObject uint32 manaBeforeDuel; }; -void AddItemsSetItem(Player*player, Item* item); -void RemoveItemsSetItem(Player*player, ItemTemplate const* proto); +void AddItemsSetItem(Player* player, Item* item); +void RemoveItemsSetItem(Player* player, ItemTemplate const* proto); // "the bodies of template functions must be made available in a header file" template T Player::ApplySpellMod(uint32 spellId, SpellModOp op, T &basevalue, Spell* spell, bool temporaryPet) diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp index 408591a9d..79065a3ba 100644 --- a/src/server/game/Handlers/CharacterHandler.cpp +++ b/src/server/game/Handlers/CharacterHandler.cpp @@ -678,10 +678,13 @@ void WorldSession::HandleCharDeleteOpcode(WorldPacket& recvData) { uint64 guid; recvData >> guid; + // Initiating + uint32 initAccountId = GetAccountId(); // can't delete loaded character if (ObjectAccessor::FindPlayerInOrOutOfWorld(guid) || sWorld->FindOfflineSessionForCharacterGUID(GUID_LOPART(guid))) { + sScriptMgr->OnPlayerFailedDelete(guid, initAccountId); WorldPacket data(SMSG_CHAR_DELETE, 1); data << (uint8)CHAR_DELETE_FAILED; SendPacket(&data); @@ -694,6 +697,7 @@ void WorldSession::HandleCharDeleteOpcode(WorldPacket& recvData) // is guild leader if (sGuildMgr->GetGuildByLeader(guid)) { + sScriptMgr->OnPlayerFailedDelete(guid, initAccountId); WorldPacket data(SMSG_CHAR_DELETE, 1); data << (uint8)CHAR_DELETE_FAILED_GUILD_LEADER; SendPacket(&data); @@ -703,6 +707,7 @@ void WorldSession::HandleCharDeleteOpcode(WorldPacket& recvData) // is arena team captain if (sArenaTeamMgr->GetArenaTeamByCaptain(guid)) { + sScriptMgr->OnPlayerFailedDelete(guid, initAccountId); WorldPacket data(SMSG_CHAR_DELETE, 1); data << (uint8)CHAR_DELETE_FAILED_ARENA_CAPTAIN; SendPacket(&data); @@ -716,15 +721,21 @@ void WorldSession::HandleCharDeleteOpcode(WorldPacket& recvData) } // prevent deleting other players' characters using cheating tools - if (accountId != GetAccountId()) + if (accountId != initAccountId) + { + sScriptMgr->OnPlayerFailedDelete(guid, initAccountId); return; + } std::string IP_str = GetRemoteAddress(); #if defined(ENABLE_EXTRAS) && defined(ENABLE_EXTRA_LOGS) sLog->outDetail("Account: %d (IP: %s) Delete Character:[%s] (GUID: %u)", GetAccountId(), IP_str.c_str(), name.c_str(), GUID_LOPART(guid)); #endif sLog->outChar("Account: %d (IP: %s) Delete Character:[%s] (GUID: %u)", GetAccountId(), IP_str.c_str(), name.c_str(), GUID_LOPART(guid)); - sScriptMgr->OnPlayerDelete(guid); + + // To prevent hook failure, place hook before removing reference from DB + sScriptMgr->OnPlayerDelete(guid, initAccountId); // To prevent race conditioning, but as it also makes sense, we hand the accountId over for successful delete. + // Shouldn't interfere with character deletion though if (sLog->IsOutCharDump()) // optimize GetPlayerDump call { diff --git a/src/server/game/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp index 52bf64647..6c6b45ce7 100644 --- a/src/server/game/Scripting/ScriptMgr.cpp +++ b/src/server/game/Scripting/ScriptMgr.cpp @@ -59,6 +59,7 @@ template class ScriptRegistry; template class ScriptRegistry; template class ScriptRegistry; template class ScriptRegistry; +template class ScriptRegistry; #include "ScriptMgrMacros.h" @@ -205,6 +206,7 @@ void ScriptMgr::Unload() SCR_CLEAR(TransportScript); SCR_CLEAR(AchievementCriteriaScript); SCR_CLEAR(PlayerScript); + SCR_CLEAR(AccountScript); SCR_CLEAR(GuildScript); SCR_CLEAR(GroupScript); SCR_CLEAR(GlobalScript); @@ -1577,12 +1579,17 @@ void ScriptMgr::OnPlayerSave(Player * player) FOREACH_SCRIPT(PlayerScript)->OnSave(player); } -void ScriptMgr::OnPlayerDelete(uint64 guid) +void ScriptMgr::OnPlayerDelete(uint64 guid, uint32 accountId) { #ifdef ELUNA sEluna->OnDelete(GUID_LOPART(guid)); #endif - FOREACH_SCRIPT(PlayerScript)->OnDelete(guid); + FOREACH_SCRIPT(PlayerScript)->OnDelete(guid, accountId); +} + +void ScriptMgr::OnPlayerFailedDelete(uint64 guid, uint32 accountId) +{ + FOREACH_SCRIPT(PlayerScript)->OnFailedDelete(guid, accountId); } void ScriptMgr::OnPlayerBindToInstance(Player* player, Difficulty difficulty, uint32 mapid, bool permanent) @@ -1704,6 +1711,37 @@ void ScriptMgr::OnFirstLogin(Player* player) FOREACH_SCRIPT(PlayerScript)->OnFirstLogin(player); } +// Account +void ScriptMgr::OnAccountLogin(uint32 accountId) +{ + FOREACH_SCRIPT(AccountScript)->OnAccountLogin(accountId); +} + +void ScriptMgr::OnFailedAccountLogin(uint32 accountId) +{ + FOREACH_SCRIPT(AccountScript)->OnFailedAccountLogin(accountId); +} + +void ScriptMgr::OnEmailChange(uint32 accountId) +{ + FOREACH_SCRIPT(AccountScript)->OnEmailChange(accountId); +} + +void ScriptMgr::OnFailedEmailChange(uint32 accountId) +{ + FOREACH_SCRIPT(AccountScript)->OnFailedEmailChange(accountId); +} + +void ScriptMgr::OnPasswordChange(uint32 accountId) +{ + FOREACH_SCRIPT(AccountScript)->OnPasswordChange(accountId); +} + +void ScriptMgr::OnFailedPasswordChange(uint32 accountId) +{ + FOREACH_SCRIPT(AccountScript)->OnFailedPasswordChange(accountId); +} + // Guild void ScriptMgr::OnGuildAddMember(Guild* guild, Player* player, uint8& plRank) { @@ -2162,6 +2200,12 @@ PlayerScript::PlayerScript(const char* name) ScriptRegistry::AddScript(this); } +AccountScript::AccountScript(const char* name) + : ScriptObject(name) +{ + ScriptRegistry::AddScript(this); +} + GuildScript::GuildScript(const char* name) : ScriptObject(name) { diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h index 34836eaec..78756ca3d 100644 --- a/src/server/game/Scripting/ScriptMgr.h +++ b/src/server/game/Scripting/ScriptMgr.h @@ -855,7 +855,10 @@ class PlayerScript : public ScriptObject virtual void OnCreate(Player* /*player*/) { } // Called when a player is deleted. - virtual void OnDelete(uint64 /*guid*/) { } + virtual void OnDelete(uint64 /*guid*/, uint32 /*accountId*/) { } + + // Called when a player delete failed. + virtual void OnFailedDelete(uint64 /*guid*/, uint32 /*accountId*/) { } // Called when a player is about to be saved. virtual void OnSave(Player* /*player*/) { } @@ -947,6 +950,38 @@ class PlayerScript : public ScriptObject virtual void OnFirstLogin(Player* /*player*/) { } }; +class AccountScript : public ScriptObject +{ + protected: + + AccountScript(const char* name); + + public: + + // Called when an account logged in successfully + virtual void OnAccountLogin(uint32 /*accountId*/) { } + + + // Called when an account login failed + virtual void OnFailedAccountLogin(uint32 /*accountId*/) { } + + + // Called when Email is successfully changed for Account + virtual void OnEmailChange(uint32 /*accountId*/) { } + + + // Called when Email failed to change for Account + virtual void OnFailedEmailChange(uint32 /*accountId*/) { } + + + // Called when Password is successfully changed for Account + virtual void OnPasswordChange(uint32 /*accountId*/) { } + + + // Called when Password failed to change for Account + virtual void OnFailedPasswordChange(uint32 /*accountId*/) { } +}; + class GuildScript : public ScriptObject { protected: @@ -1300,7 +1335,8 @@ class ScriptMgr void OnPlayerLogout(Player* player); void OnPlayerCreate(Player* player); void OnPlayerSave(Player* player); - void OnPlayerDelete(uint64 guid); + void OnPlayerDelete(uint64 guid, uint32 accountId); + void OnPlayerFailedDelete(uint64 guid, uint32 accountId); void OnPlayerBindToInstance(Player* player, Difficulty difficulty, uint32 mapid, bool permanent); void OnPlayerUpdateZone(Player* player, uint32 newZone, uint32 newArea); void OnPlayerUpdateArea(Player* player, uint32 oldArea, uint32 newArea); @@ -1333,6 +1369,15 @@ class ScriptMgr void OnFirstLogin(Player* player); void OnPlayerCompleteQuest(Player* player, Quest const* quest); + public: /* AccountScript */ + + void OnAccountLogin(uint32 accountId); + void OnFailedAccountLogin(uint32 accountId); + void OnEmailChange(uint32 accountId); + void OnFailedEmailChange(uint32 accountId); + void OnPasswordChange(uint32 accountId); + void OnFailedPasswordChange(uint32 accountId); + public: /* GuildScript */ void OnGuildAddMember(Guild* guild, Player* player, uint8& plRank); diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index 605de6434..11262b3d3 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -965,6 +965,7 @@ class WorldSession Player* _player; WorldSocket* m_Socket; std::string m_Address; + // std::string m_LAddress; // Last Attempted Remote Adress - we can not set attempted ip for a non-existing session! AccountTypes _security; bool _skipQueue; diff --git a/src/server/game/Server/WorldSocket.cpp b/src/server/game/Server/WorldSocket.cpp index d812c6eb1..13a462375 100644 --- a/src/server/game/Server/WorldSocket.cpp +++ b/src/server/game/Server/WorldSocket.cpp @@ -90,7 +90,7 @@ struct ClientPktHeader #pragma pack(pop) #endif -WorldSocket::WorldSocket (void): WorldHandler(), +WorldSocket::WorldSocket(void): WorldHandler(), m_LastPingTime(ACE_Time_Value::zero), m_OverSpeedPings(0), m_Session(0), m_RecvWPct(0), m_RecvPct(), m_Header(sizeof (ClientPktHeader)), m_OutBuffer(0), m_OutBufferSize(65536), m_OutActive(false), @@ -102,7 +102,7 @@ m_Seed(static_cast (rand32())) msg_queue()->low_water_mark(8*1024*1024); } -WorldSocket::~WorldSocket (void) +WorldSocket::~WorldSocket(void) { delete m_RecvWPct; @@ -114,12 +114,12 @@ WorldSocket::~WorldSocket (void) peer().close(); } -bool WorldSocket::IsClosed (void) const +bool WorldSocket::IsClosed(void) const { return closing_; } -void WorldSocket::CloseSocket (void) +void WorldSocket::CloseSocket(void) { { ACE_GUARD (LockType, Guard, m_OutBufferLock); @@ -138,7 +138,7 @@ void WorldSocket::CloseSocket (void) } } -const std::string& WorldSocket::GetRemoteAddress (void) const +const std::string& WorldSocket::GetRemoteAddress(void) const { return m_Address; } @@ -190,17 +190,17 @@ int WorldSocket::SendPacket(WorldPacket const& pct) return 0; } -long WorldSocket::AddReference (void) +long WorldSocket::AddReference(void) { return static_cast (add_reference()); } -long WorldSocket::RemoveReference (void) +long WorldSocket::RemoveReference(void) { return static_cast (remove_reference()); } -int WorldSocket::open (void *a) +int WorldSocket::open(void *a) { ACE_UNUSED_ARG (a); @@ -259,7 +259,7 @@ int WorldSocket::open (void *a) return 0; } -int WorldSocket::close (u_long) +int WorldSocket::close(u_long) { shutdown(); @@ -270,7 +270,7 @@ int WorldSocket::close (u_long) return 0; } -int WorldSocket::handle_input (ACE_HANDLE) +int WorldSocket::handle_input(ACE_HANDLE) { if (closing_) return -1; @@ -310,7 +310,7 @@ int WorldSocket::handle_input (ACE_HANDLE) ACE_NOTREACHED(return -1); } -int WorldSocket::handle_output (ACE_HANDLE) +int WorldSocket::handle_output(ACE_HANDLE) { ACE_GUARD_RETURN (LockType, Guard, m_OutBufferLock, -1); @@ -356,7 +356,7 @@ int WorldSocket::handle_output (ACE_HANDLE) ACE_NOTREACHED (return 0); } -int WorldSocket::handle_output_queue (GuardType& g) +int WorldSocket::handle_output_queue(GuardType& g) { if (msg_queue()->is_empty()) return cancel_wakeup_output(g); @@ -417,7 +417,7 @@ int WorldSocket::handle_output_queue (GuardType& g) ACE_NOTREACHED(return -1); } -int WorldSocket::handle_close (ACE_HANDLE h, ACE_Reactor_Mask) +int WorldSocket::handle_close(ACE_HANDLE h, ACE_Reactor_Mask) { // Critical section { @@ -440,7 +440,7 @@ int WorldSocket::handle_close (ACE_HANDLE h, ACE_Reactor_Mask) return 0; } -int WorldSocket::Update (void) +int WorldSocket::Update(void) { if (closing_) return -1; @@ -462,7 +462,7 @@ int WorldSocket::Update (void) return ret; } -int WorldSocket::handle_input_header (void) +int WorldSocket::handle_input_header(void) { ACE_ASSERT (m_RecvWPct == NULL); @@ -501,7 +501,7 @@ int WorldSocket::handle_input_header (void) return 0; } -int WorldSocket::handle_input_payload (void) +int WorldSocket::handle_input_payload(void) { // set errno properly here on error !!! // now have a header and payload @@ -524,7 +524,7 @@ int WorldSocket::handle_input_payload (void) return ret; } -int WorldSocket::handle_input_missing_data (void) +int WorldSocket::handle_input_missing_data(void) { char buf [4096]; @@ -613,7 +613,7 @@ int WorldSocket::handle_input_missing_data (void) return size_t(n) == recv_size ? 1 : 2; } -int WorldSocket::cancel_wakeup_output (GuardType& g) +int WorldSocket::cancel_wakeup_output(GuardType& g) { if (!m_OutActive) return 0; @@ -633,7 +633,7 @@ int WorldSocket::cancel_wakeup_output (GuardType& g) return 0; } -int WorldSocket::schedule_wakeup_output (GuardType& g) +int WorldSocket::schedule_wakeup_output(GuardType& g) { if (m_OutActive) return 0; @@ -741,6 +741,7 @@ int WorldSocket::HandleAuthSession(WorldPacket& recvPacket) WorldPacket packet, SendAddonPacked; BigNumber k; + bool wardenActive = sWorld->getBoolConfig(CONFIG_WARDEN_ENABLED); if (sWorld->IsClosed()) { @@ -779,6 +780,7 @@ int WorldSocket::HandleAuthSession(WorldPacket& recvPacket) // Stop if the account is not found if (!result) { + // We can not log here, as we do not know the account. Thus, no accountId. packet.Initialize (SMSG_AUTH_RESPONSE, 1); packet << uint8 (AUTH_UNKNOWN_ACCOUNT); @@ -795,21 +797,37 @@ int WorldSocket::HandleAuthSession(WorldPacket& recvPacket) if (expansion > world_expansion) expansion = world_expansion; + // For hook purposes, we get Remoteaddress at this point + std::string address = GetRemoteAddress(); // Originally, this variable should be called address, but for some reason, that variable name was already on the core. + + // As we don't know if attempted login process by ip works, we update last_attempt_ip right away + stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LAST_ATTEMPT_IP); + + stmt->setString(0, address); + stmt->setString(1, account); + + LoginDatabase.Execute(stmt); + // This also allows to check for possible "hack" attempts on account + + // id has to be fetched at this point, so that first actual account response that fails can be logged + id = fields[0].GetUInt32(); + ///- Re-check ip locking (same check as in realmd). if (fields[3].GetUInt8() == 1) // if ip is locked { - if (strcmp (fields[2].GetCString(), GetRemoteAddress().c_str())) + if (strcmp (fields[2].GetCString(), address.c_str())) { packet.Initialize (SMSG_AUTH_RESPONSE, 1); packet << uint8 (AUTH_FAILED); SendPacket(packet); - sLog->outBasic ("WorldSocket::HandleAuthSession: Sent Auth Response (Account IP differs)."); + sLog->outBasic ("WorldSocket::HandleAuthSession: Sent Auth Response (Account IP differs. Original IP: %s, new IP: %s).", fields[2].GetCString(), address.c_str()); + // We could log on hook only instead of an additional db log, however action logger is config based. Better keep DB logging as well + sScriptMgr->OnFailedAccountLogin(id); return -1; } } - id = fields[0].GetUInt32(); /* if (security > SEC_ADMINISTRATOR) // prevent invalid security settings in DB security = SEC_ADMINISTRATOR; @@ -840,13 +858,13 @@ int WorldSocket::HandleAuthSession(WorldPacket& recvPacket) TotalTime = fields[10].GetUInt32(); // Must be done before WorldSession is created - if (sWorld->getBoolConfig(CONFIG_WARDEN_ENABLED) && os != "Win" && os != "OSX") + if (wardenActive && os != "Win" && os != "OSX") { packet.Initialize(SMSG_AUTH_RESPONSE, 1); packet << uint8(AUTH_REJECT); SendPacket(packet); - sLog->outError("WorldSocket::HandleAuthSession: Client %s attempted to log in using invalid client OS (%s).", GetRemoteAddress().c_str(), os.c_str()); + sLog->outError("WorldSocket::HandleAuthSession: Client %s attempted to log in using invalid client OS (%s).", address.c_str(), os.c_str()); return -1; } @@ -871,7 +889,7 @@ int WorldSocket::HandleAuthSession(WorldPacket& recvPacket) stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_BANS); stmt->setUInt32(0, id); - stmt->setString(1, GetRemoteAddress()); + stmt->setString(1, address); PreparedQueryResult banresult = LoginDatabase.Query(stmt); @@ -882,6 +900,7 @@ int WorldSocket::HandleAuthSession(WorldPacket& recvPacket) SendPacket(packet); sLog->outError("WorldSocket::HandleAuthSession: Sent Auth Response (Account banned)."); + sScriptMgr->OnFailedAccountLogin(id); return -1; } @@ -900,6 +919,7 @@ int WorldSocket::HandleAuthSession(WorldPacket& recvPacket) #if defined(ENABLE_EXTRAS) && defined(ENABLE_EXTRA_LOGS) sLog->outDetail("WorldSocket::HandleAuthSession: User tries to login but his security level is not enough"); #endif + sScriptMgr->OnFailedAccountLogin(id); return -1; } @@ -914,8 +934,6 @@ int WorldSocket::HandleAuthSession(WorldPacket& recvPacket) sha.UpdateBigNumbers (&k, NULL); sha.Finalize(); - std::string address = GetRemoteAddress(); - if (memcmp (sha.GetDigest(), digest, 20)) { packet.Initialize (SMSG_AUTH_RESPONSE, 1); @@ -943,8 +961,7 @@ int WorldSocket::HandleAuthSession(WorldPacket& recvPacket) if (result) isRecruiter = true; - // Update the last_ip in the database - + // Update the last_ip in the database as it was successful for login stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LAST_IP); stmt->setString(0, address); @@ -965,6 +982,7 @@ int WorldSocket::HandleAuthSession(WorldPacket& recvPacket) SendPacket(packet); sLog->outError("WorldSocket::HandleAuthSession: World closed, denying client (%s).", address.c_str()); + sScriptMgr->OnFailedAccountLogin(id); return -1; } @@ -976,6 +994,7 @@ int WorldSocket::HandleAuthSession(WorldPacket& recvPacket) sLog->outError("WorldSocket::HandleAuthSession: Client %s requested connecting with realm id %u but this realm has id %u set in config.", address.c_str(), realm, realmID); + sScriptMgr->OnFailedAccountLogin(id); return -1; } @@ -983,8 +1002,11 @@ int WorldSocket::HandleAuthSession(WorldPacket& recvPacket) m_Session->LoadTutorialsData(); m_Session->ReadAddonsInfo(recvPacket); + // At this point, we can safely hook a successful login + sScriptMgr->OnAccountLogin(id); + // Initialize Warden system only if it is enabled by config - if (sWorld->getBoolConfig(CONFIG_WARDEN_ENABLED)) + if (wardenActive) m_Session->InitWarden(&k, os); // Sleep this Network thread for @@ -996,7 +1018,7 @@ int WorldSocket::HandleAuthSession(WorldPacket& recvPacket) return 0; } -int WorldSocket::HandlePing (WorldPacket& recvPacket) +int WorldSocket::HandlePing(WorldPacket& recvPacket) { uint32 ping; uint32 latency; @@ -1022,7 +1044,7 @@ int WorldSocket::HandlePing (WorldPacket& recvPacket) if (max_count && m_OverSpeedPings > max_count) { - ACE_GUARD_RETURN (LockType, Guard, m_SessionLock, -1); + ACE_GUARD_RETURN(LockType, Guard, m_SessionLock, -1); if (m_Session && AccountMgr::IsPlayerAccount(m_Session->GetSecurity())) { @@ -1043,7 +1065,7 @@ int WorldSocket::HandlePing (WorldPacket& recvPacket) // critical section { - ACE_GUARD_RETURN (LockType, Guard, m_SessionLock, -1); + ACE_GUARD_RETURN(LockType, Guard, m_SessionLock, -1); if (m_Session) { diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index f90cdfd3b..ede71d2eb 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -1296,6 +1296,8 @@ void World::LoadConfigSettings(bool reload) m_bool_configs[CONFIG_ENABLE_CONTINENT_TRANSPORT] = sConfigMgr->GetBoolDefault("IsContinentTransport.Enabled", true); m_bool_configs[CONFIG_ENABLE_CONTINENT_TRANSPORT_PRELOADING] = sConfigMgr->GetBoolDefault("IsPreloadedContinentTransport.Enabled", false); + m_bool_configs[CONFIG_IP_BASED_ACTION_LOGGING] = sConfigMgr->GetBoolDefault("Allow.IP.Based.Action.Logging", false); + m_bool_configs[CONFIG_CALCULATE_CREATURE_ZONE_AREA_DATA] = sConfigMgr->GetBoolDefault("Calculate.Creature.Zone.Area.Data", false); m_bool_configs[CONFIG_CALCULATE_GAMEOBJECT_ZONE_AREA_DATA] = sConfigMgr->GetBoolDefault("Calculate.Gameoject.Zone.Area.Data", false); diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h index 8bb131f3f..c4ba65335 100644 --- a/src/server/game/World/World.h +++ b/src/server/game/World/World.h @@ -162,6 +162,7 @@ enum WorldBoolConfigs CONFIG_ENABLE_CONTINENT_TRANSPORT, CONFIG_ENABLE_CONTINENT_TRANSPORT_PRELOADING, CONFIG_MINIGOB_MANABONK, + CONFIG_IP_BASED_ACTION_LOGGING, CONFIG_CALCULATE_CREATURE_ZONE_AREA_DATA, CONFIG_CALCULATE_GAMEOBJECT_ZONE_AREA_DATA, BOOL_CONFIG_VALUE_COUNT diff --git a/src/server/scripts/Commands/cs_account.cpp b/src/server/scripts/Commands/cs_account.cpp index 80303f028..fb8e07f15 100644 --- a/src/server/scripts/Commands/cs_account.cpp +++ b/src/server/scripts/Commands/cs_account.cpp @@ -347,6 +347,7 @@ public: if (!AccountMgr::CheckPassword(handler->GetSession()->GetAccountId(), std::string(oldPassword))) { handler->SendSysMessage(LANG_COMMAND_WRONGOLDPASSWORD); + sScriptMgr->OnFailedPasswordChange(handler->GetSession()->GetAccountId()); handler->SetSentErrorMessage(true); return false; } @@ -354,6 +355,7 @@ public: if (strcmp(newPassword, passwordConfirmation) != 0) { handler->SendSysMessage(LANG_NEW_PASSWORDS_NOT_MATCH); + sScriptMgr->OnFailedPasswordChange(handler->GetSession()->GetAccountId()); handler->SetSentErrorMessage(true); return false; } @@ -363,9 +365,11 @@ public: { case AOR_OK: handler->SendSysMessage(LANG_COMMAND_PASSWORD); + sScriptMgr->OnPasswordChange(handler->GetSession()->GetAccountId()); break; case AOR_PASS_TOO_LONG: handler->SendSysMessage(LANG_PASSWORD_TOO_LONG); + sScriptMgr->OnFailedPasswordChange(handler->GetSession()->GetAccountId()); handler->SetSentErrorMessage(true); return false; default: diff --git a/src/server/scripts/ScriptLoader.cpp b/src/server/scripts/ScriptLoader.cpp index d6b5fb573..8c4195ab2 100644 --- a/src/server/scripts/ScriptLoader.cpp +++ b/src/server/scripts/ScriptLoader.cpp @@ -6,6 +6,7 @@ #include "ScriptLoader.h" #include "ScriptMgr.h" +#include "World.h" // spells void AddSC_deathknight_spell_scripts(); @@ -73,6 +74,7 @@ void AddSC_npc_innkeeper(); void AddSC_npcs_special(); void AddSC_npc_taxi(); void AddSC_achievement_scripts(); +void AddSC_action_ip_logger(); //events void AddSC_event_brewfest_scripts(); @@ -577,6 +579,7 @@ void AddSC_outdoorpvp_gh(); // player void AddSC_chat_log(); void AddSC_character_creation(); +void AddSC_action_ip_logger(); #endif @@ -650,8 +653,9 @@ void AddWorldScripts() AddSC_npcs_special(); AddSC_npc_taxi(); AddSC_achievement_scripts(); - AddSC_chat_log(); + AddSC_chat_log(); // location: scripts\World\chat_log.cpp AddSC_character_creation(); + AddSC_action_ip_logger(); // location: scripts\World\action_ip_logger.cpp #endif } diff --git a/src/server/scripts/World/CMakeLists.txt b/src/server/scripts/World/CMakeLists.txt index 42ba3facc..54de11e73 100644 --- a/src/server/scripts/World/CMakeLists.txt +++ b/src/server/scripts/World/CMakeLists.txt @@ -23,6 +23,7 @@ set(scripts_STAT_SRCS ${AC_SCRIPTS_DIR}/World/npc_taxi.cpp ${AC_SCRIPTS_DIR}/World/npcs_special.cpp ${AC_SCRIPTS_DIR}/World/character_creation.cpp + ${AC_SCRIPTS_DIR}/World/action_ip_logger.cpp ) AC_ADD_SCRIPT_LOADER("World" "ScriptLoader.h") diff --git a/src/server/scripts/World/action_ip_logger.cpp b/src/server/scripts/World/action_ip_logger.cpp new file mode 100644 index 000000000..ab95c8732 --- /dev/null +++ b/src/server/scripts/World/action_ip_logger.cpp @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2016+ AzerothCore + * Copyright (C) 2008-2016 TrinityCore + */ + +#include "ScriptMgr.h" +#include "Channel.h" +#include "Guild.h" +#include "Group.h" + +enum IPLoggingTypes +{ + + // AccountActionIpLogger(); + ACCOUNT_LOGIN = 0, + ACCOUNT_FAIL_LOGIN = 1, + ACCOUNT_CHANGE_PW = 2, + ACCOUNT_CHANGE_PW_FAIL = 3, // Only two types of account changes exist... + ACCOUNT_CHANGE_EMAIL = 4, + ACCOUNT_CHANGE_EMAIL_FAIL = 5, // ...so we log them individually + // OBSOLETE - ACCOUNT_LOGOUT = 6, /* Can not be logged. We still keep the type however */ + // CharacterActionIpLogger(); + CHARACTER_CREATE = 7, + CHARACTER_LOGIN = 8, + CHARACTER_LOGOUT = 9, + // CharacterDeleteActionIpLogger(); + CHARACTER_DELETE = 10, + CHARACTER_FAILED_DELETE = 11, + // AccountActionIpLogger(), CharacterActionIpLogger(), CharacterActionIpLogger(); + UNKNOWN_ACTION = 12 +}; + +class AccountActionIpLogger : public AccountScript +{ +public: + AccountActionIpLogger() : AccountScript("AccountActionIpLogger") { } + + // We log last_ip instead of last_attempt_ip, as login was successful + // ACCOUNT_LOGIN = 0 + void OnAccountLogin(uint32 accountId) override + { + AccountIPLogAction(accountId, ACCOUNT_LOGIN); + } + + // We log last_attempt_ip instead of last_ip, as failed login doesn't necessarily mean approperiate user + // ACCOUNT_FAIL_LOGIN = 1 + void OnFailedAccountLogin(uint32 accountId) override + { + AccountIPLogAction(accountId, ACCOUNT_FAIL_LOGIN); + } + + // ACCOUNT_CHANGE_PW = 2 + void OnPasswordChange(uint32 accountId) override + { + AccountIPLogAction(accountId, ACCOUNT_CHANGE_PW); + } + + // ACCOUNT_CHANGE_PW_FAIL = 3 + void OnFailedPasswordChange(uint32 accountId) override + { + AccountIPLogAction(accountId, ACCOUNT_CHANGE_PW_FAIL); + } + + // Registration Email can NOT be changed apart from GM level users. Thus, we do not require to log them... + // ACCOUNT_CHANGE_EMAIL = 4 + void OnEmailChange(uint32 accountId) override + { + AccountIPLogAction(accountId, ACCOUNT_CHANGE_EMAIL); // ... they get logged by gm command logger anyway + } + + // ACCOUNT_CHANGE_EMAIL_FAIL = 5 + void OnFailedEmailChange(uint32 accountId) override + { + AccountIPLogAction(accountId, ACCOUNT_CHANGE_EMAIL_FAIL); + } + + /* It's impossible to log the account logout process out of character selection - shouldn't matter anyway, + * as ip doesn't change through playing (obviously).*/ + // ACCOUNT_LOGOUT = 6 + void AccountIPLogAction(uint32 accountId, IPLoggingTypes aType) + { + if (!sWorld->getBoolConfig(CONFIG_IP_BASED_ACTION_LOGGING)) + return; + + // Action IP Logger is only intialized if config is set up + // Else, this script isn't loaded in the first place: We require no config check. + + // We declare all the required variables + uint32 playerGuid = accountId; + uint32 characterGuid = 0; + std::string systemNote = "ERROR"; // "ERROR" is a placeholder here. We change it later. + + // With this switch, we change systemNote so that we have a more accurate phrasing of what type it is. + // Avoids Magicnumbers in SQL table + switch (aType) + { + case ACCOUNT_LOGIN: + systemNote = "Logged on Successful AccountLogin"; + break; + case ACCOUNT_FAIL_LOGIN: + systemNote = "Logged on Failed AccountLogin"; + break; + case ACCOUNT_CHANGE_PW: + systemNote = "Logged on Successful Account Password Change"; + break; + case ACCOUNT_CHANGE_PW_FAIL: + systemNote = "Logged on Failed Account Password Change"; + break; + case ACCOUNT_CHANGE_EMAIL: + systemNote = "Logged on Successful Account Email Change"; + break; + case ACCOUNT_CHANGE_EMAIL_FAIL: + systemNote = "Logged on Failed Account Email Change"; + break; + /*case ACCOUNT_LOGOUT: + systemNote = "Logged on AccountLogout"; //Can not be logged + break;*/ + // Neither should happen. Ever. Period. If it does, call Ghostbusters and all your local software defences to investigate. + case UNKNOWN_ACTION: + default: + systemNote = "ERROR! Unknown action!"; + break; + } + + // Once we have done everything, we can insert the new log. + // Seeing as the time differences should be minimal, we do not get unixtime and the timestamp right now; + // Rather, we let it be added with the SQL query. + if (aType != ACCOUNT_FAIL_LOGIN) + { + // As we can assume most account actions are NOT failed login, so this is the more accurate check. + // For those, we need last_ip... + PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_ALDL_IP_LOGGING); + + stmt->setUInt32(0, playerGuid); + stmt->setUInt32(1, characterGuid); + stmt->setUInt8(2, aType); + stmt->setUInt32(3, playerGuid); + stmt->setString(4, systemNote.c_str()); + LoginDatabase.Execute(stmt); + } + else // ... but for failed login, we query last_attempt_ip from account table. Which we do with an unique query + { + PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_FACL_IP_LOGGING); + + stmt->setUInt32(0, playerGuid); + stmt->setUInt32(1, characterGuid); + stmt->setUInt8(2, aType); + stmt->setUInt32(3, playerGuid); + stmt->setString(4, systemNote.c_str()); + LoginDatabase.Execute(stmt); + } + return; + } +}; + +class CharacterActionIpLogger : public PlayerScript +{ +public: + CharacterActionIpLogger() : PlayerScript("CharacterActionIpLogger") { } + + // CHARACTER_CREATE = 7 + void OnCreate(Player* player) override + { + CharacterIPLogAction(player, CHARACTER_CREATE); + } + + // CHARACTER_LOGIN = 8 + void OnLogin(Player* player) override + { + CharacterIPLogAction(player, CHARACTER_LOGIN); + } + + // CHARACTER_LOGOUT = 9 + void OnLogout(Player* player) override + { + CharacterIPLogAction(player, CHARACTER_LOGOUT); + } + + // CHARACTER_DELETE = 10 + // CHARACTER_FAILED_DELETE = 11 + // We don't log either here - they require a guid + + // UNKNOWN_ACTION = 12 + // There is no real hook we could use for that. + // Shouldn't happen anyway, should it ? Nothing to see here. + + /// Logs a number of actions done by players with an IP + void CharacterIPLogAction(Player* player, IPLoggingTypes aType) + { + if (!sWorld->getBoolConfig(CONFIG_IP_BASED_ACTION_LOGGING)) + return; + + // Action IP Logger is only intialized if config is set up + // Else, this script isn't loaded in the first place: We require no config check. + + // We declare all the required variables + uint32 playerGuid = player->GetSession()->GetAccountId(); + uint32 characterGuid = player->GetGUIDLow(); + const std::string currentIp = player->GetSession()->GetRemoteAddress(); + std::string systemNote = "ERROR"; // "ERROR" is a placeholder here. We change it... + + // ... with this switch, so that we have a more accurate phrasing of what type it is + switch (aType) + { + case CHARACTER_CREATE: + systemNote = "Logged on CharacterCreate"; + break; + case CHARACTER_LOGIN: + systemNote = "Logged on CharacterLogin"; + break; + case CHARACTER_LOGOUT: + systemNote = "Logged on CharacterLogout"; + break; + case CHARACTER_DELETE: + systemNote = "Logged on CharacterDelete"; + break; + case CHARACTER_FAILED_DELETE: + systemNote = "Logged on Failed CharacterDelete"; + break; + // Neither should happen. Ever. Period. If it does, call Mythbusters. + case UNKNOWN_ACTION: + default: + systemNote = "ERROR! Unknown action!"; + break; + } + + // Once we have done everything, we can insert the new log. + PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_CHAR_IP_LOGGING); + + stmt->setUInt32(0, playerGuid); + stmt->setUInt32(1, characterGuid); + stmt->setUInt8(2, aType); + stmt->setString(3, currentIp.c_str()); // We query the ip here. + stmt->setString(4, systemNote.c_str()); + // Seeing as the time differences should be minimal, we do not get unixtime and the timestamp right now; + // Rather, we let it be added with the SQL query. + + LoginDatabase.Execute(stmt); + return; + } +}; + +class CharacterDeleteActionIpLogger : public PlayerScript +{ +public: + CharacterDeleteActionIpLogger() : PlayerScript("CharacterDeleteActionIpLogger") { } + + // CHARACTER_DELETE = 10 + void OnDelete(uint64 guid, uint32 accountId) override + { + DeleteIPLogAction(guid, accountId, CHARACTER_DELETE); + } + + // CHARACTER_FAILED_DELETE = 11 + void OnFailedDelete(uint64 guid, uint32 accountId) override + { + DeleteIPLogAction(guid, accountId, CHARACTER_FAILED_DELETE); + } + + void DeleteIPLogAction(uint64 guid, uint32 playerGuid, IPLoggingTypes aType) + { + if (!sWorld->getBoolConfig(CONFIG_IP_BASED_ACTION_LOGGING)) + return; + + // Action IP Logger is only intialized if config is set up + // Else, this script isn't loaded in the first place: We require no config check. + + // We declare all the required variables + uint32 characterGuid = GUID_LOPART(guid); // We have no access to any member function of Player* or WorldSession*. So use old-fashioned way. + // Query playerGuid/accountId, as we only have characterGuid + std::string systemNote = "ERROR"; // "ERROR" is a placeholder here. We change it later. + + // With this switch, we change systemNote so that we have a more accurate phrasing of what type it is. + // Avoids Magicnumbers in SQL table + switch (aType) + { + case CHARACTER_DELETE: + systemNote = "Logged on CharacterDelete"; + break; + case CHARACTER_FAILED_DELETE: + systemNote = "Logged on Failed CharacterDelete"; + break; + // Neither should happen. Ever. Period. If it does, call to whatever god you have for mercy and guidance. + case UNKNOWN_ACTION: + default: + systemNote = "ERROR! Unknown action!"; + break; + } + + // Once we have done everything, we can insert the new log. + PreparedStatement* stmt2 = LoginDatabase.GetPreparedStatement(LOGIN_INS_ALDL_IP_LOGGING); + + stmt2->setUInt32(0, playerGuid); + stmt2->setUInt32(1, characterGuid); + stmt2->setUInt8(2, aType); + stmt2->setUInt32(3, playerGuid); + stmt2->setString(4, systemNote.c_str()); + // Seeing as the time differences should be minimal, we do not get unixtime and the timestamp right now; + // Rather, we let it be added with the SQL query. + + LoginDatabase.Execute(stmt2); + return; + } +}; + + +void AddSC_action_ip_logger() +{ + new AccountActionIpLogger(); + new CharacterActionIpLogger(); + new CharacterDeleteActionIpLogger(); +} diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist index 7bbfbc9aa..3f6c5164e 100644 --- a/src/server/worldserver/worldserver.conf.dist +++ b/src/server/worldserver/worldserver.conf.dist @@ -3174,6 +3174,14 @@ MoveMaps.Enable = 1 Minigob.Manabonk.Enable = 1 # + +# Allow.IP.Based.Action.Logging +# Description: Logs actions, e.g. account login and logout to name a few, based on IP of current session. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Allow.IP.Based.Action.Logging = 0 + # Calculate.Creature.Zone.Area.Data # Description: Calculate at loading creature zoneId / areaId and save in creature table (WARNING: SLOW WORLD SERVER STARTUP) # Default: 0 - (Do not show)