#include "stdafx.h" #include "constants.h" #include "config.h" #include "input.h" #include "desc_client.h" #include "desc_manager.h" #include "protocol.h" #include "locale_service.h" #include "db.h" #include extern time_t get_global_time(); static const int LOGIN_MAX_ATTEMPTS = 5; static const DWORD LOGIN_BLOCK_DURATION_MS = 60000; struct LoginAttemptInfo { int failCount; DWORD lastAttemptTime; }; static std::unordered_map s_loginAttempts; void RecordLoginFailure(const char* hostName) { DWORD now = get_dword_time(); auto it = s_loginAttempts.find(hostName); if (it == s_loginAttempts.end()) { s_loginAttempts[hostName] = { 1, now }; } else { if (now - it->second.lastAttemptTime >= LOGIN_BLOCK_DURATION_MS) it->second.failCount = 0; it->second.failCount++; it->second.lastAttemptTime = now; } } void ClearLoginFailure(const char* hostName) { s_loginAttempts.erase(hostName); } static bool IsLoginRateLimited(const char* hostName) { auto it = s_loginAttempts.find(hostName); if (it == s_loginAttempts.end()) return false; DWORD now = get_dword_time(); if (now - it->second.lastAttemptTime >= LOGIN_BLOCK_DURATION_MS) { s_loginAttempts.erase(it); return false; } return it->second.failCount >= LOGIN_MAX_ATTEMPTS; } bool FN_IS_VALID_LOGIN_STRING(const char *str) { const char* tmp; if (!str || !*str) return false; if (strlen(str) < 2) return false; for (tmp = str; *tmp; ++tmp) { // 알파벳과 수자만 허용 if (isdigit(*tmp) || isalpha(*tmp)) continue; // 캐나다는 몇몇 특수문자 허용 if (LC_IsCanada()) { switch (*tmp) { case ' ': case '_': case '-': case '.': case '!': case '@': case '#': case '$': case '%': case '^': case '&': case '*': case '(': case ')': continue; } } if (LC_IsYMIR() == true || LC_IsKorea() == true) { switch (*tmp) { case '-' : case '_' : continue; } } if (LC_IsBrazil() == true) { switch (*tmp) { case '_' : case '-' : case '=' : continue; } } if (LC_IsJapan() == true) { switch (*tmp) { case '-' : case '_' : case '@': case '#': continue; } } return false; } return true; } CInputAuth::CInputAuth() { RegisterHandlers(); } int CInputAuth::HandlePong(LPDESC d, const char*) { Pong(d); return 0; } int CInputAuth::HandleLogin3(LPDESC d, const char* c_pData) { Login(d, c_pData); return 0; } void CInputAuth::RegisterHandlers() { m_handlers[CG::PONG] = &CInputAuth::HandlePong; m_handlers[CG::LOGIN3] = &CInputAuth::HandleLogin3; } void CInputAuth::Login(LPDESC d, const char * c_pData) { TPacketCGLogin3 * pinfo = (TPacketCGLogin3 *) c_pData; if (!g_bAuthServer) { sys_err ("CInputAuth class is not for game server. IP %s might be a hacker.", inet_ntoa(d->GetAddr().sin_addr)); d->DelayedDisconnect(5); return; } // Rate limiting: block IPs with too many failed attempts if (IsLoginRateLimited(d->GetHostName())) { sys_log(0, "InputAuth::Login : RATE LIMITED IP %s", d->GetHostName()); LoginFailure(d, "BLOCK"); d->DelayedDisconnect(3); return; } // string 무결성을 위해 복사 char login[LOGIN_MAX_LEN + 1]; trim_and_lower(pinfo->login, login, sizeof(login)); char passwd[PASSWD_MAX_LEN + 1]; strlcpy(passwd, pinfo->passwd, sizeof(passwd)); sys_log(0, "InputAuth::Login : %s(%d) desc %p", login, strlen(login), get_pointer(d)); // check login string if (false == FN_IS_VALID_LOGIN_STRING(login)) { sys_log(0, "InputAuth::Login : IS_NOT_VALID_LOGIN_STRING(%s) desc %p", login, get_pointer(d)); LoginFailure(d, "NOID"); return; } if (g_bNoMoreClient) { TPacketGCLoginFailure failurePacket; failurePacket.header = GC::LOGIN_FAILURE; failurePacket.length = sizeof(failurePacket); strlcpy(failurePacket.szStatus, "SHUTDOWN", sizeof(failurePacket.szStatus)); d->Packet(&failurePacket, sizeof(failurePacket)); return; } if (DESC_MANAGER::instance().FindByLoginName(login)) { LoginFailure(d, "ALREADY"); return; } DWORD dwKey = DESC_MANAGER::instance().CreateLoginKey(d); sys_log(0, "InputAuth::Login : key %u login %s", dwKey, login); DBManager::instance().AuthenticateLogin(d, login, passwd); } int CInputAuth::Analyze(LPDESC d, uint16_t wHeader, const char * c_pData) { if (!g_bAuthServer) { sys_err ("CInputAuth class is not for game server. IP %s might be a hacker.", inet_ntoa(d->GetAddr().sin_addr)); d->DelayedDisconnect(5); return 0; } auto it = m_handlers.find(wHeader); if (it == m_handlers.end()) { sys_err("This phase does not handle this header %d (0x%x)(phase: AUTH)", wHeader, wHeader); return 0; } return (this->*(it->second))(d, c_pData); }