Add standalone web api server, cli handler, authorization, commands

This commit is contained in:
Akkadius 2019-07-07 03:13:04 -05:00
parent 7d71163fa0
commit 78d8b909be
18 changed files with 3749 additions and 18 deletions

View File

@ -122,6 +122,7 @@ SET(common_headers
crash.h
crc16.h
crc32.h
cli/argh.h
data_verification.h
database.h
dbcore.h
@ -156,6 +157,7 @@ SET(common_headers
global_define.h
guild_base.h
guilds.h
http/httplib.h
inventory_profile.h
inventory_slot.h
ipc_mutex.h

434
common/cli/argh.h Normal file
View File

@ -0,0 +1,434 @@
#pragma once
#include <algorithm>
#include <sstream>
#include <limits>
#include <string>
#include <vector>
#include <set>
#include <map>
#include <cassert>
namespace argh
{
// Terminology:
// A command line is composed of 2 types of args:
// 1. Positional args, i.e. free standing values
// 2. Options: args beginning with '-'. We identify two kinds:
// 2.1: Flags: boolean options => (exist ? true : false)
// 2.2: Parameters: a name followed by a non-option value
#if !defined(__GNUC__) || (__GNUC__ >= 5)
using string_stream = std::istringstream;
#else
// Until GCC 5, istringstream did not have a move constructor.
// stringstream_proxy is used instead, as a workaround.
class stringstream_proxy
{
public:
stringstream_proxy() = default;
// Construct with a value.
stringstream_proxy(std::string const& value) :
stream_(value)
{}
// Copy constructor.
stringstream_proxy(const stringstream_proxy& other) :
stream_(other.stream_.str())
{
stream_.setstate(other.stream_.rdstate());
}
void setstate(std::ios_base::iostate state) { stream_.setstate(state); }
// Stream out the value of the parameter.
// If the conversion was not possible, the stream will enter the fail state,
// and operator bool will return false.
template<typename T>
stringstream_proxy& operator >> (T& thing)
{
stream_ >> thing;
return *this;
}
// Get the string value.
std::string str() const { return stream_.str(); }
std::stringbuf* rdbuf() const { return stream_.rdbuf(); }
// Check the state of the stream.
// False when the most recent stream operation failed
operator bool() const { return !!stream_; }
~stringstream_proxy() = default;
private:
std::istringstream stream_;
};
using string_stream = stringstream_proxy;
#endif
class parser
{
public:
enum Mode { PREFER_FLAG_FOR_UNREG_OPTION = 1 << 0,
PREFER_PARAM_FOR_UNREG_OPTION = 1 << 1,
NO_SPLIT_ON_EQUALSIGN = 1 << 2,
SINGLE_DASH_IS_MULTIFLAG = 1 << 3,
};
parser() = default;
parser(std::initializer_list<char const* const> pre_reg_names)
{ add_params(pre_reg_names); }
parser(const char* const argv[], int mode = PREFER_FLAG_FOR_UNREG_OPTION)
{ parse(argv, mode); }
parser(int argc, const char* const argv[], int mode = PREFER_FLAG_FOR_UNREG_OPTION)
{ parse(argc, argv, mode); }
void add_param(std::string const& name);
void add_params(std::initializer_list<char const* const> init_list);
void parse(const char* const argv[], int mode = PREFER_FLAG_FOR_UNREG_OPTION);
void parse(int argc, const char* const argv[], int mode = PREFER_FLAG_FOR_UNREG_OPTION);
std::multiset<std::string> const& flags() const { return flags_; }
std::map<std::string, std::string> const& params() const { return params_; }
std::vector<std::string> const& pos_args() const { return pos_args_; }
// begin() and end() for using range-for over positional args.
std::vector<std::string>::const_iterator begin() const { return pos_args_.cbegin(); }
std::vector<std::string>::const_iterator end() const { return pos_args_.cend(); }
size_t size() const { return pos_args_.size(); }
//////////////////////////////////////////////////////////////////////////
// Accessors
// flag (boolean) accessors: return true if the flag appeared, otherwise false.
bool operator[](std::string const& name) const;
// multiple flag (boolean) accessors: return true if at least one of the flag appeared, otherwise false.
bool operator[](std::initializer_list<char const* const> init_list) const;
// returns positional arg string by order. Like argv[] but without the options
std::string const& operator[](size_t ind) const;
// returns a std::istream that can be used to convert a positional arg to a typed value.
string_stream operator()(size_t ind) const;
// same as above, but with a default value in case the arg is missing (index out of range).
template<typename T>
string_stream operator()(size_t ind, T&& def_val) const;
// parameter accessors, give a name get an std::istream that can be used to convert to a typed value.
// call .str() on result to get as string
string_stream operator()(std::string const& name) const;
// accessor for a parameter with multiple names, give a list of names, get an std::istream that can be used to convert to a typed value.
// call .str() on result to get as string
// returns the first value in the list to be found.
string_stream operator()(std::initializer_list<char const* const> init_list) const;
// same as above, but with a default value in case the param was missing.
// Non-string def_val types must have an operator<<() (output stream operator)
// If T only has an input stream operator, pass the string version of the type as in "3" instead of 3.
template<typename T>
string_stream operator()(std::string const& name, T&& def_val) const;
// same as above but for a list of names. returns the first value to be found.
template<typename T>
string_stream operator()(std::initializer_list<char const* const> init_list, T&& def_val) const;
private:
string_stream bad_stream() const;
std::string trim_leading_dashes(std::string const& name) const;
bool is_number(std::string const& arg) const;
bool is_option(std::string const& arg) const;
bool got_flag(std::string const& name) const;
bool is_param(std::string const& name) const;
private:
std::vector<std::string> args_;
std::map<std::string, std::string> params_;
std::vector<std::string> pos_args_;
std::multiset<std::string> flags_;
std::set<std::string> registeredParams_;
std::string empty_;
};
//////////////////////////////////////////////////////////////////////////
inline void parser::parse(const char * const argv[], int mode)
{
int argc = 0;
for (auto argvp = argv; *argvp; ++argc, ++argvp);
parse(argc, argv, mode);
}
//////////////////////////////////////////////////////////////////////////
inline void parser::parse(int argc, const char* const argv[], int mode /*= PREFER_FLAG_FOR_UNREG_OPTION*/)
{
// convert to strings
args_.resize(argc);
std::transform(argv, argv + argc, args_.begin(), [](const char* const arg) { return arg; });
// parse line
for (auto i = 0u; i < args_.size(); ++i)
{
if (!is_option(args_[i]))
{
pos_args_.emplace_back(args_[i]);
continue;
}
auto name = trim_leading_dashes(args_[i]);
if (!(mode & NO_SPLIT_ON_EQUALSIGN))
{
auto equalPos = name.find('=');
if (equalPos != std::string::npos)
{
params_.insert({ name.substr(0, equalPos), name.substr(equalPos + 1) });
continue;
}
}
// if the option is unregistered and should be a multi-flag
if (1 == (args_[i].size() - name.size()) && // single dash
argh::parser::SINGLE_DASH_IS_MULTIFLAG & mode && // multi-flag mode
!is_param(name)) // unregistered
{
std::string keep_param;
if (!name.empty() && is_param(std::string(1ul, name.back()))) // last char is param
{
keep_param += name.back();
name.resize(name.size() - 1);
}
for (auto const& c : name)
{
flags_.emplace(std::string{ c });
}
if (!keep_param.empty())
{
name = keep_param;
}
else
{
continue; // do not consider other options for this arg
}
}
// any potential option will get as its value the next arg, unless that arg is an option too
// in that case it will be determined a flag.
if (i == args_.size() - 1 || is_option(args_[i + 1]))
{
flags_.emplace(name);
continue;
}
// if 'name' is a pre-registered option, then the next arg cannot be a free parameter to it is skipped
// otherwise we have 2 modes:
// PREFER_FLAG_FOR_UNREG_OPTION: a non-registered 'name' is determined a flag.
// The following value (the next arg) will be a free parameter.
//
// PREFER_PARAM_FOR_UNREG_OPTION: a non-registered 'name' is determined a parameter, the next arg
// will be the value of that option.
assert(!(mode & argh::parser::PREFER_FLAG_FOR_UNREG_OPTION)
|| !(mode & argh::parser::PREFER_PARAM_FOR_UNREG_OPTION));
bool preferParam = mode & argh::parser::PREFER_PARAM_FOR_UNREG_OPTION;
if (is_param(name) || preferParam)
{
params_.insert({ name, args_[i + 1] });
++i; // skip next value, it is not a free parameter
continue;
}
else
{
flags_.emplace(name);
}
};
}
//////////////////////////////////////////////////////////////////////////
inline string_stream parser::bad_stream() const
{
string_stream bad;
bad.setstate(std::ios_base::failbit);
return bad;
}
//////////////////////////////////////////////////////////////////////////
inline bool parser::is_number(std::string const& arg) const
{
// inefficient but simple way to determine if a string is a number (which can start with a '-')
std::istringstream istr(arg);
double number;
istr >> number;
return !(istr.fail() || istr.bad());
}
//////////////////////////////////////////////////////////////////////////
inline bool parser::is_option(std::string const& arg) const
{
assert(0 != arg.size());
if (is_number(arg))
return false;
return '-' == arg[0];
}
//////////////////////////////////////////////////////////////////////////
inline std::string parser::trim_leading_dashes(std::string const& name) const
{
auto pos = name.find_first_not_of('-');
return std::string::npos != pos ? name.substr(pos) : name;
}
//////////////////////////////////////////////////////////////////////////
inline bool argh::parser::got_flag(std::string const& name) const
{
return flags_.end() != flags_.find(trim_leading_dashes(name));
}
//////////////////////////////////////////////////////////////////////////
inline bool argh::parser::is_param(std::string const& name) const
{
return registeredParams_.count(name);
}
//////////////////////////////////////////////////////////////////////////
inline bool parser::operator[](std::string const& name) const
{
return got_flag(name);
}
//////////////////////////////////////////////////////////////////////////
inline bool parser::operator[](std::initializer_list<char const* const> init_list) const
{
return std::any_of(init_list.begin(), init_list.end(), [&](char const* const name) { return got_flag(name); });
}
//////////////////////////////////////////////////////////////////////////
inline std::string const& parser::operator[](size_t ind) const
{
if (ind < pos_args_.size())
return pos_args_[ind];
return empty_;
}
//////////////////////////////////////////////////////////////////////////
inline string_stream parser::operator()(std::string const& name) const
{
auto optIt = params_.find(trim_leading_dashes(name));
if (params_.end() != optIt)
return string_stream(optIt->second);
return bad_stream();
}
//////////////////////////////////////////////////////////////////////////
inline string_stream parser::operator()(std::initializer_list<char const* const> init_list) const
{
for (auto& name : init_list)
{
auto optIt = params_.find(trim_leading_dashes(name));
if (params_.end() != optIt)
return string_stream(optIt->second);
}
return bad_stream();
}
//////////////////////////////////////////////////////////////////////////
template<typename T>
string_stream parser::operator()(std::string const& name, T&& def_val) const
{
auto optIt = params_.find(trim_leading_dashes(name));
if (params_.end() != optIt)
return string_stream(optIt->second);
std::ostringstream ostr;
ostr.precision(std::numeric_limits<long double>::max_digits10);
ostr << def_val;
return string_stream(ostr.str()); // use default
}
//////////////////////////////////////////////////////////////////////////
// same as above but for a list of names. returns the first value to be found.
template<typename T>
string_stream parser::operator()(std::initializer_list<char const* const> init_list, T&& def_val) const
{
for (auto& name : init_list)
{
auto optIt = params_.find(trim_leading_dashes(name));
if (params_.end() != optIt)
return string_stream(optIt->second);
}
std::ostringstream ostr;
ostr.precision(std::numeric_limits<long double>::max_digits10);
ostr << def_val;
return string_stream(ostr.str()); // use default
}
//////////////////////////////////////////////////////////////////////////
inline string_stream parser::operator()(size_t ind) const
{
if (pos_args_.size() <= ind)
return bad_stream();
return string_stream(pos_args_[ind]);
}
//////////////////////////////////////////////////////////////////////////
template<typename T>
string_stream parser::operator()(size_t ind, T&& def_val) const
{
if (pos_args_.size() <= ind)
{
std::ostringstream ostr;
ostr.precision(std::numeric_limits<long double>::max_digits10);
ostr << def_val;
return string_stream(ostr.str());
}
return string_stream(pos_args_[ind]);
}
//////////////////////////////////////////////////////////////////////////
inline void parser::add_param(std::string const& name)
{
registeredParams_.insert(trim_leading_dashes(name));
}
//////////////////////////////////////////////////////////////////////////
inline void parser::add_params(std::initializer_list<char const* const> init_list)
{
for (auto& name : init_list)
registeredParams_.insert(trim_leading_dashes(name));
}
}

View File

@ -193,7 +193,6 @@ namespace Logs {
OutF(LogSys, Logs::General, Logs::Error, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogWarning(message, ...) do {\
if (LogSys.log_settings[Logs::Warning].is_category_enabled == 1)\
OutF(LogSys, Logs::General, Logs::Warning, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\

2694
common/http/httplib.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -12,16 +12,18 @@ EQ::JsonConfigFile::JsonConfigFile(const Json::Value &value)
EQ::JsonConfigFile::~JsonConfigFile() = default;
/**
* @param filename
* @param file_name
* @return
*/
EQ::JsonConfigFile EQ::JsonConfigFile::Load(const std::string &filename)
EQ::JsonConfigFile EQ::JsonConfigFile::Load(
const std::string &file_name
)
{
JsonConfigFile ret;
ret.m_root = Json::Value();
std::ifstream ifs;
ifs.open(filename, std::ifstream::in);
ifs.open(file_name, std::ifstream::in);
if (!ifs.good()) {
return ret;
@ -37,6 +39,31 @@ EQ::JsonConfigFile EQ::JsonConfigFile::Load(const std::string &filename)
return ret;
}
/**
* @param file_name
* @return
*/
void EQ::JsonConfigFile::Save(
const std::string &file_name
)
{
std::ofstream opened_config_file;
opened_config_file.open(file_name);
/**
* Grab and build config contents
*/
Json::StreamWriterBuilder write_builder;
write_builder["indentation"] = " ";
std::string document = Json::writeString(write_builder, m_root);
/**
* Write current contents and close
*/
opened_config_file << document;
opened_config_file.close();
}
/**
* @param title
* @param parameter

View File

@ -11,7 +11,8 @@ namespace EQ
JsonConfigFile(const Json::Value &value);
~JsonConfigFile();
static JsonConfigFile Load(const std::string &filename);
static JsonConfigFile Load(const std::string &file_name);
void Save(const std::string &file_name);
std::string GetVariableString(const std::string &title, const std::string &parameter, const std::string &default_value);
int GetVariableInt(const std::string &title, const std::string &parameter, const int default_value);

View File

@ -5,6 +5,8 @@ SET(eqlogin_sources
client_manager.cpp
database.cpp
encryption.cpp
loginserver_command_handler.cpp
loginserver_webserver.cpp
main.cpp
server_manager.cpp
world_server.cpp
@ -15,6 +17,8 @@ SET(eqlogin_headers
client_manager.h
database.h
encryption.h
loginserver_command_handler.h
loginserver_webserver.h
login_server.h
login_structures.h
options.h

View File

@ -24,6 +24,7 @@
#include "login_server.h"
#include "../common/eqemu_logsys.h"
#include "../common/string_util.h"
#include "../common/util/uuid.h"
extern LoginServer server;
@ -463,7 +464,11 @@ void Database::UpdateWorldRegistration(unsigned int id, std::string long_name, s
* @param id
* @return
*/
bool Database::CreateWorldRegistration(std::string long_name, std::string short_name, unsigned int &id)
bool Database::CreateWorldRegistration(
std::string long_name,
std::string short_name,
unsigned int &id
)
{
auto query = fmt::format(
"SELECT ifnull(max(ServerID),0) + 1 FROM {0}",
@ -504,6 +509,44 @@ bool Database::CreateWorldRegistration(std::string long_name, std::string short_
return true;
}
/**
* @param long_name
* @param short_name
* @param id
* @return
*/
std::string Database::CreateLoginserverApiToken(
bool write_mode,
bool read_mode
)
{
std::string token = EQ::Util::UUID::Generate().ToString();
auto query = fmt::format(
"INSERT INTO loginserver_api_tokens (token, can_write, can_read, created_at) VALUES ('{0}', {1}, {2}, NOW())",
token,
(write_mode ? "1" : "0"),
(read_mode ? "1" : "0")
);
auto results = QueryDatabase(query);
if (!results.Success()) {
return "";
}
return token;
}
/**
* @param long_name
* @param short_name
* @param id
* @return
*/
MySQLRequestResult Database::GetLoginserverApiTokens()
{
return QueryDatabase("SELECT token, can_write, can_read FROM loginserver_api_tokens");
}
/**
* @param log_settings
*/

View File

@ -128,6 +128,15 @@ public:
void UpdateWorldRegistration(unsigned int id, std::string long_name, std::string ip_address);
bool CreateWorldRegistration(std::string long_name, std::string short_name, unsigned int &id);
void LoadLogSettings(EQEmuLogSys::LogSettings *log_settings);
/**
* @param write_mode
* @param read_mode
* @return
*/
std::string CreateLoginserverApiToken(bool write_mode, bool read_mode);
MySQLRequestResult GetLoginserverApiTokens();
protected:
std::string user, pass, host, port, name;
MYSQL *database;

View File

@ -1,3 +1,5 @@
#include <utility>
/**
* EQEmulator: Everquest Server Emulator
* Copyright (C) 2001-2019 EQEmulator Development Team (https://github.com/EQEmu/Server)
@ -28,6 +30,7 @@
#include "options.h"
#include "server_manager.h"
#include "client_manager.h"
#include "loginserver_webserver.h"
/**
* Login server struct, contains every variable for the server that needs to exist outside the scope of main()
@ -35,13 +38,16 @@
struct LoginServer
{
public:
LoginServer() : db(nullptr), server_manager(nullptr) { }
LoginServer() : db(nullptr), server_manager(nullptr) {
EQ::JsonConfigFile config;
Database *db;
Options options;
ServerManager *server_manager;
ClientManager *client_manager{};
}
EQ::JsonConfigFile config;
Database *db;
LoginserverWebserver::TokenManager *token_manager{};
Options options;
ServerManager *server_manager;
ClientManager *client_manager{};
};
#endif

View File

@ -0,0 +1,142 @@
/**
* EQEmulator: Everquest Server Emulator
* Copyright (C) 2001-2019 EQEmulator Development Team (https://github.com/EQEmu/Server)
*
* This program 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; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY except by those people which sell it, which
* are required to give you total support for your newly bought product;
* 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <iostream>
#include <random>
#include "loginserver_command_handler.h"
#include "../common/util/uuid.h"
#include "login_server.h"
#include "loginserver_webserver.h"
extern LoginServer server;
namespace LoginserverCommandHandler {
/**
* @param cmd
*/
void DisplayDebug(argh::parser &cmd)
{
if (cmd[{"-d", "--debug"}]) {
std::cout << "Positional args:\n";
for (auto &pos_arg : cmd)
std::cout << '\t' << pos_arg << std::endl;
std::cout << "Positional args:\n";
for (auto &pos_arg : cmd.pos_args())
std::cout << '\t' << pos_arg << std::endl;
std::cout << "\nFlags:\n";
for (auto &flag : cmd.flags())
std::cout << '\t' << flag << std::endl;
std::cout << "\nParameters:\n";
for (auto &param : cmd.params())
std::cout << '\t' << param.first << " : " << param.second << std::endl;
}
}
/**
* @param argc
* @param argv
*/
void CommandHandler(int argc, char **argv)
{
if (argc == 1) { return; }
argh::parser cmd;
cmd.parse(argc, argv, argh::parser::PREFER_PARAM_FOR_UNREG_OPTION);
LoginserverCommandHandler::DisplayDebug(cmd);
/**
* Declare command mapping
*/
std::map<std::string, void (*)(int argc, char **argv, argh::parser &cmd)> function_map;
function_map["create-loginserver-api-token"] = &LoginserverCommandHandler::CreateLoginserverApiToken;
function_map["list-loginserver-api-tokens"] = &LoginserverCommandHandler::ListLoginserverApiTokens;
std::map<std::string, void (*)(int argc, char **argv, argh::parser &cmd)>::const_iterator it = function_map.begin();
std::map<std::string, void (*)(int argc, char **argv, argh::parser &cmd)>::const_iterator end = function_map.end();
bool ran_command = false;
while (it != end) {
if (it->first == argv[1]) {
(it->second)(argc, argv, cmd);
ran_command = true;
}
++it;
}
if (cmd[{"-h", "--help"}] || !ran_command) {
std::cout << std::endl;
std::cout << "###########################################################" << std::endl;
std::cout << "# Loginserver CLI Menu" << std::endl;
std::cout << "###########################################################" << std::endl;
std::cout << std::endl;
std::cout << "> create-loginserver-api-token --write --read" << std::endl;
std::cout << "> list-loginserver-api-tokens" << std::endl;
std::cout << std::endl;
std::cout << std::endl;
}
exit(1);
}
/**
* @param argc
* @param argv
* @param cmd
*/
void CreateLoginserverApiToken(int argc, char **argv, argh::parser &cmd)
{
bool can_read = cmd[{"-r", "--read"}];
bool can_write = cmd[{"-w", "--write"}];
if (!can_read || !can_write) {
LogInfo("[{0}] --read or --write must be set or both!", __func__);
exit(1);
}
std::string token = server.db->CreateLoginserverApiToken(can_write, can_read);
if (!token.empty()) {
LogInfo("[{0}] Created Loginserver API token [{1}]", __func__, token);
}
}
/**
* @param argc
* @param argv
* @param cmd
*/
void ListLoginserverApiTokens(int argc, char **argv, argh::parser &cmd)
{
for (auto it = server.token_manager->loaded_api_tokens.begin();
it != server.token_manager->loaded_api_tokens.end();
++it) {
LogInfo(
"token [{0}] can_write [{1}] can_read [{2}]",
it->second.token,
it->second.can_write,
it->second.can_read
);
}
}
}

View File

@ -0,0 +1,34 @@
/**
* EQEmulator: Everquest Server Emulator
* Copyright (C) 2001-2019 EQEmulator Development Team (https://github.com/EQEmu/Server)
*
* This program 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; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY except by those people which sell it, which
* are required to give you total support for your newly bought product;
* 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include "iostream"
#include "../common/cli/argh.h"
#ifndef EQEMU_LOGINSERVER_COMMAND_HANDLER_H
#define EQEMU_LOGINSERVER_COMMAND_HANDLER_H
namespace LoginserverCommandHandler {
void CommandHandler(int argc, char **argv);
void CreateLoginserverApiToken(int argc, char **argv, argh::parser &cmd);
void ListLoginserverApiTokens(int argc, char **argv, argh::parser &cmd);
};
#endif //EQEMU_LOGINSERVER_COMMAND_HANDLER_H

View File

@ -0,0 +1,243 @@
/**
* EQEmulator: Everquest Server Emulator
* Copyright (C) 2001-2019 EQEmulator Development Team (https://github.com/EQEmu/Server)
*
* This program 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; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY except by those people which sell it, which
* are required to give you total support for your newly bought product;
* 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include "loginserver_webserver.h"
#include "server_manager.h"
#include "login_server.h"
#include "../common/json/json.h"
#include "../common/string_util.h"
extern LoginServer server;
namespace LoginserverWebserver {
/**
* @param api
*/
void RegisterRoutes(httplib::Server &api)
{
server.token_manager = new LoginserverWebserver::TokenManager;
server.token_manager->LoadApiTokens();
api.Get(
"/servers/list", [](const httplib::Request &request, httplib::Response &res) {
LoginserverWebserver::TokenManager::AuthCanRead(request, res);
Json::Value response;
auto iter = server.server_manager->getWorldServers().begin();
while (iter != server.server_manager->getWorldServers().end()) {
Json::Value row;
row["server_long_name"] = (*iter)->GetLongName();
row["server_short_name"] = (*iter)->GetLongName();
row["server_list_id"] = (*iter)->GetServerListID();
row["server_status"] = (*iter)->GetStatus();
row["zones_booted"] = (*iter)->GetZonesBooted();
row["local_ip"] = (*iter)->GetLocalIP();
row["remote_ip"] = (*iter)->GetRemoteIP();
row["players_online"] = (*iter)->GetPlayersOnline();
response.append(row);
++iter;
}
LoginserverWebserver::SendResponse(response, res);
}
);
}
/**
* @param payload
* @param res
*/
void SendResponse(const Json::Value &payload, httplib::Response &res)
{
if (res.get_header_value("response_set") == "true") {
res.set_header("response_set", "");
return;
}
std::stringstream response_payload;
if (payload.empty()) {
Json::Value response;
response["message"] = "There were no results found";
response_payload << response;
res.set_content(response_payload.str(), "application/json");
return;
}
response_payload << payload;
res.set_content(response_payload.str(), "application/json");
}
/**
* @param request
* @param res
*/
void LoginserverWebserver::TokenManager::AuthCanRead(const httplib::Request &request, httplib::Response &res)
{
LoginserverWebserver::TokenManager::token_data
user_token = LoginserverWebserver::TokenManager::CheckApiAuthorizationHeaders(request);
if (!user_token.can_read) {
Json::Value response;
std::stringstream response_payload;
response["message"] = "Authorization token is either invalid or cannot read!";
response_payload << response;
res.set_content(response_payload.str(), "application/json");
res.set_header("response_set", "true");
LogWarning(
"AuthCanRead access failure | token [{0}] remote_address [{1}] user_agent [{2}]",
user_token.token,
user_token.remote_address,
user_token.user_agent
);
return;
}
}
/**
* @param request
* @param res
*/
void LoginserverWebserver::TokenManager::AuthCanWrite(const httplib::Request &request, httplib::Response &res)
{
LoginserverWebserver::TokenManager::token_data
user_token = LoginserverWebserver::TokenManager::CheckApiAuthorizationHeaders(request);
if (!user_token.can_write) {
Json::Value response;
std::stringstream response_payload;
response["message"] = "Authorization token is either invalid or cannot write!";
response_payload << response;
res.set_content(response_payload.str(), "application/json");
res.set_header("response_set", "true");
LogWarning(
"AuthCanWrite access failure | token [{0}] remote_address [{1}] user_agent [{2}]",
user_token.token,
user_token.remote_address,
user_token.user_agent
);
return;
}
}
/**
* @param request
* @return
*/
LoginserverWebserver::TokenManager::token_data
LoginserverWebserver::TokenManager::CheckApiAuthorizationHeaders(
const httplib::Request &request
)
{
std::string authorization_key;
LoginserverWebserver::TokenManager::token_data user_token{};
for (const auto &header : request.headers) {
auto header_key = header.first;
auto header_value = header.second;
if (header_key == "Authorization") {
authorization_key = header.second;
find_replace(authorization_key, "Bearer: ", "");
if (LoginserverWebserver::TokenManager::TokenExists(authorization_key)) {
user_token = server.token_manager->GetToken(authorization_key);
}
}
if (header_key == "REMOTE_ADDR") {
user_token.remote_address = header.second;
}
if (header_key == "User-Agent") {
user_token.user_agent = header.second;
}
}
LogDebug(
"Authentication Request | remote_address [{0}] user_agent [{1}] authorization_key [{2}]",
user_token.remote_address,
user_token.user_agent,
authorization_key
);
return user_token;
}
/**
* Loads API Tokens
*/
void TokenManager::LoadApiTokens()
{
auto results = server.db->GetLoginserverApiTokens();
for (auto row = results.begin(); row != results.end(); ++row) {
LoginserverWebserver::TokenManager::token_data token_data;
token_data.token = row[0];
token_data.can_write = std::stoi(row[1]) > 0;
token_data.can_read = std::stoi(row[2]) > 0;
LogDebug(
"Inserting api token to internal list [{0}] write {1} read {2}",
token_data.token,
token_data.can_read,
token_data.can_write
);
server.token_manager->loaded_api_tokens.insert(
std::make_pair(
token_data.token,
token_data
)
);
}
}
/**
* @param token
* @return
*/
bool TokenManager::TokenExists(const std::string &token)
{
auto it = server.token_manager->loaded_api_tokens.find(token);
return !(it == server.token_manager->loaded_api_tokens.end());
}
/**
* @param token
* @return
*/
LoginserverWebserver::TokenManager::token_data TokenManager::GetToken(
const std::string &token
)
{
auto iter = server.token_manager->loaded_api_tokens.find(token);
if (iter != server.token_manager->loaded_api_tokens.end()) {
return iter->second;
}
return {};
}
}

View File

@ -0,0 +1,57 @@
/**
* EQEmulator: Everquest Server Emulator
* Copyright (C) 2001-2019 EQEmulator Development Team (https://github.com/EQEmu/Server)
*
* This program 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; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY except by those people which sell it, which
* are required to give you total support for your newly bought product;
* 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#ifndef EQEMU_LOGINSERVER_WEBSERVER_H
#define EQEMU_LOGINSERVER_WEBSERVER_H
#include "../common/http/httplib.h"
#include "../common/json/json.h"
#include "../common/types.h"
namespace LoginserverWebserver {
class TokenManager {
public:
TokenManager() = default;
struct token_data {
std::string token;
bool can_read;
bool can_write;
std::string user_agent;
std::string remote_address;
};
std::map<std::string, token_data> loaded_api_tokens{};
void LoadApiTokens();
static bool TokenExists(const std::string &token);
token_data GetToken(const std::string &token);
static token_data CheckApiAuthorizationHeaders(const httplib::Request &request);
static void AuthCanRead(const httplib::Request &request, httplib::Response &res);
static void AuthCanWrite(const httplib::Request &request, httplib::Response &res);
};
void RegisterRoutes(httplib::Server &api);
void SendResponse(const Json::Value &payload, httplib::Response &res);
};
#endif //EQEMU_LOGINSERVER_WEBSERVER_H

View File

@ -26,7 +26,10 @@
#include "../common/platform.h"
#include "../common/crash.h"
#include "../common/eqemu_logsys.h"
#include "../common/http/httplib.h"
#include "login_server.h"
#include "loginserver_webserver.h"
#include "loginserver_command_handler.h"
#include <time.h>
#include <stdlib.h>
#include <string>
@ -40,7 +43,7 @@ void CatchSignal(int sig_num)
{
}
int main()
int main(int argc, char** argv)
{
RegisterExecutablePlatform(ExePlatformLogin);
set_exception_handler();
@ -156,7 +159,7 @@ int main()
* create client manager
*/
LogInfo("Client Manager Init");
server.client_manager = new ClientManager();
server.client_manager = new ClientManager();
if (!server.client_manager) {
LogError("Client Manager Failed to Start");
LogInfo("Server Manager Shutdown");
@ -176,25 +179,46 @@ int main()
#endif
LogInfo("Server Started");
if (LogSys.log_settings[Logs::Login_Server].log_to_console == 1) {
LogInfo("Loginserver logging set to level [1] for more debugging, enable detail [3]");
}
/**
* Web API
*/
httplib::Server api;
int web_api_port = server.config.GetVariableInt("web_api", "port", 6000);
bool web_api_enabled = server.config.GetVariableBool("web_api", "enabled", true);
if (web_api_enabled) {
api.bind("0.0.0.0", web_api_port);
LogInfo("Webserver API now listening on port [{0}]", web_api_port);
LoginserverWebserver::RegisterRoutes(api);
}
LoginserverCommandHandler::CommandHandler(argc, argv);
while (run_server) {
Timer::SetCurrentTime();
server.client_manager->Process();
EQ::EventLoop::Get().Process();
Sleep(50);
if (web_api_enabled) {
api.poll();
}
Sleep(5);
}
LogInfo("Server Shutdown");
LogInfo("Client Manager Shutdown");
delete server.client_manager;
LogInfo("Server Manager Shutdown");
delete server.server_manager;
LogInfo("Database System Shutdown");
delete server.db;
return 0;
}

View File

@ -37,7 +37,6 @@ public:
dump_in_packets(false),
dump_out_packets(false),
encryption_mode(5),
local_network("127.0.0.1"),
reject_duplicate_servers(false),
allow_password_login(true),
allow_token_login(false),
@ -187,7 +186,6 @@ private:
bool auto_link_accounts;
bool update_insecure_passwords;
int encryption_mode;
std::string local_network;
std::string account_table;
std::string world_registration_table;
std::string world_admin_registration_table;

View File

@ -332,3 +332,11 @@ void ServerManager::DestroyServerByName(
++iter;
}
}
/**
* @return
*/
const std::list<std::unique_ptr<WorldServer>> &ServerManager::getWorldServers() const
{
return world_servers;
}

View File

@ -86,6 +86,11 @@ public:
*/
void DestroyServerByName(std::string server_long_name, std::string server_short_name, WorldServer *ignore = nullptr);
/**
* @return
*/
const std::list<std::unique_ptr<WorldServer>> &getWorldServers() const;
private:
/**
@ -100,6 +105,7 @@ private:
std::unique_ptr<EQ::Net::ServertalkServer> server_connection;
std::list<std::unique_ptr<WorldServer>> world_servers;
};
#endif