/* * The Mana Server * Copyright (C) 2004-2010 The Mana World Development Team * Copyright (C) 2010 The Mana Developers * * This file is part of The Mana Server. * * The Mana Server is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * any later version. * * The Mana Server is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with The Mana Server. If not, see . */ #ifdef HAVE_CONFIG_H #include "../config.h" #endif #ifdef __MINGW32__ #include "common/winver.h" #endif #include "account-server/accounthandler.h" #include "account-server/serverhandler.h" #include "account-server/storage.h" #include "chat-server/chatchannelmanager.h" #include "chat-server/chathandler.h" #include "chat-server/guildmanager.h" #include "chat-server/post.h" #include "common/configuration.h" #include "common/defines.h" #include "common/manaserv_protocol.h" #include "common/resourcemanager.h" #include "net/bandwidth.h" #include "net/connectionhandler.h" #include "net/messageout.h" #include "utils/logger.h" #include "utils/processorutils.h" #include "utils/stringfilter.h" #include "utils/time.h" #include #include #include #include #include #include #include #include #include #include using utils::Logger; // Default options that automake should be able to override. #define DEFAULT_LOG_FILE "manaserv-account.log" #define DEFAULT_STATS_FILE "manaserv.stats" #define DEFAULT_ATTRIBUTEDB_FILE "attributes.xml" static std::atomic_bool running = true; /**< Determines if server keeps running */ utils::StringFilter *stringFilter; /**< Slang's Filter */ static std::string statisticsFile = std::string(); /** Database handler. */ Storage *storage; /** Communications (chat) message handler */ ChatHandler *chatHandler; ChatChannelManager *chatChannelManager; GuildManager *guildManager; PostManager *postalManager; BandwidthMonitor *gBandwidth; /** Callback used when SIGQUIT signal is received. */ static void closeGracefully(int) { running.store(false, std::memory_order_relaxed); } /** * Initializes the server. */ static void initialize() { // Used to close via process signals #if (defined __USE_UNIX98 || defined __FreeBSD__) signal(SIGQUIT, closeGracefully); #endif signal(SIGINT, closeGracefully); signal(SIGTERM, closeGracefully); std::string logFile = Configuration::getValue("log_accountServerFile", DEFAULT_LOG_FILE); // Initialize PhysicsFS PHYSFS_init(""); Logger::initialize(logFile); // Indicate in which file the statistics are put. statisticsFile = Configuration::getValue("log_statisticsFile", DEFAULT_STATS_FILE); LOG_INFO("Using statistics file: " << statisticsFile); ResourceManager::initialize(); // Open database try { storage = new Storage; storage->open(); } catch (std::string &error) { LOG_FATAL("Error opening the database: " << error); exit(EXIT_DB_EXCEPTION); } // --- Initialize the managers stringFilter = new utils::StringFilter; // The slang's and double quotes filter. chatChannelManager = new ChatChannelManager; guildManager = new GuildManager; postalManager = new PostManager; gBandwidth = new BandwidthMonitor; // --- Initialize the global handlers // FIXME: Make the global handlers global vars or part of a bigger // singleton or a local variable in the event-loop chatHandler = new ChatHandler; // --- Initialize enet. if (enet_initialize() != 0) { LOG_FATAL("An error occurred while initializing ENet"); exit(EXIT_NET_EXCEPTION); } // Initialize the processor utility functions utils::processor::init(); // Seed the random number generator std::srand( time(nullptr) ); } /** * Deinitializes the server. */ static void deinitializeServer() { // Write configuration file Configuration::deinitialize(); // Destroy message handlers. AccountClientHandler::deinitialize(); GameServerHandler::deinitialize(); // Quit ENet enet_deinitialize(); delete chatHandler; // Destroy Managers delete stringFilter; delete chatChannelManager; delete guildManager; delete postalManager; delete gBandwidth; // Get rid of persistent data storage delete storage; PHYSFS_deinit(); } /** * Dumps statistics. */ static void dumpStatistics() { std::ofstream os(statisticsFile); os << "\n"; // Print last heartbeat os << "\n"; // Add account server information auto accountHandler = AccountClientHandler::getConnectionHandler(); auto gameHandler = GameServerHandler::getConnectionHandler(); os << "getHostname() << "\" clientport=\"" << accountHandler->getPort() << "\" gameport=\"" << gameHandler->getPort() << "\" chatclientport=\"" << chatHandler->getPort() << "\" />\n"; // Add game servers information GameServerHandler::dumpStatistics(os); os << "\n"; } /** * Show command line arguments */ static void printHelp() { std::cout << "manaserv" << std::endl << std::endl << "Options: " << std::endl << " -h --help : Display this help" << std::endl << " --config : Set the config path to use." << " (Default: ./manaserv.xml)" << std::endl << " -v --verbosity : Set the verbosity level" << std::endl << " - 0. Fatal Errors only." << std::endl << " - 1. All Errors." << std::endl << " - 2. Plus warnings." << std::endl << " - 3. Plus standard information." << std::endl << " - 4. Plus debugging information." << std::endl << " --port : Set the default port to listen on" << std::endl; exit(EXIT_NORMAL); } struct CommandLineOptions { CommandLineOptions(): verbosity(Logger::Warn), verbosityChanged(false), port(DEFAULT_SERVER_PORT), portChanged(false) {} std::string configPath; Logger::Level verbosity; bool verbosityChanged; int port; bool portChanged; }; /** * Parse the command line arguments */ static void parseOptions(int argc, char *argv[], CommandLineOptions &options) { const char *optString = "hv:"; const struct option longOptions[] = { { "help", no_argument, nullptr, 'h' }, { "config", required_argument, nullptr, 'c' }, { "verbosity", required_argument, nullptr, 'v' }, { "port", required_argument, nullptr, 'p' }, { nullptr, 0, nullptr, 0 } }; while (optind < argc) { int result = getopt_long(argc, argv, optString, longOptions, nullptr); if (result == -1) break; switch (result) { default: // Unknown option. case 'h': // Print help. printHelp(); break; case 'c': // Change config filename and path. options.configPath = optarg; break; case 'v': options.verbosity = static_cast(atoi(optarg)); options.verbosityChanged = true; LOG_INFO("Using log verbosity level " << options.verbosity); break; case 'p': options.port = atoi(optarg); options.portChanged = true; break; } } } /** * Main function, initializes and runs server. */ int main(int argc, char *argv[]) { // Parse command line options CommandLineOptions options; parseOptions(argc, argv, options); if (!Configuration::initialize(options.configPath)) { LOG_FATAL("Refusing to run without configuration!"); exit(EXIT_CONFIG_NOT_FOUND); } // Check inter-server password. if (Configuration::getValue("net_password", std::string()).empty()) { LOG_FATAL("SECURITY WARNING: 'net_password' not set!"); exit(EXIT_BAD_CONFIG_PARAMETER); } // General initialization initialize(); #ifdef PACKAGE_VERSION LOG_INFO("The Mana Account+Chat Server v" << PACKAGE_VERSION); #else LOG_INFO("The Mana Account+Chat Server (unknown version)"); #endif LOG_INFO("Manaserv Protocol version " << ManaServ::PROTOCOL_VERSION << ", Enet version " << ENET_VERSION_MAJOR << "." << ENET_VERSION_MINOR << "." << ENET_VERSION_PATCH << ", Database version " << ManaServ::SUPPORTED_DB_VERSION); if (!options.verbosityChanged) options.verbosity = static_cast( Configuration::getValue("log_accountServerLogLevel", options.verbosity) ); Logger::setVerbosity(options.verbosity); std::string accountHost = Configuration::getValue("net_accountHost", "localhost"); std::string accountListenToGameHost = Configuration::getValue("net_accountListenToGameHost", accountHost); std::string accountListenToStellarBridgeHost = Configuration::getValue("net_accountListenToStellarBridgeHost", accountListenToGameHost); // We separate the chat host as the chat server will be separated out // from the account server. std::string chatHost = Configuration::getValue("net_chatHost", "localhost"); // Setting the listen ports // Note: The accountToGame and chatToClient listen ports auto offset // to accountToClient listen port when they're not set, // or to DEFAULT_SERVER_PORT otherwise. if (!options.portChanged) options.port = Configuration::getValue("net_accountListenToClientPort", options.port); int accountGamePort = Configuration::getValue("net_accountListenToGamePort", options.port + 1); int chatClientPort = Configuration::getValue("net_chatListenToClientPort", options.port + 2); int accountStellarBridgePort = Configuration::getValue("net_accountListenToStellarBridgePort", options.port + 4); bool debugNetwork = Configuration::getBoolValue("net_debugMode", false); MessageOut::setDebugModeEnabled(debugNetwork); if (!AccountClientHandler::initialize(DEFAULT_ATTRIBUTEDB_FILE, options.port, accountHost) || !GameServerHandler::initialize(accountGamePort, accountListenToGameHost) || !chatHandler->startListen(chatClientPort, chatHost)) { LOG_FATAL("Unable to create an ENet server host."); return EXIT_NET_EXCEPTION; } struct us_loop_t *loop = (struct us_loop_t *) uWS::Loop::get(); struct us_timer_t *processTimer = us_create_timer(loop, 0, 0); struct us_timer_t *statTimer = us_create_timer(loop, 0, 0); struct us_timer_t *banTimer = us_create_timer(loop, 0, 0); constexpr int processTimerIntervalMs = 1000 / 20; // 20 times per second constexpr int statTimerIntervalMs = 10 * 1000; // dump statistics every 10 seconds constexpr int banTimerIntervalMs = 30 * 1000; // check bans every 30 seconds us_timer_set(processTimer, [](struct us_timer_t *) { AccountClientHandler::getConnectionHandler()->process(); GameServerHandler::getConnectionHandler()->process(); chatHandler->process(); }, processTimerIntervalMs, processTimerIntervalMs); us_timer_set(statTimer, [](struct us_timer_t *) { dumpStatistics(); }, statTimerIntervalMs, statTimerIntervalMs); us_timer_set(banTimer, [](struct us_timer_t *) { storage->checkBannedAccounts(); }, banTimerIntervalMs, banTimerIntervalMs); uWS::App wsApp; struct PerSocketData {}; wsApp.ws("/stellar-auth", { .maxPayloadLength = 1024, // we only expect small messages .open = [](auto *ws) { std::cout << "WebSocket opened " << ws->getRemoteAddressAsText() << std::endl; }, .message = [](auto *ws, std::string_view message, uWS::OpCode /*opCode*/) { rapidjson::Document d; d.Parse(message.data(), message.size()); if (d.HasParseError()) { LOG_ERROR("WebSocket: received JSON parse error"); return; } if (!d.IsObject()) { std::cout << "not an object" << std::endl; LOG_ERROR("WebSocket: received JSON not an object"); return; } const auto pubkeyMember = d.FindMember("pubkey"); const auto tokenMember = d.FindMember("token"); if (pubkeyMember == d.MemberEnd() || tokenMember == d.MemberEnd()) { LOG_ERROR("WebSocket: received JSON missing pubkey or token"); return; } if (!pubkeyMember->value.IsString() || !tokenMember->value.IsString()) { LOG_ERROR("WebSocket: received JSON pubkey or token not a string"); return; } const std::string pubkey = pubkeyMember->value.GetString(); const std::string token = tokenMember->value.GetString(); AccountClientHandler::handleStellarLogin(token, pubkey); }, .close = [](auto *ws, int code, std::string_view message) { std::cout << "WebSocket closed " << ws->getRemoteAddressAsText() << " with code " << code << ": " << message << std::endl; } }).listen(accountListenToStellarBridgeHost, accountStellarBridgePort, [](us_listen_socket_t *listen_socket) { if (listen_socket) { /* Note that us_listen_socket_t is castable to us_socket_t */ std::cout << "WebSocket: Thread " << std::this_thread::get_id() << " listening on port " << us_socket_local_port(1, (struct us_socket_t *) listen_socket) << std::endl; } else { std::cout << "WebSocket: Thread " << std::this_thread::get_id() << " failed to listen on port 3000" << std::endl; } }); // Write startup time to database as system world state variable std::stringstream timestamp; timestamp << time(nullptr); storage->setWorldStateVar("accountserver_startup", timestamp.str(), Storage::SystemMap); // Start a thread to close the sockets and timers when running is set to false std::thread([&wsApp,processTimer,statTimer,banTimer] { while (running.load(std::memory_order_relaxed)) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); } LOG_INFO("Closing uWebSockets app..."); wsApp.close(); LOG_INFO("Closing uSockets timers..."); us_timer_close(processTimer); us_timer_close(statTimer); us_timer_close(banTimer); }).detach(); wsApp.run(); LOG_INFO("Received: Quit signal, closing down..."); chatHandler->stopListen(); deinitializeServer(); return EXIT_NORMAL; }