1
0
Protocol/old/login/net.cpp
2025-09-01 12:53:41 -05:00

577 lines
19 KiB
C++

/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
*/
// Debug and common includes
#include "../common/debug.h"
// Standard library includes
#include <algorithm>
#include <array>
#include <chrono>
#include <cstring>
#include <ctime>
#include <filesystem>
#include <format>
#include <fstream>
#include <iostream>
#include <map>
#include <memory>
#include <random>
#include <signal.h>
#include <sstream>
#include <string>
#include <string_view>
#include <thread>
#include <termios.h>
#include <fcntl.h>
// Project common includes
#include "../common/queue.h"
#include "../common/timer.h"
#include "../common/seperator.h"
#include "../common/packet_functions.h"
#include "../common/EQStreamFactory.h"
#include "../common/MiscFunctions.h"
#include "../common/version.h"
#include "../common/PacketStruct.h"
#include "../common/DataBuffer.h"
#include "../common/ConfigReader.h"
#include "../common/Log.h"
#include "../common/JsonParser.h"
#include "../common/Common_Defines.h"
#include "../common/CRC16.h"
#include "../common/timer.h"
#include "../common/unix.h"
// Login server includes
#include "net.h"
#include "client.h"
#include "LoginDatabase.h"
#include "LWorld.h"
// Global instances
EQStreamFactory eqsf(LoginStream); // Factory for creating EQ network streams
std::map<std::int16_t, OpcodeManager*> EQOpcodeManager; // Maps version to opcode manager
NetConnection net; // Main network connection instance
ClientList client_list; // List of connected clients
LWorldList world_list; // List of connected world servers
LoginDatabase database; // Database connection manager
ConfigReader configReader; // Configuration file reader
std::map<std::int16_t, std::int16_t> EQOpcodeVersions; // Maps client versions to opcode versions
Timer statTimer(60000); // Timer for periodic statistics updates (1 minute)
// Global control flag for main loop
volatile bool RunLoops = true;
// Forward declarations
bool ReadLoginConfig();
/**
* @brief Check if a key has been pressed (Linux implementation)
* @return true if a key is available, false otherwise
*/
int kbhit()
{
struct termios oldt, newt;
int ch;
int oldf;
// Get current terminal settings
tcgetattr(STDIN_FILENO, &oldt);
newt = oldt;
// Disable canonical mode and echo
newt.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
// Set non-blocking mode
oldf = fcntl(STDIN_FILENO, F_GETFL, 0);
fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);
ch = getchar();
// Restore terminal settings
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
fcntl(STDIN_FILENO, F_SETFL, oldf);
if(ch != EOF)
{
ungetc(ch, stdin);
return 1;
}
return 0;
}
/**
* @brief Main entry point for the login server
* @param argc Number of command line arguments
* @param argv Array of command line arguments
* @return Exit code (0 for success, 1 for failure)
*/
int main(int argc, char** argv)
{
// Set up signal handler for graceful shutdown
if (signal(SIGINT, CatchSignal) == SIG_ERR)
{
std::cerr << "Could not set signal handler" << std::endl;
}
// Initialize logging system
LogStart();
LogParseConfigs();
// Display welcome header
net.WelcomeHeader();
// Initialize random number generator with current time
std::random_device rd;
std::mt19937 gen(rd());
// Read and validate login server configuration
if (!net.ReadLoginConfig())
{
return 1;
}
// Initialize web server if configured
net.InitWebServer(
net.GetWebLoginAddress(),
net.GetWebLoginPort(),
net.GetWebCertFile(),
net.GetWebKeyFile(),
net.GetWebKeyPassword(),
net.GetWebHardcodeUser(),
net.GetWebHardcodePassword()
);
// Load structure definition files for packet parsing
const std::array<const char*, 2> structList =
{
"CommonStructs.xml",
"LoginStructs.xml"
};
for (const auto& structFile : structList)
{
LogWrite(INIT__INFO, 0, "Init", "Loading Structs File %s..", structFile);
if (configReader.processXML_Elements(structFile))
{
LogWrite(INIT__INFO, 0, "Init", "Loading Structs File %s completed..", structFile);
}
else
{
LogWrite(INIT__ERROR, 0, "Init", "Loading Structs File %s FAILED!", structFile);
return 1;
}
}
// Initialize world server list
LogWrite(INIT__INFO, 0, "Init", "Initialize World List..");
world_list.Init();
// Log listening configuration
if (eqsf.listen_ip_address)
{
LogWrite(INIT__INFO, 0, "Init", "Login server listening on %s port %i",
eqsf.listen_ip_address, net.GetPort());
}
else
{
LogWrite(INIT__INFO, 0, "Init", "Login server listening on port %i", net.GetPort());
}
// Open network port for incoming connections
if (!eqsf.Open(net.GetPort()))
{
LogWrite(INIT__ERROR, 0, "Init", "Failed to open port %i.", net.GetPort());
return 1;
}
// Set server running state
net.login_running = true;
net.login_uptime = getCurrentTimestamp();
// Update console window title (no-op on Linux)
net.UpdateWindowTitle();
// Initialize timeout timer for connection management
EQStream* eqs = nullptr;
auto timeoutTimer = std::make_unique<Timer>(5000);
timeoutTimer->Start();
// Main server loop
while (RunLoops)
{
// Update current time for all timers
Timer::SetCurrentTime();
// Process new incoming connections
while ((eqs = eqsf.Pop()))
{
struct in_addr in;
in.s_addr = eqs->GetRemoteIP();
LogWrite(LOGIN__INFO, 0, "Login", "New client from IP: %s on port %i",
inet_ntoa(in), ntohs(eqs->GetRemotePort()));
// Create new client instance and add to client list
auto client = std::make_unique<Client>(eqs);
eqs->SetClientVersion(0);
client_list.Add(client.release());
net.numclients++;
net.UpdateWindowTitle();
}
// Check for timed out connections
if (timeoutTimer->Check())
{
eqsf.CheckTimeout();
}
// Update statistics periodically
if (statTimer.Check())
{
world_list.UpdateWorldStats();
database.RemoveOldWorldServerStats();
database.FixBugReport();
}
// Process active clients and world servers
client_list.Process();
world_list.Process();
// Handle console input (Linux version)
if (kbhit())
{
int hitkey = getchar();
switch (hitkey)
{
case 'l':
case 'L':
{
// List all connected world servers
world_list.ListWorldsToConsole();
break;
}
case 'v':
case 'V':
{
// Display version information
std::cout << "========Version Info=========\n";
std::cout << std::format("{} {}\n", EQ2EMU_MODULE, CURRENT_VERSION);
std::cout << std::format("Last Compiled on {} {}\n", COMPILE_DATE, COMPILE_TIME);
std::cout << "=============================\n\n";
break;
}
case 'H':
case 'h':
{
// Display help menu
std::cout << "===========Help=============\n";
std::cout << "Available Commands:\n";
std::cout << "l = Listing of World Servers\n";
std::cout << "v = Login Version\n";
std::cout << "q = Quit server\n";
std::cout << "============================\n\n";
break;
}
case 'q':
case 'Q':
{
// Quit server gracefully
std::cout << "Shutting down server...\n";
RunLoops = false;
break;
}
default:
{
std::cout << "Invalid Command. Press 'h' for help.\n";
break;
}
}
}
// Small sleep to prevent CPU spinning
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
// Cleanup and shutdown
eqsf.Close();
world_list.Shutdown();
return 0;
}
/**
* @brief Signal handler for graceful shutdown
* @param sig_num The signal number received
*/
void CatchSignal(int sig_num)
{
std::cout << "Got signal " << sig_num << std::endl;
RunLoops = false;
}
/**
* @brief Constructor for NetConnection class
* Initializes all member variables to their default values
*/
NetConnection::NetConnection() noexcept
: port_{5999}
, listening_socket_{0}
, master_address_{}
, uplink_port_{0}
, uplink_account_{}
, uplink_password_{}
, login_mode_{ServerMode::Standalone}
, uplink_wrong_version_{false}
, numclients{0}
, numservers{0}
, allow_account_creation_{true}
, expansion_flag_{0x7CFF} // Full support = 0x7CFF
, cities_flag_{0xFF} // All cities enabled
, default_subscription_level_{0xFFFFFFFF}
, enabled_races_{0xFFFF} // All races enabled
, web_login_port_{0}
, login_webserver_{nullptr}
, login_running{false}
, login_uptime{getCurrentTimestamp()}
{
// Initialize address buffer
std::memset(address, 0, sizeof(address));
}
/**
* @brief Destructor for NetConnection class
* Ensures proper cleanup of web server resources
*/
NetConnection::~NetConnection()
{
// Unique_ptr automatically handles cleanup of login_webserver
}
/**
* @brief Reads and parses the login server configuration file
* @return true if configuration loaded successfully, false otherwise
*/
bool NetConnection::ReadLoginConfig()
{
// Load JSON configuration file
JsonParser parser(MAIN_CONFIG_FILE);
if (!parser.IsLoaded())
{
LogWrite(INIT__ERROR, 0, "Init", "Failed to find %s in server directory..", MAIN_CONFIG_FILE);
return false;
}
// Parse server port configuration
std::string serverport = parser.getValue("loginconfig.serverport");
std::string serverip = parser.getValue("loginconfig.serverip");
if (!parser.convertStringToUnsignedShort(serverport, port_))
{
LogWrite(INIT__ERROR, 0, "Init", "Failed to translate loginconfig.serverport..");
return false;
}
// Set listening IP address if specified
if (!serverip.empty())
{
eqsf.listen_ip_address = new char[serverip.size() + 1];
std::strcpy(eqsf.listen_ip_address, serverip.c_str());
}
else
{
safe_delete(eqsf.listen_ip_address);
eqsf.listen_ip_address = nullptr;
}
// Parse account creation setting
std::string acctcreate_str = parser.getValue("loginconfig.accountcreation");
std::uint16_t allow_acct = 0;
parser.convertStringToUnsignedShort(acctcreate_str, allow_acct);
allow_account_creation_ = (allow_acct > 0);
// Parse expansion and feature flags
std::string expflag_str = parser.getValue("loginconfig.expansionflag");
parser.convertStringToUnsignedInt(expflag_str, expansion_flag_);
std::string citiesflag_str = parser.getValue("loginconfig.citiesflag");
parser.convertStringToUnsignedChar(citiesflag_str, cities_flag_);
std::string defaultsublevel_str = parser.getValue("loginconfig.defaultsubscriptionlevel");
parser.convertStringToUnsignedInt(defaultsublevel_str, default_subscription_level_);
std::string enableraces_str = parser.getValue("loginconfig.enabledraces");
parser.convertStringToUnsignedInt(enableraces_str, enabled_races_);
// Parse web server configuration
web_login_address_ = parser.getValue("loginconfig.webloginaddress");
web_cert_file_ = parser.getValue("loginconfig.webcertfile");
web_key_file_ = parser.getValue("loginconfig.webkeyfile");
web_key_password_ = parser.getValue("loginconfig.webkeypassword");
web_hardcode_user_ = parser.getValue("loginconfig.webhardcodeuser");
web_hardcode_password_ = parser.getValue("loginconfig.webhardcodepassword");
std::string webloginport_str = parser.getValue("loginconfig.webloginport");
parser.convertStringToUnsignedShort(webloginport_str, web_login_port_);
LogWrite(INIT__INFO, 0, "Init", "%s loaded..", MAIN_CONFIG_FILE);
// Initialize database connection
LogWrite(INIT__INFO, 0, "Init", "Database init begin..");
if (!database.Init())
{
LogWrite(INIT__ERROR, 0, "Init", "Database init FAILED!");
LogStop();
return false;
}
// Load opcode definitions from database
LogWrite(INIT__INFO, 0, "Init", "Loading opcodes 2.0..");
EQOpcodeVersions = database.GetVersions();
for (const auto& [version, _] : EQOpcodeVersions)
{
EQOpcodeManager[version] = new RegularOpcodeManager();
auto opcodes = database.GetOpcodes(version);
if (!EQOpcodeManager[version]->LoadOpcodes(&opcodes))
{
LogWrite(INIT__ERROR, 0, "Init", "Loading opcodes failed. Make sure you have sourced the opcodes.sql file!");
return false;
}
}
return true;
}
/**
* @brief Updates the console window title with server statistics
* @param iNewTitle Optional custom title string
* Note: This is a no-op on Linux, kept for API compatibility
*/
void NetConnection::UpdateWindowTitle(const char* iNewTitle)
{
// No-op on Linux - terminal titles are handled differently
// Could potentially use ANSI escape codes if needed:
// std::cout << "\033]0;" << title << "\007";
}
/**
* @brief Displays the welcome header with ASCII art and information
*/
void NetConnection::WelcomeHeader()
{
// Use ANSI color codes for Linux terminal
const char* WHITE_BOLD = "\033[1;37m";
const char* YELLOW_BOLD = "\033[1;33m";
const char* GREEN_BOLD = "\033[1;32m";
const char* MAGENTA_BOLD = "\033[1;35m";
const char* RESET = "\033[0m";
// Display module and version information
std::cout << WHITE_BOLD;
std::cout << std::format("Module: {}, Version: {}", EQ2EMU_MODULE, CURRENT_VERSION);
// Display copyright and license information
std::cout << YELLOW_BOLD;
std::cout << "\n\nCopyright (C) 2007-2021 EQ2Emulator. https://www.eq2emu.com \n\n";
std::cout << "EQ2Emulator is free software: you can redistribute it and/or modify\n";
std::cout << "it under the terms of the GNU General Public License as published by\n";
std::cout << "the Free Software Foundation, either version 3 of the License, or\n";
std::cout << "(at your option) any later version.\n\n";
std::cout << "EQ2Emulator is distributed in the hope that it will be useful,\n";
std::cout << "but WITHOUT ANY WARRANTY; without even the implied warranty of\n";
std::cout << "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n";
std::cout << "GNU General Public License for more details.\n\n";
// Display ASCII art logo
std::cout << GREEN_BOLD;
std::cout << " /$$$$$$$$ /$$$$$$ /$$$$$$ /$$$$$$$$ \n";
std::cout << "| $$_____/ /$$__ $$ /$$__ $$| $$_____/ \n";
std::cout << "| $$ | $$ \\ $$|__/ \\ $$| $$ /$$$$$$/$$$$ /$$ /$$\n";
std::cout << "| $$$$$ | $$ | $$ /$$$$$$/| $$$$$ | $$_ $$_ $$| $$ | $$\n";
std::cout << "| $$__/ | $$ | $$ /$$____/ | $$__/ | $$ \\ $$ \\ $$| $$ | $$\n";
std::cout << "| $$ | $$/$$ $$| $$ | $$ | $$ | $$ | $$| $$ | $$\n";
std::cout << "| $$$$$$$$| $$$$$$/| $$$$$$$$| $$$$$$$$| $$ | $$ | $$| $$$$$$/\n";
std::cout << "|________/ \\____ $$$|________/|________/|__/ |__/ |__/ \\______/ \n";
std::cout << " \\__/ \n\n";
// Display community links
std::cout << MAGENTA_BOLD;
std::cout << " Website : https://eq2emu.com \n";
std::cout << " Wiki : https://wiki.eq2emu.com \n";
std::cout << " Git : https://git.eq2emu.com \n";
std::cout << " Discord : https://discord.gg/5Cavm9NYQf \n\n";
std::cout << WHITE_BOLD;
std::cout << "For more detailed logging, modify 'Level' param the log_config.xml file.\n\n";
// Reset terminal colors
std::cout << RESET;
std::cout.flush();
}
/**
* @brief Initializes the web server for HTTP/HTTPS API access
* @param web_ipaddr IP address to bind to
* @param web_port Port number for web server
* @param cert_file SSL certificate file path
* @param key_file SSL key file path
* @param key_password Password for SSL key
* @param hardcode_user Basic auth username
* @param hardcode_password Basic auth password
*/
void NetConnection::InitWebServer(std::string_view web_ipaddr,
std::uint16_t web_port,
std::string_view cert_file,
std::string_view key_file,
std::string_view key_password,
std::string_view hardcode_user,
std::string_view hardcode_password)
{
// Only initialize if address and port are configured
if (!web_ipaddr.empty() && web_port > 0)
{
try
{
// Create web server instance with SSL configuration
login_webserver_ = std::make_unique<WebServer>(
std::string(web_ipaddr),
web_port,
std::string(cert_file),
std::string(key_file),
std::string(key_password),
std::string(hardcode_user),
std::string(hardcode_password)
);
// Register API routes
login_webserver_->register_route("/status", NetConnection::Web_loginhandle_status);
login_webserver_->register_route("/worlds", NetConnection::Web_loginhandle_worlds);
// Start web server
login_webserver_->run();
LogWrite(INIT__INFO, 0, "Init", "Login Web Server is listening on %s:%u..",
web_ipaddr.data(), web_port);
}
catch (const std::exception& e)
{
LogWrite(INIT__ERROR, 0, "Init", "Login Web Server failed to listen on %s:%u due to reason %s",
web_ipaddr.data(), web_port, e.what());
}
}
}