Files
m2dev-server-src/src/game/input_auth.cpp
server c5cac17125
Some checks failed
build / Linux asan (push) Has been cancelled
build / Linux release (push) Has been cancelled
build / FreeBSD build (push) Has been cancelled
game: prepare auth login query
2026-04-14 10:58:11 +02:00

251 lines
4.7 KiB
C++

#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 <unordered_map>
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<std::string, LoginAttemptInfo> 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);
}