eqemu-server/world/login_server.cpp
Alex King dc261bb203
[Code] ZSList Global to Singleton Cleanup (#4940)
* [Code] ZSList Global to Singleton Cleanup

* Final

* Post merge fixes

---------

Co-authored-by: Chris Miles <akkadius1@gmail.com>
2025-06-25 15:16:44 -05:00

692 lines
17 KiB
C++

#include "../common/global_define.h"
#include <iostream>
#include <string.h>
#include <stdio.h>
#include <iomanip>
#include <stdlib.h>
#include "../common/version.h"
#include "../common/servertalk.h"
#include "../common/misc_functions.h"
#include "../common/eq_packet_structs.h"
#include "../common/packet_dump.h"
#include "../common/strings.h"
#include "../common/eqemu_logsys.h"
#include "login_server.h"
#include "login_server_list.h"
#include "zoneserver.h"
#include "worlddb.h"
#include "zonelist.h"
#include "clientlist.h"
#include "cliententry.h"
#include "world_config.h"
extern ClientList client_list;
extern uint32 numzones;
extern uint32 numplayers;
extern volatile bool RunLoops;
LoginServer::LoginServer(const char *iAddress, uint16 iPort, const char *Account, const char *Password, bool legacy)
{
strn0cpy(m_loginserver_address, iAddress, 256);
m_loginserver_port = iPort;
m_login_account = Account;
m_login_password = Password;
m_can_account_update = false;
m_is_legacy = legacy;
Connect();
}
LoginServer::~LoginServer()
{
}
void LoginServer::ProcessUsertoWorldReqLeg(uint16_t opcode, EQ::Net::Packet &p)
{
const WorldConfig *Config = WorldConfig::get();
LogNetcode("Received ServerPacket from LS OpCode {:#04x}", opcode);
UsertoWorldRequestLegacy *utwr = (UsertoWorldRequestLegacy *) p.Data();
uint32 id = database.GetAccountIDFromLSID("eqemu", utwr->lsaccountid);
int16 status = database.GetAccountStatus(id);
LogDebug(
"id [{}] status [{}] account_id [{}] world_id [{}] from_id [{}] to_id [{}] ip [{}]",
id,
status,
utwr->lsaccountid,
utwr->worldid,
utwr->FromID,
utwr->ToID,
utwr->IPAddr
);
ServerPacket outpack;
outpack.opcode = ServerOP_UsertoWorldRespLeg;
outpack.size = sizeof(UsertoWorldResponseLegacy);
outpack.pBuffer = new uchar[outpack.size];
memset(outpack.pBuffer, 0, outpack.size);
UsertoWorldResponseLegacy *utwrs = (UsertoWorldResponseLegacy *) outpack.pBuffer;
utwrs->lsaccountid = utwr->lsaccountid;
utwrs->ToID = utwr->FromID;
utwrs->worldid = utwr->worldid;
utwrs->response = UserToWorldStatusSuccess;
if (Config->Locked) {
if (status < (RuleI(GM, MinStatusToBypassLockedServer))) {
LogDebug(
"Server locked and status is not high enough for account_id [{0}]",
utwr->lsaccountid
);
utwrs->response = UserToWorldStatusWorldUnavail;
SendPacket(&outpack);
return;
}
}
int32 x = Config->MaxClients;
if ((int32) numplayers >= x && x != -1 && x != 255 && status < (RuleI(GM, MinStatusToBypassLockedServer))) {
LogDebug("World at capacity account_id [{0}]", utwr->lsaccountid);
utwrs->response = UserToWorldStatusWorldAtCapacity;
SendPacket(&outpack);
return;
}
if (status == -1) {
LogDebug("User suspended account_id [{0}]", utwr->lsaccountid);
utwrs->response = UserToWorldStatusSuspended;
SendPacket(&outpack);
return;
}
if (status == -2) {
LogDebug("User banned account_id [{0}]", utwr->lsaccountid);
utwrs->response = UserToWorldStatusBanned;
SendPacket(&outpack);
return;
}
if (RuleB(World, EnforceCharacterLimitAtLogin)) {
if (ClientList::Instance()->IsAccountInGame(utwr->lsaccountid)) {
LogDebug("User already online account_id [{0}]", utwr->lsaccountid);
utwrs->response = UserToWorldStatusAlreadyOnline;
SendPacket(&outpack);
return;
}
}
LogDebug("Sent response to account_id [{0}]", utwr->lsaccountid);
SendPacket(&outpack);
}
void LoginServer::ProcessUsertoWorldReq(uint16_t opcode, EQ::Net::Packet &p)
{
const WorldConfig *Config = WorldConfig::get();
LogNetcode("Received ServerPacket from LS OpCode {:#04x}", opcode);
UsertoWorldRequest *utwr = (UsertoWorldRequest *) p.Data();
uint32 id = database.GetAccountIDFromLSID(utwr->login, utwr->lsaccountid);
int16 status = database.GetAccountStatus(id);
LogDebug(
"id [{}] status [{}] account_id [{}] world_id [{}] from_id [{}] to_id [{}] ip [{}]",
id,
status,
utwr->lsaccountid,
utwr->worldid,
utwr->FromID,
utwr->ToID,
utwr->IPAddr
);
ServerPacket outpack;
outpack.opcode = ServerOP_UsertoWorldResp;
outpack.size = sizeof(UsertoWorldResponse);
outpack.pBuffer = new uchar[outpack.size];
memset(outpack.pBuffer, 0, outpack.size);
UsertoWorldResponse *utwrs = (UsertoWorldResponse *) outpack.pBuffer;
utwrs->lsaccountid = utwr->lsaccountid;
utwrs->ToID = utwr->FromID;
strn0cpy(utwrs->login, utwr->login, 64);
utwrs->worldid = utwr->worldid;
utwrs->response = UserToWorldStatusSuccess;
if (Config->Locked == true) {
if (status < (RuleI(GM, MinStatusToBypassLockedServer))) {
LogDebug(
"Server locked and status is not high enough for account_id [{0}]",
utwr->lsaccountid
);
utwrs->response = UserToWorldStatusWorldUnavail;
SendPacket(&outpack);
return;
}
}
int32 x = Config->MaxClients;
if ((int32) numplayers >= x && x != -1 && x != 255 && status < (RuleI(GM, MinStatusToBypassLockedServer))) {
LogDebug("World at capacity account_id [{0}]", utwr->lsaccountid);
utwrs->response = UserToWorldStatusWorldAtCapacity;
SendPacket(&outpack);
return;
}
if (status == -1) {
LogDebug("User suspended account_id [{0}]", utwr->lsaccountid);
utwrs->response = UserToWorldStatusSuspended;
SendPacket(&outpack);
return;
}
if (status == -2) {
LogDebug("User banned account_id [{0}]", utwr->lsaccountid);
utwrs->response = UserToWorldStatusBanned;
SendPacket(&outpack);
return;
}
if (RuleB(World, EnforceCharacterLimitAtLogin)) {
if (ClientList::Instance()->IsAccountInGame(utwr->lsaccountid)) {
LogDebug("User already online account_id [{0}]", utwr->lsaccountid);
utwrs->response = UserToWorldStatusAlreadyOnline;
SendPacket(&outpack);
return;
}
}
LogDebug("Sent response to account_id [{0}]", utwr->lsaccountid);
SendPacket(&outpack);
}
void LoginServer::ProcessLSClientAuthLegacy(uint16_t opcode, EQ::Net::Packet &p)
{
const WorldConfig *Config = WorldConfig::get();
LogNetcode("Received ServerPacket from LS OpCode {:#04x}", opcode);
try {
auto r = p.GetSerialize<ClientAuthLegacy>(0);
LogDebug(
"Processing Loginserver Auth Legacy | account_id [{}] account_name [{}] key [{}] admin [{}] ip [{}] "
"local_network [{}]",
r.loginserver_account_id,
r.loginserver_account_name,
r.key,
r.is_world_admin,
r.ip_address,
r.is_client_from_local_network
);
ClientList::Instance()->CLEAdd(
r.loginserver_account_id,
"eqemu",
r.loginserver_account_name,
r.key,
r.is_world_admin,
r.ip_address,
r.is_client_from_local_network
);
}
catch (std::exception &ex) {
LogError("Error parsing ClientAuthLegacy packet from world\nReason [{0}]", ex.what());
}
}
void LoginServer::ProcessLSClientAuth(uint16_t opcode, EQ::Net::Packet &p)
{
const WorldConfig *Config = WorldConfig::get();
LogNetcode("Received ServerPacket from LS OpCode {:#04x}", opcode);
try {
auto r = p.GetSerialize<ClientAuth>(0);
LogDebug(
"Processing Loginserver Auth | account_id [{}] account_name [{}] loginserver_name [{}] key [{}] "
"admin [{}] ip [{}] local_network [{}]",
r.loginserver_account_id,
r.account_name,
r.loginserver_name,
r.key,
r.is_world_admin,
r.ip_address,
r.is_client_from_local_network
);
ClientList::Instance()->CLEAdd(
r.loginserver_account_id,
r.loginserver_name,
r.account_name,
r.key,
r.is_world_admin,
r.ip_address,
r.is_client_from_local_network
);
}
catch (std::exception &ex) {
LogError("Error parsing ClientAuth packet from world\nReason [{0}]", ex.what());
}
}
void LoginServer::ProcessLSFatalError(uint16_t opcode, EQ::Net::Packet &p)
{
const WorldConfig *Config = WorldConfig::get();
LogNetcode("Received ServerPacket from LS OpCode {:#04x}", opcode);
std::string error;
std::string reason;
if (p.Length() > 1) {
error = fmt::format("{}", (const char *) p.Data());
}
if (error.find("Worldserver Account / Password INVALID") != std::string::npos) {
reason = "Usually this indicates you do not have a valid [account] and [password] (worldserver) account associated with your loginserver configuration. ";
if (fmt::format("{}", m_loginserver_address).find("login.eqemulator.net") != std::string::npos) {
reason += "For Legacy EQEmulator connections, you need to register your server @ https://www.eqemulator.org/index.php?pageid=ws_mgmt";
}
}
LogInfo(
"Login server [{}:{}] responded with fatal error [{}] {}\n",
m_loginserver_address,
m_loginserver_port,
error,
reason
);
if (m_legacy_client) {
m_legacy_client.release();
}
else if (m_client) {
m_client.release();
}
}
void LoginServer::ProcessSystemwideMessage(uint16_t opcode, EQ::Net::Packet &p)
{
const WorldConfig *Config = WorldConfig::get();
LogNetcode("Received ServerPacket from LS OpCode {:#04x}", opcode);
ServerSystemwideMessage *swm = (ServerSystemwideMessage *) p.Data();
ZSList::Instance()->SendEmoteMessageRaw(
0,
0,
AccountStatus::Player,
swm->type,
swm->message
);
}
void LoginServer::ProcessLSRemoteAddr(uint16_t opcode, EQ::Net::Packet &p)
{
const WorldConfig *Config = WorldConfig::get();
LogNetcode("Received ServerPacket from LS OpCode {:#04x}", opcode);
if (!Config->WorldAddress.length()) {
WorldConfig::SetWorldAddress((char *) p.Data());
LogInfo("Loginserver provided [{}] as world address", (const char *) p.Data());
}
}
void LoginServer::ProcessLSAccountUpdate(uint16_t opcode, EQ::Net::Packet &p)
{
const WorldConfig *Config = WorldConfig::get();
LogNetcode("Received ServerPacket from LS OpCode {:#04x}", opcode);
LogNetcode("Received ServerOP_LSAccountUpdate packet from loginserver");
m_can_account_update = true;
}
bool LoginServer::Connect()
{
char errbuf[1024];
if ((m_loginserver_ip = ResolveIP(m_loginserver_address, errbuf)) == 0) {
LogInfo("Unable to resolve [{}] to an IP", m_loginserver_address);
return false;
}
if (m_loginserver_ip == 0 || m_loginserver_port == 0) {
LogInfo(
"Connect info incomplete, cannot connect: [{0}:{1}]",
m_loginserver_address,
m_loginserver_port
);
return false;
}
if (m_is_legacy) {
m_legacy_client = std::make_unique<EQ::Net::ServertalkLegacyClient>(
m_loginserver_address,
m_loginserver_port,
false
);
m_legacy_client->OnConnect(
[this](EQ::Net::ServertalkLegacyClient *client) {
if (client) {
LogInfo(
"Connected to Legacy Loginserver: [{0}:{1}]",
m_loginserver_address,
m_loginserver_port
);
SendInfo();
SendStatus();
ZSList::Instance()->SendLSZones();
m_statusupdate_timer = std::make_unique<EQ::Timer>(
LoginServer_StatusUpdateInterval, true, [this](EQ::Timer *t) {
SendStatus();
}
);
}
else {
LogInfo(
"Could not connect to Legacy Loginserver: [{0}:{1}]",
m_loginserver_address,
m_loginserver_port
);
}
}
);
m_legacy_client->OnMessage(
ServerOP_UsertoWorldReqLeg,
std::bind(
&LoginServer::ProcessUsertoWorldReqLeg,
this,
std::placeholders::_1,
std::placeholders::_2
)
);
m_legacy_client->OnMessage(
ServerOP_UsertoWorldReq,
std::bind(
&LoginServer::ProcessUsertoWorldReq,
this,
std::placeholders::_1,
std::placeholders::_2
)
);
m_legacy_client->OnMessage(
ServerOP_LSClientAuthLeg,
std::bind(
&LoginServer::ProcessLSClientAuthLegacy,
this,
std::placeholders::_1,
std::placeholders::_2
)
);
m_legacy_client->OnMessage(
ServerOP_LSClientAuth,
std::bind(
&LoginServer::ProcessLSClientAuth,
this,
std::placeholders::_1,
std::placeholders::_2
)
);
m_legacy_client->OnMessage(
ServerOP_LSFatalError,
std::bind(
&LoginServer::ProcessLSFatalError,
this,
std::placeholders::_1,
std::placeholders::_2
)
);
m_legacy_client->OnMessage(
ServerOP_SystemwideMessage,
std::bind(
&LoginServer::ProcessSystemwideMessage,
this,
std::placeholders::_1,
std::placeholders::_2
)
);
m_legacy_client->OnMessage(
ServerOP_LSRemoteAddr,
std::bind(
&LoginServer::ProcessLSRemoteAddr,
this,
std::placeholders::_1,
std::placeholders::_2
)
);
m_legacy_client->OnMessage(
ServerOP_LSAccountUpdate,
std::bind(
&LoginServer::ProcessLSAccountUpdate,
this,
std::placeholders::_1,
std::placeholders::_2
)
);
}
else {
m_client = std::make_unique<EQ::Net::ServertalkClient>(
m_loginserver_address,
m_loginserver_port,
false,
"World",
""
);
m_client->OnConnect(
[this](EQ::Net::ServertalkClient *client) {
if (client) {
LogInfo(
"Connected to Loginserver [{0}:{1}]",
m_loginserver_address,
m_loginserver_port
);
SendInfo();
SendStatus();
ZSList::Instance()->SendLSZones();
m_statusupdate_timer = std::make_unique<EQ::Timer>(
LoginServer_StatusUpdateInterval, true, [this](EQ::Timer *t) {
SendStatus();
}
);
}
else {
LogInfo(
"Could not connect to Loginserver: [{0}:{1}]",
m_loginserver_address,
m_loginserver_port
);
}
}
);
m_client->OnMessage(
ServerOP_UsertoWorldReqLeg,
std::bind(
&LoginServer::ProcessUsertoWorldReqLeg,
this,
std::placeholders::_1,
std::placeholders::_2
)
);
m_client->OnMessage(
ServerOP_UsertoWorldReq,
std::bind(
&LoginServer::ProcessUsertoWorldReq,
this,
std::placeholders::_1,
std::placeholders::_2
)
);
m_client->OnMessage(
ServerOP_LSClientAuthLeg,
std::bind(
&LoginServer::ProcessLSClientAuthLegacy,
this,
std::placeholders::_1,
std::placeholders::_2
)
);
m_client->OnMessage(
ServerOP_LSClientAuth,
std::bind(
&LoginServer::ProcessLSClientAuth,
this,
std::placeholders::_1,
std::placeholders::_2
)
);
m_client->OnMessage(
ServerOP_LSFatalError,
std::bind(
&LoginServer::ProcessLSFatalError,
this,
std::placeholders::_1,
std::placeholders::_2
)
);
m_client->OnMessage(
ServerOP_SystemwideMessage,
std::bind(
&LoginServer::ProcessSystemwideMessage,
this,
std::placeholders::_1,
std::placeholders::_2
)
);
m_client->OnMessage(
ServerOP_LSRemoteAddr,
std::bind(
&LoginServer::ProcessLSRemoteAddr,
this,
std::placeholders::_1,
std::placeholders::_2
)
);
m_client->OnMessage(
ServerOP_LSAccountUpdate,
std::bind(
&LoginServer::ProcessLSAccountUpdate,
this,
std::placeholders::_1,
std::placeholders::_2
)
);
}
return true;
}
void LoginServer::SendInfo()
{
if (m_client == nullptr && m_legacy_client == nullptr) {
LogDebug("No client to send info to loginserver");
return;
}
const WorldConfig *Config = WorldConfig::get();
auto pack = new ServerPacket;
pack->opcode = ServerOP_NewLSInfo;
pack->size = sizeof(LoginserverNewWorldRequest);
pack->pBuffer = new uchar[pack->size];
memset(pack->pBuffer, 0, pack->size);
auto *l = (LoginserverNewWorldRequest *) pack->pBuffer;
strcpy(l->protocol_version, EQEMU_PROTOCOL_VERSION);
strcpy(l->server_version, LOGIN_VERSION);
strcpy(l->server_long_name, Config->LongName.c_str());
strcpy(l->server_short_name, Config->ShortName.c_str());
strn0cpy(l->account_name, m_login_account.c_str(), 30);
strn0cpy(l->account_password, m_login_password.c_str(), 30);
if (Config->WorldAddress.length()) {
strcpy(l->remote_ip_address, Config->WorldAddress.c_str());
}
if (Config->LocalAddress.length()) {
strcpy(l->local_ip_address, Config->LocalAddress.c_str());
}
else {
auto local_addr = m_is_legacy ? m_legacy_client->Handle()->LocalIP() : m_client->Handle()->LocalIP();
strcpy(l->local_ip_address, local_addr.c_str());
WorldConfig::SetLocalAddress(l->local_ip_address);
}
LogInfo(
"protocol_version [{}] server_version [{}] long_name [{}] short_name [{}] account_name [{}] remote_ip_address [{}] local_ip [{}]",
l->protocol_version,
l->server_version,
l->server_long_name,
l->server_short_name,
l->account_name,
l->remote_ip_address,
l->local_ip_address
);
SendPacket(pack);
delete pack;
}
void LoginServer::SendStatus()
{
if (m_client == nullptr && m_legacy_client == nullptr) {
LogDebug("No client to send status to loginserver");
return;
}
auto pack = new ServerPacket;
pack->opcode = ServerOP_LSStatus;
pack->size = sizeof(LoginserverWorldStatusUpdate);
pack->pBuffer = new uchar[pack->size];
memset(pack->pBuffer, 0, pack->size);
auto loginserver_status = (LoginserverWorldStatusUpdate *) pack->pBuffer;
if (WorldConfig::get()->Locked) {
loginserver_status->status = -2;
}
else if (numzones <= 0) {
loginserver_status->status = -2;
}
else {
loginserver_status->status = numplayers;
}
loginserver_status->num_zones = numzones;
loginserver_status->num_players = numplayers;
SendPacket(pack);
delete pack;
}
void LoginServer::SendPacket(ServerPacket *pack)
{
if (m_legacy_client) {
m_legacy_client->SendPacket(pack);
}
else if (m_client) {
m_client->SendPacket(pack);
}
}
void LoginServer::SendAccountUpdate(ServerPacket *pack)
{
if (m_client == nullptr && m_legacy_client == nullptr) {
LogDebug("No client to send account update to loginserver");
return;
}
auto *req = (LoginserverAccountUpdate *) pack->pBuffer;
if (CanUpdate()) {
LogInfo(
"Sending ServerOP_LSAccountUpdate packet to loginserver: [{0}]:[{1}]",
m_loginserver_address,
m_loginserver_port
);
strn0cpy(req->world_account, m_login_account.c_str(), 30);
strn0cpy(req->world_password, m_login_password.c_str(), 30);
SendPacket(pack);
}
}