[Loginserver] Modernize codebase (#4647)

* Beginning of cleanup

* More cleanup

* More cleanup

* Enc cleanup

* client manager cleanup

* client cleanup

* More cleanup

* More cleanup

* Cleanup

* More cleanup, account  context, account management

* Remove positional fmt bindings

* Use LoginAccountContext

* Update loginserver_webserver.cpp

* Remove comments

* Port CreateLoginServerAccount to repositories

* More cleanup

* More cleanup

* More cleanup

* More cleanup

* Remove a ton of functions

* More cleanup

* More cleanup

* More cleanup

* Cleanup SendClientAuthToWorld

* Consolidate world server logic

* Update login_accounts_repository.h

* Update login_accounts_repository.h

* Move api tokens to repositories

* Cleanup options

* Move everything else to repositories

* Update account_management.cpp

* uint64 account

* Update login_schema.sql

* Fix

* Update world_server.cpp

* auto
This commit is contained in:
Chris Miles 2025-02-06 12:47:02 -06:00 committed by GitHub
parent 1650efa787
commit 1a48add20e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 1566 additions and 3990 deletions

View File

@ -123,7 +123,7 @@ void EQ::Net::ServertalkServerConnection::ProcessReadBuffer()
{
size_t current = 0;
size_t total = m_buffer.size();
constexpr size_t ls_info_size = sizeof(ServerNewLSInfo_Struct);
constexpr size_t ls_info_size = sizeof(LoginserverNewWorldRequest);
while (current < total) {
auto left = total - current;
@ -138,7 +138,7 @@ void EQ::Net::ServertalkServerConnection::ProcessReadBuffer()
//this creates a small edge case where the exact size of a
//packet from the modern protocol can't be "43061256"
//so in send we pad it one byte if that's the case
if (leg_opcode == ServerOP_NewLSInfo && leg_size == sizeof(ServerNewLSInfo_Struct)) {
if (leg_opcode == ServerOP_NewLSInfo && leg_size == sizeof(LoginserverNewWorldRequest)) {
m_legacy_mode = true;
m_identifier = "World";
m_parent->ConnectionIdentified(this);

View File

@ -4,47 +4,94 @@
#include "../database.h"
#include "../strings.h"
#include "base/base_login_accounts_repository.h"
#include "../../loginserver/encryption.h"
#include "../../loginserver/login_types.h"
class LoginAccountsRepository: public BaseLoginAccountsRepository {
class LoginAccountsRepository : public BaseLoginAccountsRepository {
public:
static int64 GetFreeID(Database &db, const std::string &loginserver)
{
auto query = fmt::format(
"SELECT IFNULL(MAX(id), 0) + 1 FROM login_accounts WHERE source_loginserver = '{}'",
Strings::Escape(loginserver)
);
/**
* This file was auto generated and can be modified and extended upon
*
* Base repository methods are automatically
* generated in the "base" version of this repository. The base repository
* is immutable and to be left untouched, while methods in this class
* are used as extension methods for more specific persistence-layer
* accessors or mutators.
*
* Base Methods (Subject to be expanded upon in time)
*
* Note: Not all tables are designed appropriately to fit functionality with all base methods
*
* InsertOne
* UpdateOne
* DeleteOne
* FindOne
* GetWhere(std::string where_filter)
* DeleteWhere(std::string where_filter)
* InsertMany
* All
*
* Example custom methods in a repository
*
* LoginAccountsRepository::GetByZoneAndVersion(int zone_id, int zone_version)
* LoginAccountsRepository::GetWhereNeverExpires()
* LoginAccountsRepository::GetWhereXAndY()
* LoginAccountsRepository::DeleteWhereXAndY()
*
* Most of the above could be covered by base methods, but if you as a developer
* find yourself re-using logic for other parts of the code, its best to just make a
* method that can be re-used easily elsewhere especially if it can use a base repository
* method and encapsulate filters there
*/
auto results = db.QueryDatabase(query);
if (!results.Success() || results.RowCount() != 1) {
return 0;
}
// Custom extended repository methods here
auto row = results.begin();
return Strings::ToUnsignedInt(row[0]);
}
static LoginAccountsRepository::LoginAccounts CreateAccountFromContext(
Database &db,
LoginAccountContext c
)
{
auto a = LoginAccountsRepository::NewEntity();
if (!c.password_is_encrypted) {
auto e = EncryptPasswordFromContext(c);
a.account_password = e.password;
}
a.id = c.login_account_id > 0 ? c.login_account_id : GetFreeID(db, c.source_loginserver);
a.account_name = c.username;
a.account_email = !c.email.empty() ? c.email : "local_creation";
a.source_loginserver = c.source_loginserver;
a.last_ip_address = "127.0.0.1";
a.last_login_date = std::time(nullptr);
a.created_at = std::time(nullptr);
LoginAccountsRepository::InsertOne(db, a);
return GetAccountFromContext(db, c).id > 0 ? a : NewEntity();
}
static LoginAccountsRepository::LoginAccounts GetAccountFromContext(
Database &db,
LoginAccountContext c
)
{
std::string where = fmt::format(
"account_name = '{}' AND source_loginserver = '{}'",
Strings::Escape(c.username),
Strings::Escape(c.source_loginserver)
);
if (!c.email.empty()) {
where += fmt::format(" AND account_email = '{}'", Strings::Escape(c.email));
}
if (c.login_account_id > 0) {
where += fmt::format(" AND id = {}", c.login_account_id);
}
where += " LIMIT 1";
auto results = LoginAccountsRepository::GetWhere(db, where);
auto e = LoginAccountsRepository::NewEntity();
if (results.size() == 1) {
e = results.front();
}
return e;
}
static LoginAccounts UpdateAccountPassword(Database &db, LoginAccounts a, std::string password)
{
LoginAccountContext c;
c.username = a.account_name;
c.password = password;
auto e = EncryptPasswordFromContext(c);
a.account_password = e.password;
int success = LoginAccountsRepository::UpdateOne(db, a);
return success ? a : NewEntity();
}
};
#endif //EQEMU_LOGIN_ACCOUNTS_REPOSITORY_H

View File

@ -5,46 +5,24 @@
#include "../strings.h"
#include "base/base_login_server_admins_repository.h"
class LoginServerAdminsRepository: public BaseLoginServerAdminsRepository {
class LoginServerAdminsRepository : public BaseLoginServerAdminsRepository {
public:
static LoginServerAdmins GetByName(Database &db, std::string account_name)
{
auto admins = GetWhere(
db,
fmt::format(
"account_name = '{}' LIMIT 1",
Strings::Escape(account_name)
)
);
/**
* This file was auto generated and can be modified and extended upon
*
* Base repository methods are automatically
* generated in the "base" version of this repository. The base repository
* is immutable and to be left untouched, while methods in this class
* are used as extension methods for more specific persistence-layer
* accessors or mutators.
*
* Base Methods (Subject to be expanded upon in time)
*
* Note: Not all tables are designed appropriately to fit functionality with all base methods
*
* InsertOne
* UpdateOne
* DeleteOne
* FindOne
* GetWhere(std::string where_filter)
* DeleteWhere(std::string where_filter)
* InsertMany
* All
*
* Example custom methods in a repository
*
* LoginServerAdminsRepository::GetByZoneAndVersion(int zone_id, int zone_version)
* LoginServerAdminsRepository::GetWhereNeverExpires()
* LoginServerAdminsRepository::GetWhereXAndY()
* LoginServerAdminsRepository::DeleteWhereXAndY()
*
* Most of the above could be covered by base methods, but if you as a developer
* find yourself re-using logic for other parts of the code, its best to just make a
* method that can be re-used easily elsewhere especially if it can use a base repository
* method and encapsulate filters there
*/
// Custom extended repository methods here
if (admins.size() == 1) {
return admins.front();
}
return NewEntity();
}
};
#endif //EQEMU_LOGIN_SERVER_ADMINS_REPOSITORY_H

View File

@ -7,44 +7,27 @@
class LoginWorldServersRepository: public BaseLoginWorldServersRepository {
public:
static LoginWorldServers GetFromWorldContext(Database &db, LoginWorldContext c) {
std::string where = fmt::format(
"short_name = '{}' AND long_name = '{}'",
Strings::Escape(c.short_name),
Strings::Escape(c.long_name)
);
/**
* This file was auto generated and can be modified and extended upon
*
* Base repository methods are automatically
* generated in the "base" version of this repository. The base repository
* is immutable and to be left untouched, while methods in this class
* are used as extension methods for more specific persistence-layer
* accessors or mutators.
*
* Base Methods (Subject to be expanded upon in time)
*
* Note: Not all tables are designed appropriately to fit functionality with all base methods
*
* InsertOne
* UpdateOne
* DeleteOne
* FindOne
* GetWhere(std::string where_filter)
* DeleteWhere(std::string where_filter)
* InsertMany
* All
*
* Example custom methods in a repository
*
* LoginWorldServersRepository::GetByZoneAndVersion(int zone_id, int zone_version)
* LoginWorldServersRepository::GetWhereNeverExpires()
* LoginWorldServersRepository::GetWhereXAndY()
* LoginWorldServersRepository::DeleteWhereXAndY()
*
* Most of the above could be covered by base methods, but if you as a developer
* find yourself re-using logic for other parts of the code, its best to just make a
* method that can be re-used easily elsewhere especially if it can use a base repository
* method and encapsulate filters there
*/
if (c.admin_id > 0) {
where += fmt::format(" AND login_server_admin_id = {}", c.admin_id);
}
// Custom extended repository methods here
where += " LIMIT 1";
auto results = GetWhere(db, where);
auto e = NewEntity();
if (results.size() == 1) {
e = results.front();
}
return e;
}
};
#endif //EQEMU_LOGIN_WORLD_SERVERS_REPOSITORY_H

View File

@ -646,7 +646,7 @@ struct ServerLSInfo_Struct {
uint8 servertype; // 0=world, 1=chat, 2=login, 3=MeshLogin
};
struct ServerNewLSInfo_Struct {
struct LoginserverNewWorldRequest {
char server_long_name[201]; // name the worldserver wants
char server_short_name[50]; // shortname the worldserver wants
char remote_ip_address[125]; // DNS address of the server
@ -658,21 +658,21 @@ struct ServerNewLSInfo_Struct {
uint8 server_process_type; // 0=world, 1=chat, 2=login, 3=MeshLogin
};
struct ServerLSAccountUpdate_Struct { // for updating info on login server
char worldaccount[31]; // account name for the worldserver
char worldpassword[31]; // password for the name
uint32 useraccountid; // player account ID
char useraccount[31]; // player account name
char userpassword[51]; // player account password
char user_email[101]; // player account email address
struct LoginserverAccountUpdate { // for updating info on login server
char world_account[31]; // account name for the worldserver
char world_password[31]; // password for the name
uint32 user_account_id; // player account ID
char user_account_name[31]; // player account name
char user_account_password[51]; // player account password
char user_email[101]; // player account email address
};
struct ServerLSStatus_Struct {
struct LoginserverWorldStatusUpdate {
int32 status;
int32 num_players;
int32 num_zones;
};
struct ZoneInfo_Struct {
struct LoginserverZoneInfoUpdate {
uint32 zone;
uint16 count;
uint32 zone_wid;

View File

@ -4,12 +4,11 @@ SET(eqlogin_sources
account_management.cpp
client.cpp
client_manager.cpp
database.cpp
encryption.cpp
loginserver_command_handler.cpp
loginserver_webserver.cpp
main.cpp
server_manager.cpp
world_server_manager.cpp
world_server.cpp
)
@ -17,14 +16,13 @@ SET(eqlogin_headers
account_management.h
client.h
client_manager.h
database.h
encryption.h
loginserver_command_handler.h
loginserver_webserver.h
login_server.h
login_types.h
options.h
server_manager.h
world_server_manager.h
world_server.h
)

View File

@ -1,259 +1,114 @@
#include "account_management.h"
#include "login_server.h"
#include "../common/event/task_scheduler.h"
#include "../common/event/event_loop.h"
#include "../common/net/dns.h"
#include "../common/strings.h"
#include "../common/repositories/login_accounts_repository.h"
extern LoginServer server;
EQ::Event::TaskScheduler task_runner;
/**
* @param username
* @param password
* @param email
* @param source_loginserver
* @param login_account_id
* @return
*/
int32 AccountManagement::CreateLoginServerAccount(
std::string username,
std::string password,
std::string email,
const std::string &source_loginserver,
uint32 login_account_id
)
uint64 AccountManagement::CreateLoginServerAccount(LoginAccountContext c)
{
auto mode = server.options.GetEncryptionMode();
auto hash = eqcrypt_hash(username, password, mode);
LogInfo(
"Attempting to create local login account for user [{0}] encryption algorithm [{1}] ({2})",
username,
GetEncryptionByModeId(mode),
mode
);
unsigned int db_id = 0;
if (server.db->DoesLoginServerAccountExist(username, hash, source_loginserver, 1)) {
if (LoginAccountsRepository::GetAccountFromContext(database, c).id > 0) {
LogWarning(
"Attempting to create local login account for user [{0}] login [{1}] but already exists!",
username,
source_loginserver
"Attempting to create local login account for user [{}] but already exists!",
c.username
);
return -1;
}
uint32 created_account_id = 0;
if (login_account_id > 0) {
created_account_id = server.db->CreateLoginDataWithID(username, hash, source_loginserver, login_account_id);
}
else {
created_account_id = server.db->CreateLoginAccount(username, hash, source_loginserver, email);
auto a = LoginAccountsRepository::CreateAccountFromContext(database, c);
if (a.id > 0) {
return (int64) a.id;
}
if (created_account_id > 0) {
LogInfo(
"Account creation success for user [{0}] encryption algorithm [{1}] ({2}) id: [{3}]",
username,
GetEncryptionByModeId(mode),
mode,
created_account_id
);
return (int32) created_account_id;
}
LogError("Failed to create local login account for user [{0}]!", username);
LogError("Failed to create local login account for user [{}] !", c.username);
return 0;
}
/**
* @param username
* @param password
* @param email
* @return
*/
bool AccountManagement::CreateLoginserverWorldAdminAccount(
const std::string &username,
const std::string &password,
const std::string &email,
const std::string &first_name,
const std::string &last_name,
const std::string &ip_address
)
uint64 AccountManagement::CheckLoginserverUserCredentials(LoginAccountContext c)
{
auto mode = server.options.GetEncryptionMode();
auto hash = eqcrypt_hash(username, password, mode);
LogInfo(
"Attempting to create world admin account | username [{0}] encryption algorithm [{1}] ({2})",
username,
GetEncryptionByModeId(mode),
mode
);
if (server.db->DoesLoginserverWorldAdminAccountExist(username)) {
LogWarning(
"Attempting to create world admin account for user [{0}] but already exists!",
username
);
return false;
}
uint32 created_world_admin_id = server.db->CreateLoginserverWorldAdminAccount(
username,
hash,
first_name,
last_name,
email,
ip_address
);
if (created_world_admin_id > 0) {
LogInfo(
"Account creation success for user [{0}] encryption algorithm [{1}] ({2}) new admin id [{3}]",
username,
GetEncryptionByModeId(mode),
mode,
created_world_admin_id
);
return true;
}
LogError("Failed to create world admin account account for user [{0}]!", username);
return false;
}
/**
* @param in_account_username
* @param in_account_password
* @return
*/
uint32 AccountManagement::CheckLoginserverUserCredentials(
const std::string &in_account_username,
const std::string &in_account_password,
const std::string &source_loginserver
)
{
auto mode = server.options.GetEncryptionMode();
Database::DbLoginServerAccount
login_server_admin = server.db->GetLoginServerAccountByAccountName(
in_account_username,
source_loginserver
);
if (!login_server_admin.loaded) {
auto a = LoginAccountsRepository::GetAccountFromContext(database, c);
if (!a.id) {
LogError(
"account [{0}] source_loginserver [{1}] not found!",
in_account_username,
source_loginserver
"account [{}] source_loginserver [{}] not found!",
c.username,
c.source_loginserver
);
return false;
return 0;
}
bool validated_credentials = eqcrypt_verify_hash(
in_account_username,
in_account_password,
login_server_admin.account_password,
mode
);
bool validated_credentials = eqcrypt_verify_hash(c.username, c.password, a.account_password, mode);
if (!validated_credentials) {
LogError(
"account [{0}] source_loginserver [{1}] invalid credentials!",
in_account_username,
source_loginserver
"account [{}] source_loginserver [{}] invalid credentials!",
c.username,
c.source_loginserver
);
return 0;
}
LogInfo(
"account [{0}] source_loginserver [{1}] credentials validated success!",
in_account_username,
source_loginserver
"account [{}] source_loginserver [{}] credentials validated success!",
c.username,
c.source_loginserver
);
return login_server_admin.id;
return a.id;
}
/**
* @param in_account_username
* @param in_account_password
* @return
*/
bool AccountManagement::UpdateLoginserverUserCredentials(
const std::string &in_account_username,
const std::string &in_account_password,
const std::string &source_loginserver
)
bool AccountManagement::UpdateLoginserverUserCredentials(LoginAccountContext c)
{
auto mode = server.options.GetEncryptionMode();
Database::DbLoginServerAccount
login_server_account = server.db->GetLoginServerAccountByAccountName(
in_account_username,
source_loginserver
);
if (!login_server_account.loaded) {
auto a = LoginAccountsRepository::GetAccountFromContext(database, c);
if (!a.id) {
LogError(
"account [{0}] source_loginserver [{1}] not found!",
in_account_username,
source_loginserver
"account [{}] source_loginserver [{}] not found!",
c.username,
c.source_loginserver
);
return false;
}
server.db->UpdateLoginserverAccountPasswordHash(
in_account_username,
source_loginserver,
eqcrypt_hash(
in_account_username,
in_account_password,
mode
)
);
LoginAccountsRepository::UpdateAccountPassword(database, a, c.password);
LogInfo(
"account [{0}] source_loginserver [{1}] credentials updated!",
in_account_username,
source_loginserver
"account [{}] source_loginserver [{}] credentials updated!",
c.username,
c.source_loginserver
);
return true;
}
/**
* @param in_account_username
* @param in_account_password
*/
bool AccountManagement::UpdateLoginserverWorldAdminAccountPasswordByName(
const std::string &in_account_username,
const std::string &in_account_password
)
bool AccountManagement::UpdateLoginserverWorldAdminAccountPasswordByName(LoginAccountContext c)
{
auto mode = server.options.GetEncryptionMode();
auto hash = eqcrypt_hash(in_account_username, in_account_password, mode);
bool updated_account = server.db->UpdateLoginWorldAdminAccountPasswordByUsername(
in_account_username,
hash
auto hash = eqcrypt_hash(
c.username,
c.password,
mode
);
auto a = LoginServerAdminsRepository::GetByName(database, c.username);
if (!a.id) {
LogError(
"account_name [{}] not found!",
c.username
);
return false;
}
a.account_password = hash;
auto updated_account = LoginServerAdminsRepository::UpdateOne(database, a);
LogInfo(
"[{}] account_name [{}] status [{}]",
__func__,
in_account_username,
"account_name [{}] status [{}]",
c.username,
(updated_account ? "success" : "failed")
);
@ -262,15 +117,7 @@ bool AccountManagement::UpdateLoginserverWorldAdminAccountPasswordByName(
constexpr int REQUEST_TIMEOUT_MS = 1500;
/**
* @param in_account_username
* @param in_account_password
* @return
*/
uint32 AccountManagement::CheckExternalLoginserverUserCredentials(
const std::string &in_account_username,
const std::string &in_account_password
)
uint64 AccountManagement::CheckExternalLoginserverUserCredentials(LoginAccountContext c)
{
auto res = task_runner.Enqueue(
[&]() -> uint32 {
@ -278,11 +125,11 @@ uint32 AccountManagement::CheckExternalLoginserverUserCredentials(
uint32 ret = 0;
EQ::Net::DaybreakConnectionManager mgr;
std::shared_ptr<EQ::Net::DaybreakConnection> c;
std::shared_ptr<EQ::Net::DaybreakConnection> conn;
mgr.OnNewConnection(
[&](std::shared_ptr<EQ::Net::DaybreakConnection> connection) {
c = connection;
conn = connection;
}
);
@ -296,7 +143,7 @@ uint32 AccountManagement::CheckExternalLoginserverUserCredentials(
EQ::Net::DynamicPacket p;
p.PutUInt16(0, 1); //OP_SessionReady
p.PutUInt32(2, 2);
c->QueuePacket(p);
conn->QueuePacket(p);
}
else if (EQ::Net::StatusDisconnected == to) {
running = false;
@ -310,13 +157,12 @@ uint32 AccountManagement::CheckExternalLoginserverUserCredentials(
switch (opcode) {
case 0x0017: //OP_ChatMessage
{
size_t buffer_len =
in_account_username.length() + in_account_password.length() + 2;
size_t buffer_len = c.username.length() + c.password.length() + 2;
std::unique_ptr<char[]> buffer(new char[buffer_len]);
strcpy(&buffer[0], in_account_username.c_str());
strcpy(&buffer[in_account_username.length() + 1], in_account_password.c_str());
strcpy(&buffer[0], c.username.c_str());
strcpy(&buffer[c.username.length() + 1], c.password.c_str());
size_t encrypted_len = buffer_len;
@ -330,11 +176,11 @@ uint32 AccountManagement::CheckExternalLoginserverUserCredentials(
p.PutUInt32(2, 3);
eqcrypt_block(&buffer[0], buffer_len, (char *) p.Data() + 12, true);
c->QueuePacket(p);
conn->QueuePacket(p);
break;
}
case 0x0018: {
auto encrypt_size = p.Length() - 12;
auto encrypt_size = p.Length() - 12;
if (encrypt_size % 8 > 0) {
encrypt_size = (encrypt_size / 8) * 8;
}
@ -394,15 +240,15 @@ uint32 AccountManagement::CheckExternalLoginserverUserCredentials(
return res.get();
}
uint32 AccountManagement::HealthCheckUserLogin()
uint64 AccountManagement::HealthCheckUserLogin()
{
std::string in_account_username = "healthcheckuser";
std::string in_account_password = "healthcheckpassword";
auto res = task_runner.Enqueue(
[&]() -> uint32 {
[&]() -> uint64 {
bool running = true;
uint32 ret = 0;
uint64 ret = 0;
EQ::Net::DaybreakConnectionManager mgr;
std::shared_ptr<EQ::Net::DaybreakConnection> c;
@ -461,7 +307,7 @@ uint32 AccountManagement::HealthCheckUserLogin()
break;
}
case 0x0018: {
auto encrypt_size = p.Length() - 12;
auto encrypt_size = p.Length() - 12;
if (encrypt_size % 8 > 0) {
encrypt_size = (encrypt_size / 8) * 8;
}
@ -487,11 +333,11 @@ uint32 AccountManagement::HealthCheckUserLogin()
mgr.Connect("127.0.0.1", 5999);
std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now();
auto &loop = EQ::EventLoop::Get();
auto &loop = EQ::EventLoop::Get();
while (running) {
std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
if (std::chrono::duration_cast<std::chrono::milliseconds>(end - begin).count() > 2000) {
ret = 0;
ret = 0;
running = false;
}
@ -504,3 +350,59 @@ uint32 AccountManagement::HealthCheckUserLogin()
return res.get();
}
bool AccountManagement::CreateLoginserverWorldAdminAccount(
const std::string &username,
const std::string &password,
const std::string &email,
const std::string &first_name,
const std::string &last_name,
const std::string &ip_address
)
{
auto mode = server.options.GetEncryptionMode();
auto hash = eqcrypt_hash(username, password, mode);
LogInfo(
"Attempting to create world admin account | username [{}] encryption algorithm [{}] ({})",
username,
GetEncryptionByModeId(mode),
mode
);
auto a = LoginServerAdminsRepository::GetByName(database, username);
if (a.id > 0) {
LogWarning(
"Attempting to create world admin account for user [{}] but already exists!",
username
);
return false;
}
a = LoginServerAdminsRepository::NewEntity();
a.account_name = username;
a.account_password = hash;
a.first_name = first_name;
a.last_name = last_name;
a.email = email;
a.registration_ip_address = ip_address;
a.registration_date = std::time(nullptr);
a = LoginServerAdminsRepository::InsertOne(database, a);
if (a.id > 0) {
LogInfo(
"Account creation success for user [{}] encryption algorithm [{}] ({}) new admin id [{}]",
username,
GetEncryptionByModeId(mode),
mode,
a.id
);
return true;
}
LogError("Failed to create world admin account account for user [{}] !", username);
return false;
}

View File

@ -3,32 +3,22 @@
#include "iostream"
#include "../common/types.h"
#include "login_types.h"
#include "encryption.h"
#include "login_server.h"
extern LoginServer server;
extern Database database;
class AccountManagement {
public:
static uint64 CreateLoginServerAccount(LoginAccountContext c);
static uint64 CheckLoginserverUserCredentials(LoginAccountContext c);
static bool UpdateLoginserverUserCredentials(LoginAccountContext c);
static uint64 CheckExternalLoginserverUserCredentials(LoginAccountContext c);
static bool UpdateLoginserverWorldAdminAccountPasswordByName(LoginAccountContext c);
static uint64 HealthCheckUserLogin();
/**
* @param username
* @param password
* @param email
* @param source_loginserver
* @param login_account_id
* @return
*/
static int32 CreateLoginServerAccount(
std::string username,
std::string password,
std::string email = "",
const std::string &source_loginserver = "local",
uint32 login_account_id = 0
);
/**
* @param username
* @param password
* @param email
* @return
*/
static bool CreateLoginserverWorldAdminAccount(
const std::string &username,
const std::string &password,
@ -37,50 +27,6 @@ public:
const std::string &last_name = "",
const std::string &ip_address = ""
);
/**
* @param in_account_username
* @param in_account_password
* @return
*/
static uint32 CheckLoginserverUserCredentials(
const std::string &in_account_username,
const std::string &in_account_password,
const std::string &source_loginserver = "local"
);
/**
* @param in_account_username
* @param in_account_password
* @return
*/
static bool UpdateLoginserverUserCredentials(
const std::string &in_account_username,
const std::string &in_account_password,
const std::string &source_loginserver = "local"
);
/**
* @param in_account_username
* @param in_account_password
* @return
*/
static uint32 CheckExternalLoginserverUserCredentials(
const std::string &in_account_username,
const std::string &in_account_password
);
/**
* @param in_account_username
* @param in_account_password
* @return
*/
static bool UpdateLoginserverWorldAdminAccountPasswordByName(
const std::string &in_account_username,
const std::string &in_account_password
);
static uint32 HealthCheckUserLogin();
};

View File

@ -1,25 +1,18 @@
#include "client.h"
#include "login_server.h"
#include "../common/misc_functions.h"
#include "../common/eqemu_logsys.h"
#include "../common/strings.h"
#include "encryption.h"
#include "account_management.h"
extern LoginServer server;
/**
* @param c
* @param v
*/
Client::Client(std::shared_ptr<EQStreamInterface> c, LSClientVersion v)
{
m_connection = c;
m_client_version = v;
m_client_status = cs_not_sent_session_ready;
m_account_id = 0;
m_play_server_id = 0;
m_play_sequence_id = 0;
m_connection = c;
m_client_version = v;
m_client_status = cs_not_sent_session_ready;
m_account_id = 0;
m_selected_play_server_id = 0;
m_play_sequence_id = 0;
}
bool Client::Process()
@ -43,8 +36,8 @@ bool Client::Process()
switch (app->GetOpcode()) {
case OP_SessionReady: {
LogInfo("Session ready received from client account {}", GetClientDescription());
Handle_SessionReady((const char *) app->pBuffer, app->Size());
LogInfo("Session ready received from client account {}", GetClientLoggingDescription());
HandleSessionReady((const char *) app->pBuffer, app->Size());
break;
}
case OP_Login: {
@ -53,9 +46,9 @@ bool Client::Process()
break;
}
LogInfo("Login received from client {}", GetClientDescription());
LogInfo("Login received from client {}", GetClientLoggingDescription());
Handle_Login((const char *) app->pBuffer, app->Size());
HandleLogin((const char *) app->pBuffer, app->Size());
break;
}
case OP_ServerListRequest: {
@ -64,18 +57,18 @@ bool Client::Process()
break;
}
LogInfo("Server list request received from client {}", GetClientDescription());
LogInfo("Server list request received from client {}", GetClientLoggingDescription());
SendServerListPacket(*(uint32_t *) app->pBuffer);
break;
}
case OP_PlayEverquestRequest: {
if (app->Size() < sizeof(PlayEverquestRequest_Struct)) {
if (app->Size() < sizeof(PlayEverquestRequest)) {
LogError("Play received but it is too small, discarding");
break;
}
Handle_Play((const char *) app->pBuffer);
SendPlayToWorld((const char *) app->pBuffer);
break;
}
}
@ -87,13 +80,7 @@ bool Client::Process()
return true;
}
/**
* Sends our reply to session ready packet
*
* @param data
* @param size
*/
void Client::Handle_SessionReady(const char *data, unsigned int size)
void Client::HandleSessionReady(const char *data, unsigned int size)
{
if (m_client_status != cs_not_sent_session_ready) {
LogError("Session ready received again after already being received");
@ -107,11 +94,8 @@ void Client::Handle_SessionReady(const char *data, unsigned int size)
m_client_status = cs_waiting_for_login;
/**
* The packets are identical between the two versions
*/
auto *outapp = new EQApplicationPacket(OP_ChatMessage, sizeof(LoginHandShakeReply_Struct));
auto buf = reinterpret_cast<LoginHandShakeReply_Struct*>(outapp->pBuffer);
auto *outapp = new EQApplicationPacket(OP_ChatMessage, sizeof(LoginHandShakeReply));
auto buf = reinterpret_cast<LoginHandShakeReply *>(outapp->pBuffer);
buf->base_header.sequence = 0x02;
buf->base_reply.success = true;
buf->base_reply.error_str_id = 0x65; // 101 "No Error"
@ -120,13 +104,7 @@ void Client::Handle_SessionReady(const char *data, unsigned int size)
delete outapp;
}
/**
* Verifies login and send a reply
*
* @param data
* @param size
*/
void Client::Handle_Login(const char *data, unsigned int size)
void Client::HandleLogin(const char *data, unsigned int size)
{
if (m_client_status != cs_waiting_for_login) {
LogError("Login received after already having logged in");
@ -134,41 +112,37 @@ void Client::Handle_Login(const char *data, unsigned int size)
}
// login user/pass are variable length after unencrypted opcode and base message header (size includes opcode)
constexpr int header_size = sizeof(uint16_t) + sizeof(LoginBaseMessage_Struct);
int data_size = size - header_size;
constexpr int header_size = sizeof(uint16_t) + sizeof(LoginBaseMessage);
int data_size = size - header_size;
if (size <= header_size) {
LogError("Login received packet of size: {0}, this would cause a buffer overflow, discarding", size);
LogError("Login received packet of size: {}, this would cause a buffer overflow, discarding", size);
return;
}
if (data_size % 8 != 0) {
LogError("Login received packet of size: {0}, this would cause a block corruption, discarding", size);
LogError("Login received packet of size: {}, this would cause a block corruption, discarding", size);
return;
}
char *login_packet_buffer = nullptr;
unsigned int db_account_id = 0;
std::string db_loginserver = "local";
if (server.options.CanAutoLinkAccounts()) {
if (std::getenv("LSPX")) {
db_loginserver = "eqemu";
}
std::string db_account_password_hash;
std::string outbuffer;
outbuffer.resize(data_size);
if (outbuffer.length() == 0) {
if (outbuffer.empty()) {
LogError("Corrupt buffer sent to server, no length");
return;
}
// data starts at base message header (opcode not included)
auto r = eqcrypt_block(data + sizeof(LoginBaseMessage_Struct), data_size, &outbuffer[0], 0);
auto r = eqcrypt_block(data + sizeof(LoginBaseMessage), data_size, &outbuffer[0], false);
if (r == nullptr) {
LogError("Failed to decrypt eqcrypt block");
return;
@ -182,131 +156,114 @@ void Client::Handle_Login(const char *data, unsigned int size)
return;
}
// std::cout << "User: " << user << std::endl;
// only need to copy the base header for reply options, ignore login info
memcpy(&m_llrs, data, sizeof(LoginBaseMessage_Struct));
memcpy(&m_login_base_message, data, sizeof(LoginBaseMessage));
bool result = false;
if (outbuffer[0] == 0 && outbuffer[1] == 0) {
// std::cout << "Seq: " << m_login_base_message.sequence << std::endl;
// std::cout << "compressed: " << m_login_base_message.compressed << std::endl;
// std::cout << "encrypt_type: " << m_login_base_message.encrypt_type << std::endl;
// std::cout << "unk3: " << m_login_base_message.unk3 << std::endl;
bool login_success = false;
bool token_login = outbuffer[0] == 0 && outbuffer[1] == 0;
if (token_login) {
if (server.options.IsTokenLoginAllowed()) {
cred = (&outbuffer[2 + user.length()]);
result = server.db->GetLoginTokenDataFromToken(
cred,
m_connection->GetRemoteAddr(),
db_account_id,
db_loginserver,
user
);
cred = (&outbuffer[2 + user.length()]);
// todo: implement token login
// SELECT login_server, username, account_id FROM login_tickets WHERE expires > NOW() AND id='{}' AND ip_address='{}' LIMIT 1
// login_success ? DoSuccessfulLogin(user, db_account_id, db_loginserver) : SendFailedLogin();
SendFailedLogin();
}
return;
}
else {
if (server.options.IsPasswordLoginAllowed()) {
cred = (&outbuffer[1 + user.length()]);
auto components = Strings::Split(user, ':');
if (components.size() == 2) {
db_loginserver = components[0];
user = components[1];
}
// health checks
if (ProcessHealthCheck(user)) {
DoFailedLogin();
return;
}
// normal login
cred = (&outbuffer[1 + user.length()]);
auto components = Strings::Split(user, ':');
if (components.size() == 2) {
db_loginserver = components[0];
user = components[1];
}
LogInfo(
"Attempting password based login [{0}] login [{1}]",
user,
db_loginserver
);
// health checks
if (ProcessHealthCheck(user)) {
SendFailedLogin();
return;
}
ParseAccountString(user, user, db_loginserver);
LogInfo(
"Attempting password based login [{}] login [{}]",
user,
db_loginserver
);
if (server.db->GetLoginDataFromAccountInfo(user, db_loginserver, db_account_password_hash, db_account_id)) {
result = VerifyLoginHash(user, db_loginserver, cred, db_account_password_hash);
ParseAccountString(user, user, db_loginserver);
#ifdef LSPX
// if user updated their password on the login server, update it here by validating their credentials with the login server
if (!result && db_loginserver == "eqemu") {
uint32 account_id = AccountManagement::CheckExternalLoginserverUserCredentials(user, cred);
if (account_id > 0) {
auto encryption_mode = server.options.GetEncryptionMode();
server.db->UpdateLoginserverAccountPasswordHash(
user,
db_loginserver,
eqcrypt_hash(user, cred, encryption_mode)
);
LogInfo("Updating eqemu account [{}] password hash", account_id);
result = true;
}
LoginAccountContext c = {};
c.username = user;
c.password = cred;
c.source_loginserver = db_loginserver;
auto a = LoginAccountsRepository::GetAccountFromContext(database, c);
if (a.id > 0) {
login_success = VerifyAndUpdateLoginHash(c, a);
// if user updated their password on the login server, update it here by validating their credentials with the login server
if (std::getenv("LSPX") && !login_success && db_loginserver == "eqemu") {
LogInfo("LSPX | Attempting login account via [{}]", db_loginserver);
uint32 account_id = AccountManagement::CheckExternalLoginserverUserCredentials(c);
LogInfo("LSPX | External login account id [{}]", account_id);
if (account_id > 0) {
auto updated_account = LoginAccountsRepository::UpdateAccountPassword(database, a, cred);
if (!updated_account.id) {
LogError("Failed to update eqemu account [{}] password hash", account_id);
SendFailedLogin();
return;
}
#endif
LogDebug("Success [{0}]", (result ? "true" : "false"));
}
else {
m_client_status = cs_creating_account;
AttemptLoginAccountCreation(user, cred, db_loginserver);
LogInfo("Updating eqemu account [{}] password hash", account_id);
DoSuccessfulLogin(updated_account);
return;
}
}
LogInfo("Successful login [{}]", (login_success ? "true" : "false"));
login_success ? DoSuccessfulLogin(a) : SendFailedLogin();
return;
}
/**
* Login accepted
*/
if (result) {
LogInfo(
"login [{0}] user [{1}] Login succeeded",
db_loginserver,
user
);
DoSuccessfulLogin(user, db_account_id, db_loginserver);
}
else {
LogInfo(
"login [{0}] user [{1}] Login failed",
db_loginserver,
user
);
DoFailedLogin();
}
// if we are here, the account does not exist
m_client_status = cs_creating_account;
AttemptLoginAccountCreation(c);
}
/**
* Sends a packet to the requested server to see if the client is allowed or not
*
* @param data
*/
void Client::Handle_Play(const char *data)
void Client::SendPlayToWorld(const char *data)
{
if (m_client_status != cs_logged_in) {
LogError("Client sent a play request when they were not logged in, discarding");
return;
}
const auto *play = (const PlayEverquestRequest_Struct *) data;
const auto *play = (const PlayEverquestRequest *) data;
auto server_id_in = (unsigned int) play->server_number;
auto sequence_in = (unsigned int) play->base_header.sequence;
LogInfo(
"[Handle_Play] Play received from client [{}] server number [{}] sequence [{}]",
"[SendPlayToWorld] Play received from client [{}] server number [{}] sequence [{}]",
GetAccountName(),
server_id_in,
sequence_in
);
m_play_server_id = (unsigned int) play->server_number;
m_play_sequence_id = sequence_in;
m_play_server_id = server_id_in;
server.server_manager->SendUserToWorldRequest(server_id_in, m_account_id, m_loginserver_name);
m_selected_play_server_id = (unsigned int) play->server_number;
m_play_sequence_id = sequence_in;
m_selected_play_server_id = server_id_in;
server.server_manager->SendUserLoginToWorldRequest(server_id_in, m_account_id, m_loginserver_name);
}
/**
* @param seq
*/
void Client::SendServerListPacket(uint32 seq)
{
auto app = server.server_manager->CreateServerListPacket(this, seq);
@ -316,11 +273,11 @@ void Client::SendServerListPacket(uint32 seq)
void Client::SendPlayResponse(EQApplicationPacket *outapp)
{
LogInfo("Sending play response for {}", GetClientDescription());
LogInfo("Sending play response for {}", GetClientLoggingDescription());
m_connection->QueuePacket(outapp);
}
void Client::GenerateKey()
void Client::GenerateRandomLoginKey()
{
m_key.clear();
int count = 0;
@ -339,229 +296,193 @@ void Client::GenerateKey()
}
}
/**
* @param user
* @param pass
* @param loginserver
*/
void Client::AttemptLoginAccountCreation(
const std::string &user,
const std::string &pass,
const std::string &loginserver
)
void Client::AttemptLoginAccountCreation(LoginAccountContext c)
{
LogInfo("user [{}] loginserver [{}]", user, loginserver);
LogInfo("user [{}] loginserver [{}]", c.username, c.source_loginserver);
#ifdef LSPX
if (loginserver == "eqemu") {
LogInfo("Attempting login account creation via '{0}'", loginserver);
if (!server.options.CanAutoLinkAccounts()) {
LogInfo("CanAutoLinkAccounts disabled - sending failed login");
DoFailedLogin();
return;
}
uint32 account_id = AccountManagement::CheckExternalLoginserverUserCredentials(
user,
pass
);
if (std::getenv("LSPX") && c.source_loginserver == "eqemu") {
LogInfo("LSPX | Attempting login account creation via [{}]", c.source_loginserver);
uint32 account_id = AccountManagement::CheckExternalLoginserverUserCredentials(c);
c.login_account_id = account_id;
if (account_id > 0) {
LogInfo("Found and creating eqemu account [{}]", account_id);
CreateEQEmuAccount(user, pass, account_id);
LogInfo("LSPX | Found and creating eqemu account [{}]", account_id);
auto a = LoginAccountsRepository::CreateAccountFromContext(database, c);
if (a.id > 0) {
DoSuccessfulLogin(a);
return;
}
}
LogInfo("LSPX | External authentication failed for user [{}]", c.username);
SendFailedLogin();
return;
}
if (server.options.CanAutoCreateAccounts() && c.source_loginserver == "local") {
LogInfo("CanAutoCreateAccounts enabled, attempting to crate account [{}]", c.username);
auto a = LoginAccountsRepository::CreateAccountFromContext(database, c);
if (a.id > 0) {
DoSuccessfulLogin(a);
return;
}
DoFailedLogin();
return;
}
#endif
if (server.options.CanAutoCreateAccounts() && loginserver == "local") {
LogInfo("CanAutoCreateAccounts enabled, attempting to creating account [{0}]", user);
CreateLocalAccount(user, pass);
return;
}
DoFailedLogin();
SendFailedLogin();
}
void Client::DoFailedLogin()
void Client::SendFailedLogin()
{
m_stored_user.clear();
m_stored_pass.clear();
m_stored_username.clear();
m_stored_password.clear();
// unencrypted
LoginBaseMessage_Struct base_header{};
base_header.sequence = m_llrs.sequence; // login (3)
base_header.encrypt_type = m_llrs.encrypt_type;
LoginBaseMessage h{};
h.sequence = m_login_base_message.sequence; // login (3)
h.encrypt_type = m_login_base_message.encrypt_type;
// encrypted
PlayerLoginReply_Struct login_reply{};
login_reply.base_reply.success = false;
login_reply.base_reply.error_str_id = 105; // Error - The username and/or password were not valid
PlayerLoginReply r{};
r.base_reply.success = false;
r.base_reply.error_str_id = 105; // Error - The username and/or password were not valid
char encrypted_buffer[80] = {0};
auto rc = eqcrypt_block((const char*)&login_reply, sizeof(login_reply), encrypted_buffer, 1);
auto rc = eqcrypt_block((const char *) &r, sizeof(r), encrypted_buffer, 1);
if (rc == nullptr) {
LogDebug("Failed to encrypt eqcrypt block for failed login");
}
constexpr int outsize = sizeof(LoginBaseMessage_Struct) + sizeof(encrypted_buffer);
constexpr int outsize = sizeof(LoginBaseMessage) + sizeof(encrypted_buffer);
EQApplicationPacket outapp(OP_LoginAccepted, outsize);
outapp.WriteData(&base_header, sizeof(base_header));
outapp.WriteData(&h, sizeof(h));
outapp.WriteData(&encrypted_buffer, sizeof(encrypted_buffer));
m_connection->QueuePacket(&outapp);
m_client_status = cs_failed_to_login;
}
/**
* Verifies a login hash, will also attempt to update a login hash if needed
*
* @param account_username
* @param source_loginserver
* @param account_password
* @param password_hash
* @return
*/
bool Client::VerifyLoginHash(
const std::string &account_username,
const std::string &source_loginserver,
const std::string &account_password,
const std::string &password_hash
)
bool Client::VerifyAndUpdateLoginHash(LoginAccountContext c, const LoginAccountsRepository::LoginAccounts &a)
{
auto encryption_mode = server.options.GetEncryptionMode();
if (eqcrypt_verify_hash(account_username, account_password, password_hash, encryption_mode)) {
if (eqcrypt_verify_hash(a.account_name, c.password, a.account_password, encryption_mode)) {
return true;
}
else {
if (server.options.IsUpdatingInsecurePasswords()) {
if (encryption_mode < EncryptionModeArgon2) {
encryption_mode = EncryptionModeArgon2;
}
uint32 insecure_source_encryption_mode = 0;
if (password_hash.length() == CryptoHash::md5_hash_length) {
for (int i = EncryptionModeMD5; i <= EncryptionModeMD5Triple; ++i) {
if (i != encryption_mode &&
eqcrypt_verify_hash(account_username, account_password, password_hash, i)) {
insecure_source_encryption_mode = i;
}
}
}
else if (password_hash.length() == CryptoHash::sha1_hash_length && insecure_source_encryption_mode == 0) {
for (int i = EncryptionModeSHA; i <= EncryptionModeSHATriple; ++i) {
if (i != encryption_mode &&
eqcrypt_verify_hash(account_username, account_password, password_hash, i)) {
insecure_source_encryption_mode = i;
}
}
}
else if (password_hash.length() == CryptoHash::sha512_hash_length && insecure_source_encryption_mode == 0) {
for (int i = EncryptionModeSHA512; i <= EncryptionModeSHA512Triple; ++i) {
if (i != encryption_mode &&
eqcrypt_verify_hash(account_username, account_password, password_hash, i)) {
insecure_source_encryption_mode = i;
}
}
}
if (encryption_mode < EncryptionModeArgon2) {
encryption_mode = EncryptionModeArgon2;
}
if (insecure_source_encryption_mode > 0) {
LogInfo(
"[{}] Updated insecure password user [{}] loginserver [{}] from mode [{}] ({}) to mode [{}] ({})",
__func__,
account_username,
source_loginserver,
GetEncryptionByModeId(insecure_source_encryption_mode),
insecure_source_encryption_mode,
GetEncryptionByModeId(encryption_mode),
encryption_mode
);
uint32 insecure_source_encryption_mode = 0;
server.db->UpdateLoginserverAccountPasswordHash(
account_username,
source_loginserver,
eqcrypt_hash(
account_username,
account_password,
encryption_mode
)
);
return true;
auto verify_encryption_mode = [&](int start, int end) {
for (int i = start; i <= end; ++i) {
if (i != encryption_mode && eqcrypt_verify_hash(a.account_name, c.password, a.account_password, i)) {
insecure_source_encryption_mode = i;
}
}
};
switch (a.account_password.length()) {
case CryptoHash::md5_hash_length:
verify_encryption_mode(EncryptionModeMD5, EncryptionModeMD5Triple);
break;
case CryptoHash::sha1_hash_length:
if (insecure_source_encryption_mode == 0) {
verify_encryption_mode(EncryptionModeSHA, EncryptionModeSHATriple);
}
break;
case CryptoHash::sha512_hash_length:
if (insecure_source_encryption_mode == 0) {
verify_encryption_mode(EncryptionModeSHA512, EncryptionModeSHA512Triple);
}
break;
}
if (insecure_source_encryption_mode > 0) {
LogInfo(
"Updated insecure password user [{}] loginserver [{}] from mode [{}] ({}) to mode [{}] ({})",
c.username,
c.source_loginserver,
GetEncryptionByModeId(insecure_source_encryption_mode),
insecure_source_encryption_mode,
GetEncryptionByModeId(encryption_mode),
encryption_mode
);
LoginAccountsRepository::UpdateAccountPassword(database, a, c.password);
return true;
}
return false;
}
/**
* @param in_account_name
* @param db_account_id
* @param db_loginserver
*/
void Client::DoSuccessfulLogin(
const std::string& in_account_name,
int db_account_id,
const std::string &db_loginserver
)
void Client::DoSuccessfulLogin(LoginAccountsRepository::LoginAccounts &a)
{
m_stored_user.clear();
m_stored_pass.clear();
m_stored_username.clear();
m_stored_password.clear();
server.client_manager->RemoveExistingClient(db_account_id, db_loginserver);
LogInfo(
"Successful login for user id [{}] account name [{}] login server [{}]",
a.id,
a.account_name,
a.source_loginserver
);
server.client_manager->RemoveExistingClient(a.id, a.source_loginserver);
in_addr in{};
in.s_addr = m_connection->GetRemoteIP();
server.db->UpdateLSAccountData(db_account_id, std::string(inet_ntoa(in)));
GenerateKey();
a.last_ip_address = std::string(inet_ntoa(in));
LoginAccountsRepository::UpdateOne(database, a);
m_account_id = db_account_id;
m_account_name = in_account_name;
m_loginserver_name = db_loginserver;
GenerateRandomLoginKey();
m_account_id = a.id;
m_account_name = a.account_name;
m_loginserver_name = a.source_loginserver;
// unencrypted
LoginBaseMessage_Struct base_header{};
base_header.sequence = m_llrs.sequence;
base_header.compressed = false;
base_header.encrypt_type = m_llrs.encrypt_type;
base_header.unk3 = m_llrs.unk3;
LoginBaseMessage h{};
h.sequence = m_login_base_message.sequence;
h.compressed = false;
h.encrypt_type = m_login_base_message.encrypt_type;
h.unk3 = m_login_base_message.unk3;
// not serializing any of the variable length strings so just use struct directly
PlayerLoginReply_Struct login_reply{};
login_reply.base_reply.success = true;
login_reply.base_reply.error_str_id = 101; // No Error
login_reply.unk1 = 0;
login_reply.unk2 = 0;
login_reply.lsid = db_account_id;
login_reply.failed_attempts = 0;
login_reply.show_player_count = server.options.IsShowPlayerCountEnabled();
login_reply.offer_min_days = 99;
login_reply.offer_min_views = -1;
login_reply.offer_cooldown_minutes = 0;
login_reply.web_offer_number = 0;
login_reply.web_offer_min_days = 99;
login_reply.web_offer_min_views = -1;
login_reply.web_offer_cooldown_minutes = 0;
memcpy(login_reply.key, m_key.c_str(), m_key.size());
PlayerLoginReply r{};
r.base_reply.success = true;
r.base_reply.error_str_id = 101; // No Error
r.unk1 = 0;
r.unk2 = 0;
r.lsid = a.id;
r.failed_attempts = 0;
r.show_player_count = server.options.IsShowPlayerCountEnabled();
r.offer_min_days = 99;
r.offer_min_views = -1;
r.offer_cooldown_minutes = 0;
r.web_offer_number = 0;
r.web_offer_min_days = 99;
r.web_offer_min_views = -1;
r.web_offer_cooldown_minutes = 0;
memcpy(r.key, m_key.c_str(), m_key.size());
SendExpansionPacketData(login_reply);
SendExpansionPacketData(r);
char encrypted_buffer[80] = {0};
auto rc = eqcrypt_block((const char*)&login_reply, sizeof(login_reply), encrypted_buffer, 1);
auto rc = eqcrypt_block((const char *) &r, sizeof(r), encrypted_buffer, 1);
if (rc == nullptr) {
LogDebug("Failed to encrypt eqcrypt block");
}
constexpr int outsize = sizeof(LoginBaseMessage_Struct) + sizeof(encrypted_buffer);
auto outapp = std::make_unique<EQApplicationPacket>(OP_LoginAccepted, outsize);
outapp->WriteData(&base_header, sizeof(base_header));
constexpr int outsize = sizeof(LoginBaseMessage) + sizeof(encrypted_buffer);
auto outapp = std::make_unique<EQApplicationPacket>(OP_LoginAccepted, outsize);
outapp->WriteData(&h, sizeof(h));
outapp->WriteData(&encrypted_buffer, sizeof(encrypted_buffer));
m_connection->QueuePacket(outapp.get());
@ -569,19 +490,21 @@ void Client::DoSuccessfulLogin(
m_client_status = cs_logged_in;
}
void Client::SendExpansionPacketData(PlayerLoginReply_Struct& plrs)
void Client::SendExpansionPacketData(PlayerLoginReply &plrs)
{
SerializeBuffer buf;
//from eqlsstr_us.txt id of each expansion, excluding 'Everquest'
int ExpansionLookup[20] = { 3007, 3008, 3009, 3010, 3012,
3014, 3031, 3033, 3036, 3040,
3045, 3046, 3047, 3514, 3516,
3518, 3520, 3522, 3524 };
int ExpansionLookup[20] = {
3007, 3008, 3009, 3010, 3012,
3014, 3031, 3033, 3036, 3040,
3045, 3046, 3047, 3514, 3516,
3518, 3520, 3522, 3524
};
if (server.options.IsDisplayExpansions()) {
int32_t expansion = server.options.GetMaxExpansions();
int32_t expansion = server.options.GetMaxExpansions();
int32_t owned_expansion = (expansion << 1) | 1;
if (m_client_version == cv_sod) {
@ -593,15 +516,14 @@ void Client::SendExpansionPacketData(PlayerLoginReply_Struct& plrs)
buf.WriteInt32(19); //number of expansions to include in packet
//generate expansion data
for (int i = 0; i < 19; i++)
{
buf.WriteInt32(i); //sequenctial number
buf.WriteInt32((expansion & (1 << i)) == (1 << i) ? 0x01 : 0x00); //1 own 0 not own
for (int i = 0; i < 19; i++) {
buf.WriteInt32(i); //sequenctial number
buf.WriteInt32((expansion & (1 << i)) == (1 << i) ? 0x01 : 0x00); //1 own 0 not own
buf.WriteInt8(0x00);
buf.WriteInt32(ExpansionLookup[i]); //from eqlsstr_us.txt
buf.WriteInt32(0x179E); //from eqlsstr_us.txt for buttons/order
buf.WriteInt32(0xFFFFFFFF); //end identification
buf.WriteInt8(0x0); //force order window to appear 1 appear 0 not appear
buf.WriteInt32(ExpansionLookup[i]); //from eqlsstr_us.txt
buf.WriteInt32(0x179E); //from eqlsstr_us.txt for buttons/order
buf.WriteInt32(0xFFFFFFFF); //end identification
buf.WriteInt8(0x0); //force order window to appear 1 appear 0 not appear
buf.WriteInt8(0x0);
buf.WriteInt32(0x0000);
buf.WriteInt32(0x0000);
@ -612,79 +534,21 @@ void Client::SendExpansionPacketData(PlayerLoginReply_Struct& plrs)
m_connection->QueuePacket(out.get());
}
else if (m_client_version == cv_titanium)
{
if (expansion >= EQ::expansions::bitPoR)
{
else if (m_client_version == cv_titanium) {
if (expansion >= EQ::expansions::bitPoR) {
// Titanium shipped with 10 expansions. Set owned expansions to be max 10.
plrs.offer_min_days = ((EQ::expansions::bitDoD << 2) | 1) - 2;
}
else
{
else {
plrs.offer_min_days = owned_expansion;
}
// Titanium shipped with 10 expansions. Set owned expansions to be max 10.
plrs.web_offer_min_views = ((EQ::expansions::bitDoD << 2) | 1) - 2;
}
}
}
/**
* @param username
* @param password
*/
void Client::CreateLocalAccount(const std::string &username, const std::string &password)
{
auto mode = server.options.GetEncryptionMode();
auto hash = eqcrypt_hash(username, password, mode);
unsigned int db_id = 0;
if (!server.db->CreateLoginData(username, hash, "local", db_id)) {
DoFailedLogin();
}
else {
DoSuccessfulLogin(username, db_id, "local");
}
}
/**
* @param in_account_name
* @param in_account_password
* @param loginserver_account_id
*/
void Client::CreateEQEmuAccount(
const std::string &in_account_name,
const std::string &in_account_password,
unsigned int loginserver_account_id
)
{
auto mode = server.options.GetEncryptionMode();
auto hash = eqcrypt_hash(in_account_name, in_account_password, mode);
if (server.db->DoesLoginServerAccountExist(in_account_name, hash, "eqemu", loginserver_account_id)) {
DoSuccessfulLogin(in_account_name, loginserver_account_id, "eqemu");
return;
}
if (!server.db->CreateLoginDataWithID(in_account_name, hash, "eqemu", loginserver_account_id)) {
DoFailedLogin();
}
else {
DoSuccessfulLogin(in_account_name, loginserver_account_id, "eqemu");
}
}
bool Client::ProcessHealthCheck(std::string username)
{
if (username == "healthcheckuser") {
return true;
}
return false;
}
std::string Client::GetClientDescription()
std::string Client::GetClientLoggingDescription()
{
in_addr in{};
in.s_addr = GetConnection()->GetRemoteIP();

View File

@ -8,204 +8,60 @@
#include "../common/net/dns.h"
#include "../common/net/daybreak_connection.h"
#include "login_types.h"
#include "../common/repositories/login_accounts_repository.h"
#include <memory>
/**
* Client class, controls a single client and it's connection to the login server
*/
class Client {
public:
/**
* Constructor, sets our connection to c and version to v
*
* @param c
* @param v
*/
Client(std::shared_ptr<EQStreamInterface> c, LSClientVersion v);
/**
* Destructor
*/
~Client() {}
/**
* Processes the client's connection and does various actions
*
* @return
*/
bool Process();
void HandleSessionReady(const char *data, unsigned int size);
void HandleLogin(const char *data, unsigned int size);
/**
* Sends our reply to session ready packet
*
* @param data
* @param size
*/
void Handle_SessionReady(const char *data, unsigned int size);
/**
* Verifies login and send a reply
*
* @param data
* @param size
*/
void Handle_Login(const char *data, unsigned int size);
/**
* Sends the expansion data packet
*
* Titanium uses the encrypted data block to contact the expansion (You own xxx:) and the max expansions (of yyy)
* Rof uses a seperate data packet specifically for the expansion data
* Live, as of July 2021 uses a similar but slightly different seperate data packet
*
* @param PlayerLoginReply_Struct
*
*/
void SendExpansionPacketData(PlayerLoginReply_Struct& plrs);
/**
* Sends a packet to the requested server to see if the client is allowed or not
*
* @param data
*/
void Handle_Play(const char *data);
/**
* Sends a server list packet to the client
*
* @param seq
*/
// Sends the expansion data packet
// Titanium uses the encrypted data block to contact the expansion (You own xxx:) and the max expansions (of yyy)
// Rof uses a separate data packet specifically for the expansion data
// Live, as of July 2021 uses a similar but slightly different seperate data packet
void SendExpansionPacketData(PlayerLoginReply &plrs);
void SendPlayToWorld(const char *data);
void SendServerListPacket(uint32 seq);
/**
* Sends the input packet to the client and clears our play response states
*
* @param outapp
*/
void SendPlayResponse(EQApplicationPacket *outapp);
/**
* Generates a random login key for the client during login
*/
void GenerateKey();
/**
* Gets the account id of this client
*
* @return
*/
void GenerateRandomLoginKey();
unsigned int GetAccountID() const { return m_account_id; }
/**
* Gets the loginserver name of this client
*
* @return
*/
std::string GetLoginServerName() const { return m_loginserver_name; }
/**
* Gets the account name of this client
*
* @return
*/
std::string GetAccountName() const { return m_account_name; }
/**
* Returns a description for the client for logging
* @return std::string
*/
std::string GetClientDescription();
/**
* Gets the key generated at login for this client
*
* @return
*/
std::string GetKey() const { return m_key; }
/**
* Gets the server selected to be played on for this client
*
* @return
*/
unsigned int GetPlayServerID() const { return m_play_server_id; }
/**
* Gets the play sequence state for this client
*
* @return
*/
unsigned int GetPlaySequence() const { return m_play_sequence_id; }
/**
* Gets the client version
*
* @return
*/
std::string GetClientLoggingDescription();
std::string GetLoginKey() const { return m_key; }
unsigned int GetSelectedPlayServerID() const { return m_selected_play_server_id; }
unsigned int GetCurrentPlaySequence() const { return m_play_sequence_id; }
LSClientVersion GetClientVersion() const { return m_client_version; }
/**
* Gets the connection for this client
*
* @return
*/
std::shared_ptr<EQStreamInterface> GetConnection() { return m_connection; }
/**
* Attempts to create a login account
*
* @param user
* @param pass
* @param loginserver
*/
void AttemptLoginAccountCreation(const std::string &user, const std::string &pass, const std::string &loginserver);
/**
* Does a failed login
*/
void DoFailedLogin();
/**
* Verifies a login hash, will also attempt to update a login hash if needed
*
* @param account_username
* @param source_loginserver
* @param account_password
* @param password_hash
* @return
*/
bool VerifyLoginHash(
const std::string &account_username,
const std::string &source_loginserver,
const std::string &account_password,
const std::string &password_hash
);
void DoSuccessfulLogin(const std::string& in_account_name, int db_account_id, const std::string &db_loginserver);
void CreateLocalAccount(const std::string &username, const std::string &password);
void CreateEQEmuAccount(const std::string &in_account_name, const std::string &in_account_password, unsigned int loginserver_account_id);
void AttemptLoginAccountCreation(LoginAccountContext c);
void SendFailedLogin();
bool VerifyAndUpdateLoginHash(LoginAccountContext c, const LoginAccountsRepository::LoginAccounts& a);
void DoSuccessfulLogin(LoginAccountsRepository::LoginAccounts& a);
private:
EQ::Random m_random;
std::shared_ptr<EQStreamInterface> m_connection;
LSClientVersion m_client_version;
LSClientStatus m_client_status;
std::string m_account_name;
unsigned int m_account_id;
std::string m_loginserver_name;
unsigned int m_play_server_id;
unsigned int m_play_sequence_id;
std::string m_key;
EQ::Random m_random;
std::shared_ptr<EQStreamInterface> m_connection;
LSClientVersion m_client_version;
LSClientStatus m_client_status;
std::string m_account_name;
unsigned int m_account_id;
std::string m_loginserver_name;
unsigned int m_selected_play_server_id;
unsigned int m_play_sequence_id;
std::string m_key;
std::unique_ptr<EQ::Net::DaybreakConnectionManager> m_login_connection_manager;
std::shared_ptr<EQ::Net::DaybreakConnection> m_login_connection;
LoginBaseMessage_Struct m_llrs;
std::string m_stored_user;
std::string m_stored_pass;
static bool ProcessHealthCheck(std::string username);
LoginBaseMessage m_login_base_message;
std::string m_stored_username;
std::string m_stored_password;
static bool ProcessHealthCheck(std::string username) {
return username == "healthcheckuser";
}
};
#endif

View File

@ -4,12 +4,12 @@
extern LoginServer server;
extern bool run_server;
#include "../common/eqemu_logsys.h"
#include "../common/misc.h"
#include "../common/path_manager.h"
#include "../common/file.h"
void CheckTitaniumOpcodeFile(const std::string &path) {
void CheckTitaniumOpcodeFile(const std::string &path)
{
if (File::Exists(path)) {
return;
}
@ -32,7 +32,8 @@ void CheckTitaniumOpcodeFile(const std::string &path) {
}
}
void CheckSoDOpcodeFile(const std::string& path) {
void CheckSoDOpcodeFile(const std::string &path)
{
if (File::Exists(path)) {
return;
}
@ -56,7 +57,8 @@ void CheckSoDOpcodeFile(const std::string& path) {
}
}
void CheckLarionOpcodeFile(const std::string& path) {
void CheckLarionOpcodeFile(const std::string &path)
{
if (File::Exists(path)) {
return;
}
@ -87,8 +89,8 @@ ClientManager::ClientManager()
EQStreamManagerInterfaceOptions titanium_opts(titanium_port, false, false);
titanium_stream = new EQ::Net::EQStreamManager(titanium_opts);
titanium_ops = new RegularOpcodeManager;
m_titanium_stream = new EQ::Net::EQStreamManager(titanium_opts);
m_titanium_ops = new RegularOpcodeManager;
std::string opcodes_path = fmt::format(
"{}/{}",
@ -98,34 +100,34 @@ ClientManager::ClientManager()
CheckTitaniumOpcodeFile(opcodes_path);
if (!titanium_ops->LoadOpcodes(opcodes_path.c_str())) {
if (!m_titanium_ops->LoadOpcodes(opcodes_path.c_str())) {
LogError(
"ClientManager fatal error: couldn't load opcodes for Titanium file [{0}]",
"ClientManager fatal error: couldn't load opcodes for Titanium file [{}]",
server.config.GetVariableString("client_configuration", "titanium_opcodes", "login_opcodes.conf")
);
run_server = false;
}
titanium_stream->OnNewConnection(
m_titanium_stream->OnNewConnection(
[this](std::shared_ptr<EQ::Net::EQStream> stream) {
LogInfo(
"New Titanium client connection from [{0}:{1}]",
"New Titanium client connection from [{}:{}]",
long2ip(stream->GetRemoteIP()),
stream->GetRemotePort()
);
stream->SetOpcodeManager(&titanium_ops);
stream->SetOpcodeManager(&m_titanium_ops);
Client *c = new Client(stream, cv_titanium);
clients.push_back(c);
m_clients.push_back(c);
}
);
int sod_port = server.config.GetVariableInt("client_configuration", "sod_port", 5999);
EQStreamManagerInterfaceOptions sod_opts(sod_port, false, false);
sod_stream = new EQ::Net::EQStreamManager(sod_opts);
sod_ops = new RegularOpcodeManager;
m_sod_stream = new EQ::Net::EQStreamManager(sod_opts);
m_sod_ops = new RegularOpcodeManager;
opcodes_path = fmt::format(
"{}/{}",
@ -135,26 +137,26 @@ ClientManager::ClientManager()
CheckSoDOpcodeFile(opcodes_path);
if (!sod_ops->LoadOpcodes(opcodes_path.c_str())) {
if (!m_sod_ops->LoadOpcodes(opcodes_path.c_str())) {
LogError(
"ClientManager fatal error: couldn't load opcodes for SoD file {0}",
"ClientManager fatal error: couldn't load opcodes for SoD file {}",
server.config.GetVariableString("client_configuration", "sod_opcodes", "login_opcodes.conf").c_str()
);
run_server = false;
}
sod_stream->OnNewConnection(
m_sod_stream->OnNewConnection(
[this](std::shared_ptr<EQ::Net::EQStream> stream) {
LogInfo(
"New SoD+ client connection from [{0}:{1}]",
"New SoD+ client connection from [{}:{}]",
long2ip(stream->GetRemoteIP()),
stream->GetRemotePort()
);
stream->SetOpcodeManager(&sod_ops);
stream->SetOpcodeManager(&m_sod_ops);
auto *c = new Client(stream, cv_sod);
clients.push_back(c);
m_clients.push_back(c);
}
);
@ -162,8 +164,8 @@ ClientManager::ClientManager()
EQStreamManagerInterfaceOptions larion_opts(larion_port, false, false);
larion_stream = new EQ::Net::EQStreamManager(larion_opts);
larion_ops = new RegularOpcodeManager;
m_larion_stream = new EQ::Net::EQStreamManager(larion_opts);
m_larion_ops = new RegularOpcodeManager;
opcodes_path = fmt::format(
"{}/{}",
@ -173,115 +175,98 @@ ClientManager::ClientManager()
CheckLarionOpcodeFile(opcodes_path);
if (!larion_ops->LoadOpcodes(opcodes_path.c_str())) {
if (!m_larion_ops->LoadOpcodes(opcodes_path.c_str())) {
LogError(
"ClientManager fatal error: couldn't load opcodes for Larion file [{0}]",
"ClientManager fatal error: couldn't load opcodes for Larion file [{}]",
server.config.GetVariableString("client_configuration", "larion_opcodes", "login_opcodes.conf")
);
run_server = false;
}
larion_stream->OnNewConnection(
m_larion_stream->OnNewConnection(
[this](std::shared_ptr<EQ::Net::EQStream> stream) {
LogInfo(
"New Larion client connection from [{0}:{1}]",
"New Larion client connection from [{}:{}]",
long2ip(stream->GetRemoteIP()),
stream->GetRemotePort()
);
stream->SetOpcodeManager(&larion_ops);
Client* c = new Client(stream, cv_larion);
clients.push_back(c);
stream->SetOpcodeManager(&m_larion_ops);
Client *c = new Client(stream, cv_larion);
m_clients.push_back(c);
}
);
}
ClientManager::~ClientManager()
{
if (titanium_stream) {
delete titanium_stream;
}
if (titanium_ops) {
delete titanium_ops;
}
if (sod_stream) {
delete sod_stream;
}
if (sod_ops) {
delete sod_ops;
}
ClientManager::~ClientManager() {
delete m_titanium_stream;
delete m_titanium_ops;
delete m_sod_stream;
delete m_sod_ops;
}
void ClientManager::Process()
{
ProcessDisconnect();
auto iter = clients.begin();
while (iter != clients.end()) {
if ((*iter)->Process() == false) {
LogWarning("Client had a fatal error and had to be removed from the login");
delete (*iter);
iter = clients.erase(iter);
}
else {
++iter;
}
}
m_clients.erase(
std::remove_if(
m_clients.begin(), m_clients.end(),
[](Client *c) {
if (!c->Process()) {
LogWarning("Client had a fatal error and had to be removed from the login");
delete c;
return true;
}
return false;
}
),
m_clients.end());
}
void ClientManager::ProcessDisconnect()
{
auto iter = clients.begin();
while (iter != clients.end()) {
std::shared_ptr<EQStreamInterface> c = (*iter)->GetConnection();
if (c->CheckState(CLOSED)) {
LogInfo("Client disconnected from the server, removing client");
delete (*iter);
iter = clients.erase(iter);
}
else {
++iter;
}
}
m_clients.erase(
std::remove_if(
m_clients.begin(), m_clients.end(),
[](Client *c) {
if (c->GetConnection()->CheckState(CLOSED)) {
LogInfo("Client disconnected from the server, removing client");
delete c;
return true;
}
return false;
}
),
m_clients.end());
}
/**
* @param account_id
* @param loginserver
*/
void ClientManager::RemoveExistingClient(unsigned int account_id, const std::string &loginserver)
{
auto iter = clients.begin();
while (iter != clients.end()) {
if ((*iter)->GetAccountID() == account_id && (*iter)->GetLoginServerName().compare(loginserver) == 0) {
LogInfo("Client attempting to log in existing client already logged in, removing existing client");
delete (*iter);
iter = clients.erase(iter);
}
else {
++iter;
}
}
m_clients.erase(
std::remove_if(
m_clients.begin(), m_clients.end(),
[&](Client *c) {
if (c->GetAccountID() == account_id && c->GetLoginServerName() == loginserver) {
LogInfo("Client attempting to log in existing client already logged in, removing existing client");
delete c;
return true;
}
return false;
}
),
m_clients.end());
}
/**
* @param account_id
* @param loginserver
* @return
*/
Client *ClientManager::GetClient(unsigned int account_id, const std::string &loginserver)
{
auto iter = clients.begin();
while (iter != clients.end()) {
if ((*iter)->GetAccountID() == account_id && (*iter)->GetLoginServerName().compare(loginserver) == 0) {
return (*iter);
auto iter = std::find_if(
m_clients.begin(), m_clients.end(),
[&](Client *c) {
return c->GetAccountID() == account_id && c->GetLoginServerName() == loginserver;
}
++iter;
}
);
return nullptr;
return (iter != m_clients.end()) ? *iter : nullptr;
}

View File

@ -7,56 +7,23 @@
#include "client.h"
#include <list>
/**
* Client manager class, holds all the client objects and does basic processing.
*/
class ClientManager {
public:
/**
* Constructor: sets up the stream factories and opcode managers
*/
ClientManager();
/**
* Destructor: shuts down the streams and opcode managers
*/
~ClientManager();
/**
* Processes every client in the internal list, removes them if necessary.
*/
void Process();
/**
* Removes a client with a certain account id
*
* @param account_id
* @param loginserver
*/
void RemoveExistingClient(unsigned int account_id, const std::string &loginserver);
/**
* Gets a client (if exists) by their account id
*
* @param account_id
* @param loginserver
* @return
*/
Client *GetClient(unsigned int account_id, const std::string &loginserver);
void RemoveExistingClient(unsigned int c, const std::string &loginserver);
Client *GetClient(unsigned int c, const std::string &loginserver);
private:
/**
* Processes disconnected clients, removes them if necessary
*/
void ProcessDisconnect();
std::list<Client *> clients;
OpcodeManager *titanium_ops;
EQ::Net::EQStreamManager *titanium_stream;
OpcodeManager *sod_ops;
EQ::Net::EQStreamManager *sod_stream;
OpcodeManager *larion_ops;
EQ::Net::EQStreamManager* larion_stream;
std::list<Client *> m_clients;
OpcodeManager *m_titanium_ops;
EQ::Net::EQStreamManager *m_titanium_stream;
OpcodeManager *m_sod_ops;
EQ::Net::EQStreamManager *m_sod_stream;
OpcodeManager *m_larion_ops;
EQ::Net::EQStreamManager *m_larion_stream;
};
#endif

View File

@ -1,680 +0,0 @@
#include "../common/global_define.h"
#include "database.h"
#include "login_server.h"
#include "../common/eqemu_logsys.h"
#include "../common/strings.h"
#include "../common/util/uuid.h"
extern LoginServer server;
/**
* Initial connect
*
* @param user
* @param pass
* @param host
* @param port
* @param name
*/
Database::Database(
std::string user,
std::string pass,
std::string host,
std::string port,
std::string name
)
{
uint32 errnum = 0;
char errbuf[MYSQL_ERRMSG_SIZE];
if (!Open(
host.c_str(),
user.c_str(),
pass.c_str(),
name.c_str(),
Strings::ToUnsignedInt(port),
&errnum,
errbuf
)
) {
LogError("Failed to connect to database: Error: [{0}]", errbuf);
exit(1);
}
else {
LogInfo("Using database [{0}] at [{1}:{2}]", name, host, port);
}
}
/**
* Deconstructor
*/
Database::~Database()
{
if (m_database) {
mysql_close(m_database);
}
}
/**
* @param name
* @param loginserver
* @param password
* @param id
* @return
*/
bool Database::GetLoginDataFromAccountInfo(
const std::string &name,
const std::string &loginserver,
std::string &password,
unsigned int &id
)
{
auto query = fmt::format(
"SELECT id, account_password FROM login_accounts WHERE account_name = '{0}' AND source_loginserver = '{1}' LIMIT 1",
Strings::Escape(name),
Strings::Escape(loginserver)
);
auto results = QueryDatabase(query);
if (results.RowCount() != 1) {
LogDebug(
"Could not find account for name [{0}] login [{1}]",
name,
loginserver
);
return false;
}
if (!results.Success()) {
return false;
}
auto row = results.begin();
id = Strings::ToUnsignedInt(row[0]);
password = row[1];
LogDebug(
"Found account for name [{0}] login [{1}]",
name,
loginserver
);
return true;
}
/**
* @param token
* @param ip
* @param db_account_id
* @param db_loginserver
* @param user
* @return
*/
bool Database::GetLoginTokenDataFromToken(
const std::string &token,
const std::string &ip,
unsigned int &db_account_id,
std::string &db_loginserver,
std::string &user
)
{
auto query = fmt::format("SELECT login_server, username, account_id FROM login_tickets WHERE expires > NOW()"
" AND id='{0}' AND ip_address='{1}' LIMIT 1",
Strings::Escape(token),
Strings::Escape(ip));
auto results = QueryDatabase(query);
if (results.RowCount() == 0 || !results.Success()) {
return false;
}
for (auto row = results.begin(); row != results.end(); ++row) {
db_loginserver = row[0];
user = row[1];
db_account_id = Strings::ToUnsignedInt(row[2]);
return true;
}
return false;
}
/**
* @param loginserver
* @return
*/
unsigned int Database::GetFreeID(const std::string &loginserver)
{
auto query = fmt::format(
"SELECT IFNULL(MAX(id), 0) + 1 FROM login_accounts WHERE source_loginserver = '{0}'",
Strings::Escape(loginserver)
);
auto results = QueryDatabase(query);
if (!results.Success() || results.RowCount() != 1) {
return 0;
}
auto row = results.begin();
return Strings::ToUnsignedInt(row[0]);
}
/**
* @param name
* @param password
* @param loginserver
* @param id
* @return
*/
bool Database::CreateLoginData(
const std::string &name,
const std::string &password,
const std::string &loginserver,
unsigned int &id
)
{
uint32 free_id = GetFreeID(loginserver);
id = free_id;
return CreateLoginDataWithID(name, password, loginserver, free_id);
}
/**
* @param name
* @param password
* @param loginserver
* @param email
* @return
*/
uint32 Database::CreateLoginAccount(
const std::string &name,
const std::string &password,
const std::string &loginserver,
const std::string &email
)
{
uint32 free_id = GetFreeID(loginserver);
if (free_id <= 0) {
return 0;
}
auto query = fmt::format(
"INSERT INTO login_accounts (id, source_loginserver, account_name, account_password, account_email, last_login_date, last_ip_address, created_at) "
"VALUES ({0}, '{1}', '{2}', '{3}', '{4}', NOW(), '127.0.0.1', NOW())",
free_id,
Strings::Escape(loginserver),
Strings::Escape(name),
Strings::Escape(password),
Strings::Escape(email)
);
auto results = QueryDatabase(query);
return (results.Success() ? free_id : 0);
}
/**
* @param in_account_name
* @param in_account_password
* @param loginserver
* @param id
* @return
*/
bool Database::CreateLoginDataWithID(
const std::string &in_account_name,
const std::string &in_account_password,
const std::string &loginserver,
unsigned int id
)
{
if (id == 0) {
return false;
}
auto query = fmt::format(
"INSERT INTO login_accounts (id, source_loginserver, account_name, account_password, account_email, last_login_date, last_ip_address, created_at) "
"VALUES ({0}, '{1}', '{2}', '{3}', 'local_creation', NOW(), '127.0.0.1', NOW())",
id,
Strings::Escape(loginserver),
Strings::Escape(in_account_name),
Strings::Escape(in_account_password)
);
auto results = QueryDatabase(query);
return results.Success();
}
/**
* @param name
* @param password
* @param loginserver
* @param id
* @return
*/
bool Database::DoesLoginServerAccountExist(
const std::string &name,
const std::string &password,
const std::string &loginserver,
unsigned int id
)
{
if (id == 0) {
return false;
}
auto query = fmt::format(
"SELECT account_name FROM login_accounts WHERE account_name = '{0}' AND source_loginserver = '{1}'",
Strings::Escape(name),
Strings::Escape(loginserver)
);
auto results = QueryDatabase(query);
if (!results.Success() || results.RowCount() != 1) {
return false;
}
return true;
}
/**
* @param name
* @param loginserver
* @param hash
*/
void Database::UpdateLoginserverAccountPasswordHash(
const std::string &name,
const std::string &loginserver,
const std::string &hash
)
{
LogDebug(
"name [{0}] loginserver [{1}] hash [{2}]",
name,
loginserver,
hash
);
auto query = fmt::format(
"UPDATE login_accounts SET account_password = '{0}' WHERE account_name = '{1}' AND source_loginserver = '{2}'",
hash,
Strings::Escape(name),
Strings::Escape(loginserver)
);
QueryDatabase(query);
}
/**
* @param short_name
* @param long_name
* @param login_world_server_admin_id
* @return
*/
Database::DbWorldRegistration Database::GetWorldRegistration(
const std::string &short_name,
const std::string &long_name,
uint32 login_world_server_admin_id
)
{
auto query = fmt::format(
"SELECT\n"
" WSR.id,\n"
" WSR.tag_description,\n"
" WSR.is_server_trusted,\n"
" SLT.id,\n"
" SLT.description,\n"
" ifnull(WSR.login_server_admin_id, 0) AS login_server_admin_id\n"
"FROM\n"
" login_world_servers AS WSR\n"
" JOIN login_server_list_types AS SLT ON WSR.login_server_list_type_id = SLT.id\n"
"WHERE\n"
" WSR.short_name = '{}' AND WSR.long_name = '{}' AND WSR.login_server_admin_id = {} LIMIT 1",
Strings::Escape(short_name),
Strings::Escape(long_name),
login_world_server_admin_id
);
Database::DbWorldRegistration r{};
auto results = QueryDatabase(query);
if (!results.Success() || results.RowCount() != 1) {
return r;
}
auto row = results.begin();
r.loaded = true;
r.server_id = Strings::ToInt(row[0]);
r.server_description = row[1];
r.server_list_type = Strings::ToInt(row[3]);
r.is_server_trusted = Strings::ToInt(row[2]) > 0;
r.server_list_description = row[4];
r.server_admin_id = Strings::ToUnsignedInt(row[5]);
if (r.server_admin_id <= 0) {
return r;
}
auto world_registration_query = fmt::format(
"SELECT account_name, account_password FROM login_server_admins WHERE id = {0} LIMIT 1",
r.server_admin_id
);
auto world_registration_results = QueryDatabase(world_registration_query);
if (world_registration_results.Success() && world_registration_results.RowCount() == 1) {
auto world_registration_row = world_registration_results.begin();
r.server_admin_account_name = world_registration_row[0];
r.server_admin_account_password = world_registration_row[1];
}
return r;
}
/**
* @param id
* @param ip_address
*/
void Database::UpdateLSAccountData(unsigned int id, std::string ip_address)
{
auto query = fmt::format(
"UPDATE login_accounts SET last_ip_address = '{0}', last_login_date = NOW() where id = {1}",
ip_address,
id
);
QueryDatabase(query);
}
/**
* @param id
* @param name
* @param password
* @param email
*/
void Database::UpdateLSAccountInfo(
unsigned int id,
std::string name,
std::string password,
std::string email
)
{
auto query = fmt::format(
"REPLACE login_accounts SET id = {0}, account_name = '{1}', account_password = sha('{2}'), "
"account_email = '{3}', last_ip_address = '0.0.0.0', last_login_date = now()",
id,
Strings::Escape(name),
Strings::Escape(password),
Strings::Escape(email)
);
QueryDatabase(query);
}
/**
* @param id
* @param long_name
* @param ip_address
*/
void Database::UpdateWorldRegistration(unsigned int id, std::string long_name, std::string ip_address)
{
auto query = fmt::format(
"UPDATE login_world_servers SET last_login_date = NOW(), last_ip_address = '{0}', long_name = '{1}' WHERE id = {2}",
ip_address,
Strings::Escape(long_name),
id
);
QueryDatabase(query);
}
/**
* @param id
* @param admin_account_password_hash
*/
bool Database::UpdateLoginWorldAdminAccountPassword(
unsigned int id,
const std::string &admin_account_password_hash
)
{
auto results = QueryDatabase(
fmt::format(
"UPDATE login_server_admins SET account_password = '{}' WHERE id = {}",
Strings::Escape(admin_account_password_hash),
id
)
);
return results.Success();
}
/**
*
* @param admin_account_username
* @param admin_account_password_hash
*/
bool Database::UpdateLoginWorldAdminAccountPasswordByUsername(
const std::string &admin_account_username,
const std::string &admin_account_password_hash
)
{
auto results = QueryDatabase(
fmt::format(
"UPDATE login_server_admins SET account_password = '{}' WHERE account_name = '{}'",
Strings::Escape(admin_account_password_hash),
Strings::Escape(admin_account_username)
)
);
return results.Success();
}
/**
* @param server_long_name
* @param server_short_name
* @param id
* @return
*/
bool Database::CreateWorldRegistration(
std::string server_long_name,
std::string server_short_name,
std::string server_remote_ip,
unsigned int &id,
unsigned int &server_admin_id
)
{
auto results = QueryDatabase("SELECT IFNULL(max(id), 0) + 1 FROM login_world_servers");
if (!results.Success() || results.RowCount() != 1) {
return false;
}
auto row = results.begin();
id = Strings::ToUnsignedInt(row[0]);
auto insert_query = fmt::format(
"INSERT INTO login_world_servers SET id = {0}, long_name = '{1}', short_name = '{2}', last_ip_address = '{3}', \n"
"login_server_list_type_id = 3, login_server_admin_id = {4}, is_server_trusted = 0, tag_description = ''",
id,
Strings::Escape(server_long_name),
Strings::Escape(server_short_name),
server_remote_ip,
server_admin_id
);
auto insert_results = QueryDatabase(insert_query);
if (!insert_results.Success()) {
LogError(
"Failed to register world server {0} - {1}",
server_long_name,
server_short_name
);
return false;
}
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 login_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 login_api_tokens");
}
/**
* @param account_name
* @param account_password
* @param first_name
* @param last_name
* @param email
* @param ip_address
* @return
*/
uint32 Database::CreateLoginserverWorldAdminAccount(
const std::string &account_name,
const std::string &account_password,
const std::string &first_name,
const std::string &last_name,
const std::string &email,
const std::string &ip_address
)
{
auto query = fmt::format(
"INSERT INTO login_server_admins (account_name, account_password, first_name, last_name, email, registration_date, "
"registration_ip_address) "
"VALUES ('{0}', '{1}', '{2}', '{3}', '{4}', NOW(), '{5}')",
Strings::Escape(account_name),
Strings::Escape(account_password),
Strings::Escape(first_name),
Strings::Escape(last_name),
Strings::Escape(email),
ip_address
);
auto results = QueryDatabase(query);
return (results.Success() ? results.LastInsertedID() : 0);
}
/**
* @param account_name
* @return
*/
bool Database::DoesLoginserverWorldAdminAccountExist(
const std::string &account_name
)
{
auto query = fmt::format(
"SELECT account_name FROM login_server_admins WHERE account_name = '{0}' LIMIT 1",
Strings::Escape(account_name)
);
auto results = QueryDatabase(query);
return (results.RowCount() == 1);
}
/**
* @param account_name
* @return
*/
Database::DbLoginServerAdmin Database::GetLoginServerAdmin(const std::string &account_name)
{
auto query = fmt::format(
"SELECT id, account_name, account_password, first_name, last_name, email, registration_date, registration_ip_address"
" FROM login_server_admins WHERE account_name = '{0}' LIMIT 1",
Strings::Escape(account_name)
);
auto results = QueryDatabase(query);
Database::DbLoginServerAdmin r{};
if (results.RowCount() == 1) {
auto row = results.begin();
r.loaded = true;
r.id = Strings::ToUnsignedInt(row[0]);
r.account_name = row[1];
r.account_password = row[2];
r.first_name = row[3];
r.last_name = row[4];
r.email = row[5];
r.registration_date = row[7];
r.registration_ip_address = row[8];
}
return r;
}
/**
* @param account_name
* @return
*/
Database::DbLoginServerAccount Database::GetLoginServerAccountByAccountName(
const std::string &account_name,
const std::string &source_loginserver
)
{
auto query = fmt::format(
"SELECT id, account_name, account_password, account_email, source_loginserver, last_ip_address, last_login_date, "
"created_at, updated_at"
" FROM login_accounts WHERE account_name = '{0}' and source_loginserver = '{1}' LIMIT 1",
Strings::Escape(account_name),
Strings::Escape(source_loginserver)
);
auto results = QueryDatabase(query);
Database::DbLoginServerAccount r{};
if (results.RowCount() == 1) {
auto row = results.begin();
r.loaded = true;
r.id = Strings::ToUnsignedInt(row[0]);
r.account_name = row[1];
r.account_password = row[2];
r.account_email = row[3];
r.source_loginserver = row[4];
r.last_ip_address = row[5];
r.last_login_date = row[6];
r.created_at = row[7];
r.updated_at = row[8];
}
return r;
}

View File

@ -1,286 +0,0 @@
#ifndef EQEMU_DATABASEMYSQL_H
#define EQEMU_DATABASEMYSQL_H
#include "../common/dbcore.h"
#include "../common/eqemu_logsys.h"
#include <string>
#include <sstream>
#include <stdlib.h>
#include <mysql.h>
class Database : public DBcore {
public:
Database() { m_database = nullptr; }
/**
* Constructor, tries to set our database to connect to the supplied options.
*
* @param user
* @param pass
* @param host
* @param port
* @param name
*/
Database(std::string user, std::string pass, std::string host, std::string port, std::string name);
/**
* Destructor, frees our database if needed.
*/
~Database();
/**
* Retrieves the login data (password hash and account id) from the account name provided needed for client login procedure.
* @param name
* @param loginserver
* @param password
* @param id
* @return
*/
bool GetLoginDataFromAccountInfo(
const std::string &name,
const std::string &loginserver,
std::string &password,
unsigned int &id
);
/**
* @param token
* @param ip
* @param db_account_id
* @param db_loginserver
* @param user
* @return
*/
bool GetLoginTokenDataFromToken(
const std::string &token,
const std::string &ip,
unsigned int &db_account_id,
std::string &db_loginserver,
std::string &user
);
/**
* @param loginserver
* @return
*/
unsigned int GetFreeID(const std::string &loginserver);
/**
* @param name
* @param password
* @param loginserver
* @param id
* @return
*/
bool CreateLoginData(
const std::string &name,
const std::string &password,
const std::string &loginserver,
unsigned int &id
);
/**
* @param in_account_name
* @param in_account_password
* @param loginserver
* @param id
* @return
*/
bool CreateLoginDataWithID(
const std::string &in_account_name,
const std::string &in_account_password,
const std::string &loginserver,
unsigned int id
);
/**
* @param name
* @param loginserver
* @param hash
*/
void UpdateLoginserverAccountPasswordHash(
const std::string &name,
const std::string &loginserver,
const std::string &hash);
/**
* @param name
* @param password
* @param loginserver
* @param id
* @return
*/
bool DoesLoginServerAccountExist(
const std::string &name,
const std::string &password,
const std::string &loginserver,
unsigned int id
);
struct DbWorldRegistration {
bool loaded = false;
int32 server_id = 0;
int8 server_list_type = 3;
bool is_server_trusted = false;
std::string server_description;
std::string server_list_description;
std::string server_admin_account_name;
std::string server_admin_account_password;
uint32 server_admin_id;
};
/**
* Retrieves the world registration from the long and short names provided
* Needed for world login procedure
* Returns true if the record was found, false otherwise
*
* @param short_name
* @param long_name
* @param login_world_server_admin_id
* @return
*/
Database::DbWorldRegistration GetWorldRegistration(
const std::string &short_name,
const std::string &long_name,
uint32 login_world_server_admin_id
);
/**
* @param id
* @param ip_address
*/
void UpdateLSAccountData(unsigned int id, std::string ip_address);
/**
* @param id
* @param name
* @param password
* @param email
*/
void UpdateLSAccountInfo(unsigned int id, std::string name, std::string password, std::string email);
/**
* @param id
* @param long_name
* @param ip_address
*/
void UpdateWorldRegistration(unsigned int id, std::string long_name, std::string ip_address);
/**
* @param server_long_name
* @param server_short_name
* @param id
* @return
*/
bool CreateWorldRegistration(
std::string server_long_name,
std::string server_short_name,
std::string server_remote_ip,
unsigned int &id,
unsigned int &server_admin_id
);
/**
* @param write_mode
* @param read_mode
* @return
*/
std::string CreateLoginserverApiToken(bool write_mode, bool read_mode);
MySQLRequestResult GetLoginserverApiTokens();
/**
* @param account_name
* @param account_password
* @param first_name
* @param last_name
* @param email
* @param ip_address
* @return
*/
uint32 CreateLoginserverWorldAdminAccount(
const std::string &account_name,
const std::string &account_password,
const std::string &first_name,
const std::string &last_name,
const std::string &email,
const std::string &ip_address
);
/**
* @param account_name
* @return
*/
bool DoesLoginserverWorldAdminAccountExist(const std::string &account_name);
struct DbLoginServerAdmin {
bool loaded = false;
uint32 id;
std::string account_name;
std::string account_password;
std::string first_name;
std::string last_name;
std::string email;
std::string registration_date;
std::string registration_ip_address;
};
Database::DbLoginServerAdmin GetLoginServerAdmin(const std::string &account_name);
struct DbLoginServerAccount {
bool loaded = false;
uint32 id;
std::string account_name;
std::string account_password;
std::string account_email;
std::string source_loginserver;
std::string last_login_date;
std::string last_ip_address;
std::string created_at;
std::string updated_at;
};
Database::DbLoginServerAccount GetLoginServerAccountByAccountName(
const std::string &account_name,
const std::string &source_loginserver = "local"
);
/**
* @param id
* @param admin_account_password_hash
*/
bool UpdateLoginWorldAdminAccountPassword(
unsigned int id,
const std::string& admin_account_password_hash
);
/**
* @param admin_account_username
* @param admin_account_password_hash
*/
bool UpdateLoginWorldAdminAccountPasswordByUsername(
const std::string &admin_account_username,
const std::string &admin_account_password_hash
);
/**
* @param name
* @param password
* @param loginserver
* @param email
* @return
*/
uint32 CreateLoginAccount(
const std::string &name,
const std::string &password,
const std::string &loginserver = "local",
const std::string &email = "local_creation"
);
protected:
MYSQL *m_database{};
};
#endif

View File

@ -21,10 +21,6 @@
#endif
/**
* @param mode
* @return
*/
std::string GetEncryptionByModeId(uint32 mode)
{
switch (mode) {
@ -61,13 +57,6 @@ std::string GetEncryptionByModeId(uint32 mode)
}
}
/**
* @param buffer_in
* @param buffer_in_sz
* @param buffer_out
* @param enc
* @return
*/
const char *eqcrypt_block(const char *buffer_in, size_t buffer_in_sz, char *buffer_out, bool enc)
{
#ifdef EQEMU_USE_MBEDTLS
@ -113,7 +102,7 @@ const char *eqcrypt_block(const char *buffer_in, size_t buffer_in_sz, char *buff
unsigned char iv[8];
memset(&key, 0, MBEDTLS_DES_KEY_SIZE);
memset(&iv, 0, 8);
mbedtls_des_context context;
mbedtls_des_setkey_dec(&context, key);
mbedtls_des_crypt_cbc(&context, MBEDTLS_DES_DECRYPT, buffer_in_sz, iv, (const unsigned char*)buffer_in, (unsigned char*)buffer_out);
@ -123,23 +112,19 @@ const char *eqcrypt_block(const char *buffer_in, size_t buffer_in_sz, char *buff
#ifdef EQEMU_USE_OPENSSL
DES_key_schedule k;
DES_cblock v;
memset(&k, 0, sizeof(DES_key_schedule));
memset(&v, 0, sizeof(DES_cblock));
if (!enc && buffer_in_sz && buffer_in_sz % 8 != 0) {
return nullptr;
}
DES_ncbc_encrypt((const unsigned char*)buffer_in, (unsigned char*)buffer_out, (long)buffer_in_sz, &k, &v, enc);
#endif
return buffer_out;
}
/**
* @param msg
* @return
*/
std::string eqcrypt_md5(const std::string &msg)
{
std::string ret;
@ -174,10 +159,6 @@ std::string eqcrypt_md5(const std::string &msg)
return ret;
}
/**
* @param msg
* @return
*/
std::string eqcrypt_sha1(const std::string &msg)
{
std::string ret;
@ -212,10 +193,6 @@ std::string eqcrypt_sha1(const std::string &msg)
return ret;
}
/**
* @param msg
* @return
*/
std::string eqcrypt_sha512(const std::string &msg)
{
std::string ret;
@ -252,10 +229,6 @@ std::string eqcrypt_sha512(const std::string &msg)
#ifdef ENABLE_SECURITY
/**
* @param msg
* @return
*/
std::string eqcrypt_argon2(const std::string &msg)
{
char buffer[crypto_pwhash_STRBYTES] = {0};
@ -275,10 +248,6 @@ std::string eqcrypt_argon2(const std::string &msg)
return ret;
}
/**
* @param msg
* @return
*/
std::string eqcrypt_scrypt(const std::string &msg)
{
char buffer[crypto_pwhash_scryptsalsa208sha256_STRBYTES] = {0};
@ -300,12 +269,6 @@ std::string eqcrypt_scrypt(const std::string &msg)
#endif
/**
* @param username
* @param password
* @param mode
* @return
*/
std::string eqcrypt_hash(const std::string &username, const std::string &password, int mode)
{
switch (mode) {
@ -346,13 +309,6 @@ std::string eqcrypt_hash(const std::string &username, const std::string &passwor
}
}
/**
* @param username
* @param password
* @param pwhash
* @param mode
* @return
*/
bool eqcrypt_verify_hash(const std::string &username, const std::string &password, const std::string &pwhash, int mode)
{
switch (mode) {

View File

@ -2,6 +2,9 @@
#include <string>
#include "../common/types.h"
#include "login_types.h"
#include "../common/eqemu_logsys.h"
#include "../common/strings.h"
enum EncryptionMode {
EncryptionModeMD5 = 1,
@ -26,11 +29,34 @@ namespace CryptoHash {
const int sha512_hash_length = 128;
}
/**
* @param mode
* @return
*/
std::string GetEncryptionByModeId(uint32 mode);
const char *eqcrypt_block(const char *buffer_in, size_t buffer_in_sz, char *buffer_out, bool enc);
std::string eqcrypt_hash(const std::string &username, const std::string &password, int mode);
bool eqcrypt_verify_hash(const std::string &username, const std::string &password, const std::string &pwhash, int mode);
struct EncryptionResult {
std::string password;
int mode = 0;
std::string mode_name;
};
static EncryptionResult EncryptPasswordFromContext(LoginAccountContext c, int mode = EncryptionModeSCrypt)
{
if (mode == 0) {
LogError("Encryption mode not set!");
return {};
}
EncryptionResult r;
r.password = eqcrypt_hash(
c.username,
c.password,
mode
);
r.mode = mode;
r.mode_name = GetEncryptionByModeId(r.mode);
LogInfo("Encrypted password for user [{}] using mode [{}] ({})", c.username, r.mode_name, r.mode);
return r;
}

View File

@ -3,29 +3,24 @@
#include <utility>
#include "../common/json_config.h"
#include "database.h"
#include "encryption.h"
#include "options.h"
#include "server_manager.h"
#include "world_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()
*/
struct LoginServer
{
struct LoginServer {
public:
LoginServer() : db(nullptr), server_manager(nullptr) {
LoginServer() : server_manager(nullptr)
{
}
EQ::JsonConfigFile config;
Database *db;
LoginserverWebserver::TokenManager *token_manager{};
Options options;
ServerManager *server_manager;
WorldServerManager *server_manager;
ClientManager *client_manager{};
};

View File

@ -4,30 +4,30 @@
#pragma pack(1)
// unencrypted base message header in all packets
struct LoginBaseMessage_Struct {
struct LoginBaseMessage {
int32_t sequence; // request type/login sequence (2: handshake, 3: login, 4: serverlist, ...)
bool compressed; // true: deflated
int8_t encrypt_type; // 1: invert (unused) 2: des (2 for encrypted player logins and order expansions) (client uses what it sent, ignores in reply)
int32_t unk3; // unused?
};
struct LoginBaseReplyMessage_Struct {
struct LoginBaseReplyMessage {
bool success; // 0: failure (shows error string) 1: success
int32_t error_str_id; // last error eqlsstr id, default: 101 (no error)
char str[1]; // variable length, unknown (may be unused, this struct is a common pattern elsewhere)
};
struct LoginHandShakeReply_Struct {
LoginBaseMessage_Struct base_header;
LoginBaseReplyMessage_Struct base_reply;
char unknown[1]; // variable length string
struct LoginHandShakeReply {
LoginBaseMessage base_header;
LoginBaseReplyMessage base_reply;
char unknown[1]; // variable length string
};
// variable length, can use directly if not serializing strings
struct PlayerLoginReply_Struct {
struct PlayerLoginReply {
// base header excluded to make struct data easier to encrypt
//LoginBaseMessage_Struct base_header;
LoginBaseReplyMessage_Struct base_reply;
//LoginBaseMessage base_header;
LoginBaseReplyMessage base_reply;
int8_t unk1; // (default: 0)
int8_t unk2; // (default: 0)
@ -47,7 +47,7 @@ struct PlayerLoginReply_Struct {
};
// variable length, for reference
struct LoginClientServerData_Struct {
struct LoginClientServerData {
char ip[1];
int32_t server_type; // legends, preferred, standard
int32_t server_id;
@ -59,24 +59,24 @@ struct LoginClientServerData_Struct {
};
// variable length, for reference
struct ServerListReply_Struct {
LoginBaseMessage_Struct base_header;
LoginBaseReplyMessage_Struct base_reply;
struct ServerListReply {
LoginBaseMessage base_header;
LoginBaseReplyMessage base_reply;
int32_t server_count;
LoginClientServerData_Struct servers[0];
int32_t server_count;
LoginClientServerData servers[0];
};
struct PlayEverquestRequest_Struct {
LoginBaseMessage_Struct base_header;
uint32 server_number;
struct PlayEverquestRequest {
LoginBaseMessage base_header;
uint32 server_number;
};
// SCJoinServerReply
struct PlayEverquestResponse_Struct {
LoginBaseMessage_Struct base_header;
LoginBaseReplyMessage_Struct base_reply;
uint32 server_number;
struct PlayEverquestResponse {
LoginBaseMessage base_header;
LoginBaseReplyMessage base_reply;
uint32 server_number;
};
#pragma pack()
@ -95,6 +95,34 @@ enum LSClientStatus {
cs_logged_in
};
struct LoginWorldContext {
std::string long_name;
std::string short_name;
std::string password;
std::string password_hash;
int64 admin_id = 0;
};
struct LoginWorldAdminAccountContext {
int64 id;
std::string username;
std::string password;
std::string password_hash;
std::string email;
std::string first_name;
std::string last_name;
std::string ip_address;
};
struct LoginAccountContext {
std::string username;
std::string password;
std::string email;
std::string source_loginserver = "local";
uint32 login_account_id = 0;
bool password_is_encrypted = false;
};
namespace LS {
namespace ServerStatusFlags {
enum eServerStatusFlags {
@ -123,13 +151,13 @@ namespace LS {
};
namespace ErrStr {
constexpr static int ERROR_NONE = 101; // No Error
constexpr static int ERROR_UNKNOWN = 102; // Error - Unknown Error Occurred
constexpr static int ERROR_ACTIVE_CHARACTER = 111; // Error 1018: You currently have an active character on that EverQuest Server, please allow a minute for synchronization and try again.
constexpr static int ERROR_SERVER_UNAVAILABLE = 326; // That server is currently unavailable. Please check the EverQuest webpage for current server status and try again later.
constexpr static int ERROR_ACCOUNT_SUSPENDED = 337; // This account is currently suspended. Please contact customer service for more information.
constexpr static int ERROR_ACCOUNT_BANNED = 338; // This account is currently banned. Please contact customer service for more information.
constexpr static int ERROR_WORLD_MAX_CAPACITY = 339; // The world server is currently at maximum capacity and not allowing further logins until the number of players online decreases. Please try again later.
constexpr static int ERROR_NONE = 101; // No Error
constexpr static int ERROR_UNKNOWN = 102; // Error - Unknown Error Occurred
constexpr static int ERROR_ACTIVE_CHARACTER = 111; // Error 1018: You currently have an active character on that EverQuest Server, please allow a minute for synchronization and try again.
constexpr static int ERROR_SERVER_UNAVAILABLE = 326; // That server is currently unavailable. Please check the EverQuest webpage for current server status and try again later.
constexpr static int ERROR_ACCOUNT_SUSPENDED = 337; // This account is currently suspended. Please contact customer service for more information.
constexpr static int ERROR_ACCOUNT_BANNED = 338; // This account is currently banned. Please contact customer service for more information.
constexpr static int ERROR_WORLD_MAX_CAPACITY = 339; // The world server is currently at maximum capacity and not allowing further logins until the number of players online decreases. Please try again later.
};
}

View File

@ -33,10 +33,6 @@ CREATE TABLE `login_server_list_types` (
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `login_server_list_types` (`id`, `description`) VALUES ('1', 'Legends'),
('2', 'Preferred'),
('3', 'Standard');
DROP TABLE IF EXISTS `login_world_servers`;
CREATE TABLE `login_world_servers` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,

View File

@ -5,15 +5,11 @@
#include "login_server.h"
#include "loginserver_webserver.h"
#include "account_management.h"
#include "../common/repositories/login_api_tokens_repository.h"
extern LoginServer server;
namespace LoginserverCommandHandler {
/**
* @param argc
* @param argv
*/
void CommandHandler(int argc, char **argv)
{
if (argc == 1) { return; }
@ -22,14 +18,7 @@ namespace LoginserverCommandHandler {
cmd.parse(argc, argv, argh::parser::PREFER_PARAM_FOR_UNREG_OPTION);
EQEmuCommand::DisplayDebug(cmd);
/**
* Declare command mapping
*/
auto function_map = EQEmuCommand::function_map;
/**
* Register commands
*/
function_map["login-user:check-credentials"] = &LoginserverCommandHandler::CheckLoginserverUserCredentials;
function_map["login-user:check-external-credentials"] = &LoginserverCommandHandler::CheckExternalLoginserverUserCredentials;
function_map["login-user:create"] = &LoginserverCommandHandler::CreateLocalLoginserverAccount;
@ -43,12 +32,6 @@ namespace LoginserverCommandHandler {
EQEmuCommand::HandleMenu(function_map, cmd, argc, argv);
}
/**
* @param argc
* @param argv
* @param cmd
* @param description
*/
void CreateLoginserverApiToken(int argc, char **argv, argh::parser &cmd, std::string &description)
{
description = "Creates Loginserver API Token";
@ -69,22 +52,22 @@ namespace LoginserverCommandHandler {
bool can_write = cmd[{"-w", "--write"}];
if (!can_read || !can_write) {
LogInfo("[{0}] --read or --write must be set or both!", __func__);
LogInfo("--read or --write must be set or both!");
exit(1);
}
std::string token = server.db->CreateLoginserverApiToken(can_write, can_read);
if (!token.empty()) {
LogInfo("[{0}] Created Loginserver API token [{1}]", __func__, token);
auto t = LoginApiTokensRepository::NewEntity();
t.can_read = can_read;
t.can_write = can_write;
t.token = EQ::Util::UUID::Generate().ToString();
t.created_at = std::time(nullptr);
auto created = LoginApiTokensRepository::InsertOne(database, t);
if (created.id) {
LogInfo("Created Loginserver API token [{}] [{}]", created.id, created.token);
}
}
/**
* @param argc
* @param argv
* @param cmd
* @param description
*/
void ListLoginserverApiTokens(int argc, char **argv, argh::parser &cmd, std::string &description)
{
description = "Lists Loginserver API Tokens";
@ -96,9 +79,10 @@ namespace LoginserverCommandHandler {
server.token_manager = new LoginserverWebserver::TokenManager;
server.token_manager->LoadApiTokens();
for (auto &it : server.token_manager->loaded_api_tokens) {
for (auto &it: server.token_manager->loaded_api_tokens) {
LogInfo(
"token [{0}] can_write [{1}] can_read [{2}]",
"token id [{}] [{}] can_write [{}] can_read [{}]",
it.second.id,
it.second.token,
it.second.can_write,
it.second.can_read
@ -106,12 +90,6 @@ namespace LoginserverCommandHandler {
}
}
/**
* @param argc
* @param argv
* @param cmd
* @param description
*/
void CreateLocalLoginserverAccount(int argc, char **argv, argh::parser &cmd, std::string &description)
{
description = "Creates Local Loginserver Account";
@ -130,19 +108,14 @@ namespace LoginserverCommandHandler {
EQEmuCommand::ValidateCmdInput(arguments, options, cmd, argc, argv);
AccountManagement::CreateLoginServerAccount(
cmd(2).str(),
cmd(3).str(),
cmd("--email").str()
);
LoginAccountContext c;
c.username = cmd(2).str();
c.password = cmd(3).str();
c.email = cmd("--email").str();
AccountManagement::CreateLoginServerAccount(c);
}
/**
* @param argc
* @param argv
* @param cmd
* @param description
*/
void CreateLoginserverWorldAdminAccount(int argc, char **argv, argh::parser &cmd, std::string &description)
{
description = "Creates Loginserver World Administrator Account";
@ -167,12 +140,6 @@ namespace LoginserverCommandHandler {
);
}
/**
* @param argc
* @param argv
* @param cmd
* @param description
*/
void CheckLoginserverUserCredentials(int argc, char **argv, argh::parser &cmd, std::string &description)
{
description = "Check user login credentials";
@ -189,20 +156,15 @@ namespace LoginserverCommandHandler {
EQEmuCommand::ValidateCmdInput(arguments, options, cmd, argc, argv);
auto res = AccountManagement::CheckLoginserverUserCredentials(
cmd(2).str(),
cmd(3).str()
);
LoginAccountContext c;
c.username = cmd(2).str();
c.password = cmd(3).str();
LogInfo("Credentials were {0}", res != 0 ? "accepted" : "not accepted");
auto res = AccountManagement::CheckLoginserverUserCredentials(c);
LogInfo("Credentials were {}", res != 0 ? "accepted" : "not accepted");
}
/**
* @param argc
* @param argv
* @param cmd
* @param description
*/
void UpdateLoginserverUserCredentials(int argc, char **argv, argh::parser &cmd, std::string &description)
{
description = "Change user login credentials";
@ -219,18 +181,12 @@ namespace LoginserverCommandHandler {
EQEmuCommand::ValidateCmdInput(arguments, options, cmd, argc, argv);
AccountManagement::UpdateLoginserverUserCredentials(
cmd(2).str(),
cmd(3).str()
);
LoginAccountContext c;
c.username = cmd(2).str();
c.password = cmd(3).str();
AccountManagement::UpdateLoginserverUserCredentials(c);
}
/**
* @param argc
* @param argv
* @param cmd
* @param description
*/
void CheckExternalLoginserverUserCredentials(int argc, char **argv, argh::parser &cmd, std::string &description)
{
description = "Check user external login credentials";
@ -247,20 +203,14 @@ namespace LoginserverCommandHandler {
EQEmuCommand::ValidateCmdInput(arguments, options, cmd, argc, argv);
auto res = AccountManagement::CheckExternalLoginserverUserCredentials(
cmd(2).str(),
cmd(3).str()
);
LoginAccountContext c;
c.username = cmd(2).str();
c.password = cmd(3).str();
auto res = AccountManagement::CheckExternalLoginserverUserCredentials(c);
LogInfo("Credentials were {0}", res ? "accepted" : "not accepted");
LogInfo("Credentials were {}", res ? "accepted" : "not accepted");
}
/**
* @param argc
* @param argv
* @param cmd
* @param description
*/
void UpdateLoginserverWorldAdminAccountPassword(int argc, char **argv, argh::parser &cmd, std::string &description)
{
description = "Update world admin account password";
@ -277,18 +227,12 @@ namespace LoginserverCommandHandler {
EQEmuCommand::ValidateCmdInput(arguments, options, cmd, argc, argv);
AccountManagement::UpdateLoginserverWorldAdminAccountPasswordByName(
cmd(2).str(),
cmd(3).str()
);
LoginAccountContext c;
c.username = cmd(2).str();
c.password = cmd(3).str();
AccountManagement::UpdateLoginserverWorldAdminAccountPasswordByName(c);
}
/**
* @param argc
* @param argv
* @param cmd
* @param description
*/
void HealthCheckLogin(int argc, char **argv, argh::parser &cmd, std::string &description)
{
description = "Checks login health using a test user";

View File

@ -1,9 +1,10 @@
#include "loginserver_webserver.h"
#include "server_manager.h"
#include "world_server_manager.h"
#include "login_server.h"
#include "../common/json/json.h"
#include "../common/strings.h"
#include "account_management.h"
#include "../common/repositories/login_api_tokens_repository.h"
extern LoginServer server;
@ -13,9 +14,6 @@ namespace LoginserverWebserver {
constexpr static int HTTP_RESPONSE_BAD_REQUEST = 400;
constexpr static int HTTP_RESPONSE_UNAUTHORIZED = 401;
/**
* @param api
*/
void RegisterRoutes(httplib::Server &api)
{
server.token_manager = new LoginserverWebserver::TokenManager;
@ -27,21 +25,21 @@ namespace LoginserverWebserver {
return;
}
Json::Value response;
auto iter = server.server_manager->getWorldServers().begin();
while (iter != server.server_manager->getWorldServers().end()) {
Json::Value response;
auto iter = server.server_manager->GetWorldServers().begin();
for (const auto &s: server.server_manager->GetWorldServers()) {
Json::Value row;
row["server_long_name"] = (*iter)->GetServerLongName();
row["server_short_name"] = (*iter)->GetServerShortName();
row["server_list_type_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();
row["world_id"] = (*iter)->GetServerId();
row["server_long_name"] = s->GetServerLongName();
row["server_short_name"] = s->GetServerShortName();
row["server_list_type_id"] = s->GetServerListID();
row["server_status"] = s->GetStatus();
row["zones_booted"] = s->GetZonesBooted();
row["local_ip"] = s->GetLocalIP();
row["remote_ip"] = s->GetRemoteIP();
row["players_online"] = s->GetPlayersOnline();
row["world_id"] = s->GetServerId();
response.append(row);
++iter;
}
LoginserverWebserver::SendResponse(response, res);
@ -54,10 +52,10 @@ namespace LoginserverWebserver {
return;
}
Json::Value request_body = LoginserverWebserver::ParseRequestBody(request);
std::string username = request_body.get("username", "").asString();
std::string password = request_body.get("password", "").asString();
std::string email = request_body.get("email", "").asString();
Json::Value req = LoginserverWebserver::ParseRequestBody(request);
std::string username = req.get("username", "").asString();
std::string password = req.get("password", "").asString();
std::string email = req.get("email", "").asString();
Json::Value response;
if (username.empty() || password.empty()) {
@ -67,7 +65,12 @@ namespace LoginserverWebserver {
return;
}
int32 account_created_id = AccountManagement::CreateLoginServerAccount(username, password, email);
LoginAccountContext c;
c.username = username;
c.password = password;
c.email = email;
int32 account_created_id = AccountManagement::CreateLoginServerAccount(c);
if (account_created_id > 0) {
response["message"] = "Account created successfully!";
response["data"]["account_id"] = account_created_id;
@ -91,11 +94,11 @@ namespace LoginserverWebserver {
return;
}
Json::Value request_body = LoginserverWebserver::ParseRequestBody(request);
std::string username = request_body.get("username", "").asString();
std::string password = request_body.get("password", "").asString();
std::string email = request_body.get("email", "").asString();
uint32 login_account_id = request_body.get("login_account_id", "").asInt();
Json::Value req = LoginserverWebserver::ParseRequestBody(request);
std::string username = req.get("username", "").asString();
std::string password = req.get("password", "").asString();
std::string email = req.get("email", "").asString();
uint32 login_account_id = req.get("login_account_id", "").asInt();
Json::Value response;
if (username.empty() || password.empty()) {
@ -105,14 +108,14 @@ namespace LoginserverWebserver {
return;
}
std::string source_loginserver = "eqemu";
int32 account_created_id = AccountManagement::CreateLoginServerAccount(
username,
password,
email,
source_loginserver,
login_account_id
);
LoginAccountContext c;
c.username = username;
c.password = password;
c.email = email;
c.source_loginserver = "eqemu";
c.login_account_id = login_account_id;
int32 account_created_id = AccountManagement::CreateLoginServerAccount(c);
if (account_created_id > 0) {
response["message"] = "Account created successfully!";
@ -137,9 +140,9 @@ namespace LoginserverWebserver {
return;
}
Json::Value request_body = LoginserverWebserver::ParseRequestBody(request);
std::string username = request_body.get("username", "").asString();
std::string password = request_body.get("password", "").asString();
Json::Value req = LoginserverWebserver::ParseRequestBody(request);
std::string username = req.get("username", "").asString();
std::string password = req.get("password", "").asString();
Json::Value response;
if (username.empty() || password.empty()) {
@ -149,11 +152,11 @@ namespace LoginserverWebserver {
return;
}
uint32 login_account_id = AccountManagement::CheckLoginserverUserCredentials(
username,
password
);
LoginAccountContext c;
c.username = username;
c.password = password;
uint32 login_account_id = AccountManagement::CheckLoginserverUserCredentials(c);
if (login_account_id > 0) {
response["message"] = "Credentials valid!";
response["data"]["account_id"] = login_account_id;
@ -173,9 +176,9 @@ namespace LoginserverWebserver {
return;
}
Json::Value request_body = LoginserverWebserver::ParseRequestBody(request);
std::string username = request_body.get("username", "").asString();
std::string password = request_body.get("password", "").asString();
Json::Value req = LoginserverWebserver::ParseRequestBody(request);
std::string username = req.get("username", "").asString();
std::string password = req.get("password", "").asString();
Json::Value response;
if (username.empty() || password.empty()) {
@ -185,24 +188,20 @@ namespace LoginserverWebserver {
return;
}
Database::DbLoginServerAccount
login_server_account = server.db->GetLoginServerAccountByAccountName(
username
);
LoginAccountContext c;
c.username = username;
c.password = password;
if (!login_server_account.loaded) {
auto a = LoginAccountsRepository::GetAccountFromContext(database, c);
if (!a.id) {
res.status = HTTP_RESPONSE_BAD_REQUEST;
response["error"] = "Failed to find associated loginserver account!";
LoginserverWebserver::SendResponse(response, res);
return;
}
bool credentials_valid = AccountManagement::UpdateLoginserverUserCredentials(
username,
password
);
if (credentials_valid) {
bool success = AccountManagement::UpdateLoginserverUserCredentials(c);
if (success) {
response["message"] = "Loginserver account credentials updated!";
}
else {
@ -214,16 +213,15 @@ namespace LoginserverWebserver {
}
);
api.Post(
"/v1/account/credentials/update/external", [](const httplib::Request &request, httplib::Response &res) {
if (!LoginserverWebserver::TokenManager::AuthCanWrite(request, res)) {
return;
}
Json::Value request_body = LoginserverWebserver::ParseRequestBody(request);
std::string username = request_body.get("username", "").asString();
std::string password = request_body.get("password", "").asString();
Json::Value req = LoginserverWebserver::ParseRequestBody(request);
std::string username = req.get("username", "").asString();
std::string password = req.get("password", "").asString();
Json::Value response;
if (username.empty() || password.empty()) {
@ -234,25 +232,20 @@ namespace LoginserverWebserver {
std::string source_loginserver = "eqemu";
Database::DbLoginServerAccount
login_server_account = server.db->GetLoginServerAccountByAccountName(
username,
source_loginserver
);
LoginAccountContext c;
c.username = username;
c.password = password;
c.source_loginserver = source_loginserver;
if (!login_server_account.loaded) {
auto a = LoginAccountsRepository::GetAccountFromContext(database, c);
if (!a.id) {
response["error"] = "Failed to find associated loginserver account!";
LoginserverWebserver::SendResponse(response, res);
return;
}
bool credentials_valid = AccountManagement::UpdateLoginserverUserCredentials(
username,
password,
source_loginserver
);
if (credentials_valid) {
bool success = AccountManagement::UpdateLoginserverUserCredentials(c);
if (success) {
response["message"] = "Loginserver account credentials updated!";
}
else {
@ -269,9 +262,9 @@ namespace LoginserverWebserver {
return;
}
Json::Value request_body = LoginserverWebserver::ParseRequestBody(request);
std::string username = request_body.get("username", "").asString();
std::string password = request_body.get("password", "").asString();
Json::Value req = LoginserverWebserver::ParseRequestBody(request);
std::string username = req.get("username", "").asString();
std::string password = req.get("password", "").asString();
Json::Value response;
if (username.empty() || password.empty()) {
@ -280,10 +273,10 @@ namespace LoginserverWebserver {
return;
}
uint32 account_id = AccountManagement::CheckExternalLoginserverUserCredentials(
username,
password
);
LoginAccountContext c;
c.username = username;
c.password = password;
uint32 account_id = AccountManagement::CheckExternalLoginserverUserCredentials(c);
if (account_id > 0) {
response["message"] = "Credentials valid!";
@ -301,7 +294,7 @@ namespace LoginserverWebserver {
api.Get(
"/probes/healthcheck", [](const httplib::Request &request, httplib::Response &res) {
Json::Value response;
uint32 login_response = AccountManagement::HealthCheckUserLogin();
uint32 login_response = AccountManagement::HealthCheckUserLogin();
response["status"] = login_response;
if (login_response == 0) {
@ -317,10 +310,6 @@ namespace LoginserverWebserver {
);
}
/**
* @param payload
* @param res
*/
void SendResponse(const Json::Value &payload, httplib::Response &res)
{
if (res.get_header_value("response_set") == "true") {
@ -342,10 +331,6 @@ namespace LoginserverWebserver {
res.set_content(response_payload.str(), "application/json");
}
/**
* @param payload
* @param res
*/
Json::Value ParseRequestBody(const httplib::Request &request)
{
Json::Value request_body;
@ -364,13 +349,9 @@ namespace LoginserverWebserver {
return request_body;
}
/**
* @param request
* @param res
*/
bool LoginserverWebserver::TokenManager::AuthCanRead(const httplib::Request &request, httplib::Response &res)
{
LoginserverWebserver::TokenManager::token_data
LoginserverWebserver::TokenManager::Token
user_token = LoginserverWebserver::TokenManager::CheckApiAuthorizationHeaders(request);
if (!user_token.can_read) {
@ -384,7 +365,7 @@ namespace LoginserverWebserver {
res.set_header("response_set", "true");
LogWarning(
"AuthCanRead access failure remote_address [{0}] user_agent [{1}]",
"AuthCanRead access failure remote_address [{}] user_agent [{}]",
user_token.remote_address,
user_token.user_agent
);
@ -395,13 +376,9 @@ namespace LoginserverWebserver {
return true;
}
/**
* @param request
* @param res
*/
bool LoginserverWebserver::TokenManager::AuthCanWrite(const httplib::Request &request, httplib::Response &res)
{
LoginserverWebserver::TokenManager::token_data
LoginserverWebserver::TokenManager::Token
user_token = LoginserverWebserver::TokenManager::CheckApiAuthorizationHeaders(request);
if (!user_token.can_write) {
@ -415,7 +392,7 @@ namespace LoginserverWebserver {
res.set_header("response_set", "true");
LogWarning(
"AuthCanWrite access failure remote_address [{0}] user_agent [{1}]",
"AuthCanWrite access failure remote_address [{}] user_agent [{}]",
user_token.remote_address,
user_token.user_agent
);
@ -426,20 +403,16 @@ namespace LoginserverWebserver {
return true;
}
/**
* @param request
* @return
*/
LoginserverWebserver::TokenManager::token_data
LoginserverWebserver::TokenManager::Token
LoginserverWebserver::TokenManager::CheckApiAuthorizationHeaders(
const httplib::Request &request
)
{
std::string authorization_key;
LoginserverWebserver::TokenManager::token_data user_token{};
LoginserverWebserver::TokenManager::Token user_token{};
for (const auto &header : request.headers) {
for (const auto &header: request.headers) {
auto header_key = header.first;
auto header_value = header.second;
if (header_key == "Authorization") {
@ -460,7 +433,7 @@ namespace LoginserverWebserver {
}
LogDebug(
"Authentication Request | remote_address [{0}] user_agent [{1}] authorization_key [{2}] request_path [{3}]",
"Authentication Request | remote_address [{}] user_agent [{}] authorization_key [{}] request_path [{}]",
user_token.remote_address,
user_token.user_agent,
authorization_key,
@ -470,43 +443,24 @@ namespace LoginserverWebserver {
return user_token;
}
/**
* Loads API Tokens
*/
void TokenManager::LoadApiTokens()
{
auto results = server.db->GetLoginserverApiTokens();
int token_count = 0;
for (auto row = results.begin(); row != results.end(); ++row) {
LoginserverWebserver::TokenManager::token_data token_data;
token_data.token = row[0];
token_data.can_write = Strings::ToInt(row[1]) > 0;
token_data.can_read = Strings::ToInt(row[2]) > 0;
int token_count = 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.emplace(
std::make_pair(
token_data.token,
token_data
)
);
for (auto &t: LoginApiTokensRepository::GetWhere(database, "TRUE ORDER BY id ASC")) {
LoginserverWebserver::TokenManager::Token td;
td.id = t.id;
td.token = t.token;
td.can_write = t.can_write;
td.can_read = t.can_read;
server.token_manager->loaded_api_tokens.emplace(std::make_pair(td.token, td));
token_count++;
}
LogInfo("Loaded [{}] API token(s)", token_count);
}
/**
* @param token
* @return
*/
bool TokenManager::TokenExists(const std::string &token)
{
auto it = server.token_manager->loaded_api_tokens.find(token);
@ -514,11 +468,7 @@ namespace LoginserverWebserver {
return !(it == server.token_manager->loaded_api_tokens.end());
}
/**
* @param token
* @return
*/
LoginserverWebserver::TokenManager::token_data TokenManager::GetToken(
LoginserverWebserver::TokenManager::Token TokenManager::GetToken(
const std::string &token
)
{

View File

@ -12,7 +12,8 @@ namespace LoginserverWebserver {
public:
TokenManager() = default;
struct token_data {
struct Token {
int id;
std::string token;
bool can_read;
bool can_write;
@ -20,12 +21,12 @@ namespace LoginserverWebserver {
std::string remote_address;
};
std::map<std::string, token_data> loaded_api_tokens{};
std::map<std::string, Token> 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);
Token GetToken(const std::string &token);
static Token CheckApiAuthorizationHeaders(const httplib::Request &request);
static bool AuthCanRead(const httplib::Request &request, httplib::Response &res);
static bool AuthCanWrite(const httplib::Request &request, httplib::Response &res);
};

View File

@ -12,18 +12,23 @@
#include "loginserver_command_handler.h"
#include "../common/strings.h"
#include "../common/path_manager.h"
#include "../common/database.h"
#include "../common/events/player_event_logs.h"
#include "../common/zone_store.h"
#include <time.h>
#include <stdlib.h>
#include <string>
#include <sstream>
#include <thread>
LoginServer server;
EQEmuLogSys LogSys;
bool run_server = true;
PathManager path;
LoginServer server;
EQEmuLogSys LogSys;
bool run_server = true;
PathManager path;
Database database;
PlayerEventLogs player_event_logs;
ZoneStore zone_store;
void ResolveAddresses();
void CatchSignal(int sig_num)
{
}
@ -32,14 +37,16 @@ void LoadDatabaseConnection()
{
LogInfo("MySQL Database Init");
server.db = new Database(
if (!database.Connect(
server.config.GetVariableString("database", "host", "localhost"),
server.config.GetVariableString("database", "user", "root"),
server.config.GetVariableString("database", "password", ""),
server.config.GetVariableString("database", "host", "localhost"),
server.config.GetVariableString("database", "port", "3306"),
server.config.GetVariableString("database", "db", "peq")
);
server.config.GetVariableString("database", "db", "peq"),
server.config.GetVariableInt("database", "port", 3306)
)) {
LogError("Cannot continue without a database connection");
std::exit(1);
}
}
void LoadServerConfig()
@ -49,9 +56,6 @@ void LoadServerConfig()
);
LogInfo("Config System Init");
/**
* Worldservers
*/
server.options.RejectDuplicateServers(
server.config.GetVariableBool(
"worldservers",
@ -82,41 +86,33 @@ void LoadServerConfig()
)
);
/**
* Expansion Display Settings
*/
server.options.DisplayExpansions(
server.config.GetVariableBool(
"client_configuration",
"display_expansions",
false
)); //disable by default
)
);
server.options.MaxExpansions(
server.config.GetVariableInt(
"client_configuration",
"max_expansions_mask",
67108863
)); //enable display of all expansions
/**
* Account
*/
server.options.AutoCreateAccounts(server.config.GetVariableBool("account", "auto_create_accounts", true));
server.options.AutoLinkAccounts(server.config.GetVariableBool("account", "auto_link_accounts", false));
#ifdef LSPX
server.options.EQEmuLoginServerAddress(
server.config.GetVariableString(
"general",
"eqemu_loginserver_address",
"login.eqemulator.net:5999"
)
);
#endif
/**
* Default Loginserver Name (Don't change)
*/
server.options.AutoCreateAccounts(server.config.GetVariableBool("account", "auto_create_accounts", true));
if (std::getenv("LSPX")) {
server.options.EQEmuLoginServerAddress(
server.config.GetVariableString(
"general",
"eqemu_loginserver_address",
"login.eqemulator.net:5999"
)
);
}
server.options.DefaultLoginServerName(
server.config.GetVariableString(
"general",
@ -125,10 +121,6 @@ void LoadServerConfig()
)
);
/**
* Security
*/
#ifdef ENABLE_SECURITY
server.options.EncryptionMode(server.config.GetVariableInt("security", "mode", 13));
#else
@ -136,14 +128,6 @@ void LoadServerConfig()
#endif
server.options.AllowTokenLogin(server.config.GetVariableBool("security", "allow_token_login", false));
server.options.AllowPasswordLogin(server.config.GetVariableBool("security", "allow_password_login", true));
server.options.UpdateInsecurePasswords(
server.config.GetVariableBool(
"security",
"update_insecure_passwords",
true
)
);
}
void start_web_server()
@ -151,7 +135,7 @@ void start_web_server()
Sleep(1);
int web_api_port = server.config.GetVariableInt("web_api", "port", 6000);
LogInfo("Webserver API now listening on port [{0}]", web_api_port);
LogInfo("Webserver API now listening on port [{}]", web_api_port);
httplib::Server api;
@ -180,9 +164,7 @@ int main(int argc, char **argv)
path.LoadPaths();
/**
* Command handler
*/
// command handler
if (argc > 1) {
LogSys.SilenceConsoleLogging();
@ -197,43 +179,23 @@ int main(int argc, char **argv)
}
LoadServerConfig();
/**
* mysql connect
*/
LoadDatabaseConnection();
if (argc == 1) {
LogSys.SetDatabase(server.db)
LogSys.SetDatabase(&database)
->SetLogPath("logs")
->LoadLogDatabaseSettings()
->StartFileLogs();
}
/**
* make sure our database got created okay, otherwise cleanup and exit
*/
if (!server.db) {
LogError("Database Initialization Failure");
LogInfo("Log System Shutdown");
return 1;
}
/**
* create server manager
*/
LogInfo("Server Manager Init");
server.server_manager = new ServerManager();
server.server_manager = new WorldServerManager();
if (!server.server_manager) {
LogError("Server Manager Failed to Start");
LogInfo("Database System Shutdown");
delete server.db;
return 1;
}
/**
* create client manager
*/
LogInfo("Client Manager Init");
server.client_manager = new ClientManager();
if (!server.client_manager) {
@ -242,7 +204,6 @@ int main(int argc, char **argv)
delete server.server_manager;
LogInfo("Database System Shutdown");
delete server.db;
return 1;
}
@ -256,37 +217,33 @@ int main(int argc, char **argv)
LogInfo("Server Started");
/**
* Web API
*/
bool web_api_enabled = server.config.GetVariableBool("web_api", "enabled", true);
if (web_api_enabled) {
std::thread web_api_thread(start_web_server);
web_api_thread.detach();
}
LogInfo("[Config] [Account] CanAutoCreateAccounts [{0}]", server.options.CanAutoCreateAccounts());
LogInfo("[Config] [ClientConfiguration] DisplayExpansions [{0}]", server.options.IsDisplayExpansions());
LogInfo("[Config] [ClientConfiguration] MaxExpansions [{0}]", server.options.GetMaxExpansions());
LogInfo("[Config] [Account] CanAutoCreateAccounts [{}]", server.options.CanAutoCreateAccounts());
LogInfo("[Config] [ClientConfiguration] DisplayExpansions [{}]", server.options.IsDisplayExpansions());
LogInfo("[Config] [ClientConfiguration] MaxExpansions [{}]", server.options.GetMaxExpansions());
#ifdef LSPX
LogInfo("[Config] [Account] CanAutoLinkAccounts [{0}]", server.options.CanAutoLinkAccounts());
#endif
LogInfo("[Config] [WorldServer] IsRejectingDuplicateServers [{0}]", server.options.IsRejectingDuplicateServers());
LogInfo("[Config] [WorldServer] IsUnregisteredAllowed [{0}]", server.options.IsUnregisteredAllowed());
LogInfo("[Config] [WorldServer] ShowPlayerCount [{0}]", server.options.IsShowPlayerCountEnabled());
if (std::getenv("LSPX")) {
LogInfo("[Config] [Account] LSPX [on]");
}
LogInfo("[Config] [WorldServer] IsRejectingDuplicateServers [{}]", server.options.IsRejectingDuplicateServers());
LogInfo("[Config] [WorldServer] IsUnregisteredAllowed [{}]", server.options.IsUnregisteredAllowed());
LogInfo("[Config] [WorldServer] ShowPlayerCount [{}]", server.options.IsShowPlayerCountEnabled());
LogInfo(
"[Config] [WorldServer] DevAndTestServersListBottom [{0}]",
"[Config] [WorldServer] DevAndTestServersListBottom [{}]",
server.options.IsWorldDevTestServersListBottom()
);
LogInfo(
"[Config] [WorldServer] SpecialCharactersStartListBottom [{0}]",
"[Config] [WorldServer] SpecialCharactersStartListBottom [{}]",
server.options.IsWorldSpecialCharacterStartListBottom()
);
LogInfo("[Config] [Security] GetEncryptionMode [{0}]", server.options.GetEncryptionMode());
LogInfo("[Config] [Security] IsTokenLoginAllowed [{0}]", server.options.IsTokenLoginAllowed());
LogInfo("[Config] [Security] IsPasswordLoginAllowed [{0}]", server.options.IsPasswordLoginAllowed());
LogInfo("[Config] [Security] IsUpdatingInsecurePasswords [{0}]", server.options.IsUpdatingInsecurePasswords());
LogInfo("[Config] [Security] GetEncryptionMode [{}]", server.options.GetEncryptionMode());
LogInfo("[Config] [Security] IsTokenLoginAllowed [{}]", server.options.IsTokenLoginAllowed());
Timer keepalive(INTERSERVER_TIMER); // does auto-reconnect
@ -295,7 +252,7 @@ int main(int argc, char **argv)
if (keepalive.Check()) {
keepalive.Start();
server.db->ping();
database.ping();
}
if (!run_server) {
@ -319,8 +276,5 @@ int main(int argc, char **argv)
LogInfo("Server Manager Shutdown");
delete server.server_manager;
LogInfo("Database System Shutdown");
delete server.db;
return 0;
}

View File

@ -1,124 +1,55 @@
#ifndef EQEMU_OPTIONS_H
#define EQEMU_OPTIONS_H
/**
* Collects options on one object, because having a bunch of global variables floating around is
* really ugly and just a little dangerous.
*/
class Options {
public:
/**
* Constructor: Default options
*/
Options() :
allow_unregistered(true),
display_expansions(false),
max_expansions_mask(0),
encryption_mode(5),
reject_duplicate_servers(false),
allow_password_login(true),
allow_token_login(false),
auto_create_accounts(false) {}
m_allow_unregistered(true),
m_display_expansions(false),
m_max_expansions_mask(0),
m_encryption_mode(14),
m_reject_duplicate_servers(false),
m_allow_token_login(false),
m_auto_create_accounts(false) {}
/**
* Sets allow_unregistered.
*/
inline void AllowUnregistered(bool b) { allow_unregistered = b; }
/**
* Returns the value of expansion display settings.
*/
inline void DisplayExpansions(bool b) { display_expansions = b; }
inline void MaxExpansions(int i) { max_expansions_mask = i; }
inline bool IsDisplayExpansions() const { return display_expansions; }
inline int GetMaxExpansions() const { return max_expansions_mask; }
/**
* Returns the value of allow_unregistered.
*/
inline bool IsUnregisteredAllowed() const { return allow_unregistered; }
/**
* Sets encryption_mode.
*/
inline void EncryptionMode(int m) { encryption_mode = m; }
/**
* Returns the value of encryption_mode.
*/
inline int GetEncryptionMode() const { return encryption_mode; }
/**
* Sets whether we are rejecting duplicate servers or not.
*/
inline void RejectDuplicateServers(bool b) { reject_duplicate_servers = b; }
/**
* Returns whether we are rejecting duplicate servers or not.
*/
inline bool IsRejectingDuplicateServers() { return reject_duplicate_servers; }
inline void AllowTokenLogin(bool b) { allow_token_login = b; }
inline bool IsTokenLoginAllowed() const { return allow_token_login; }
inline void AllowPasswordLogin(bool b) { allow_password_login = b; }
inline bool IsPasswordLoginAllowed() const { return allow_password_login; }
inline void AutoCreateAccounts(bool b) { auto_create_accounts = b; }
inline bool CanAutoCreateAccounts() const { return auto_create_accounts; }
inline void AutoLinkAccounts(bool b) { auto_link_accounts = b; }
inline bool CanAutoLinkAccounts() const { return auto_link_accounts; }
inline void EQEmuLoginServerAddress(const std::string& v) { eqemu_loginserver_address = v; }
inline std::string GetEQEmuLoginServerAddress() const { return eqemu_loginserver_address; }
inline void DefaultLoginServerName(const std::string& v) { default_loginserver_name = v; }
inline std::string GetDefaultLoginServerName() const { return default_loginserver_name; }
inline void UpdateInsecurePasswords(bool b) { update_insecure_passwords = b; }
inline bool IsUpdatingInsecurePasswords() const { return update_insecure_passwords; }
inline bool IsShowPlayerCountEnabled() const
{
return show_player_count;
}
inline void SetShowPlayerCount(bool show_player_count)
{
Options::show_player_count = show_player_count;
}
inline bool IsWorldDevTestServersListBottom() const { return world_dev_test_servers_list_bottom; }
inline void SetWorldDevTestServersListBottom(bool dev_test_servers_list_bottom)
{
Options::world_dev_test_servers_list_bottom = dev_test_servers_list_bottom;
}
inline bool IsWorldSpecialCharacterStartListBottom() const
{
return world_special_character_start_list_bottom;
}
inline void SetWorldSpecialCharacterStartListBottom(bool world_special_character_start_list_bottom)
{
Options::world_special_character_start_list_bottom = world_special_character_start_list_bottom;
}
inline void AllowUnregistered(bool b) { m_allow_unregistered = b; }
inline void DisplayExpansions(bool b) { m_display_expansions = b; }
inline void MaxExpansions(int i) { m_max_expansions_mask = i; }
inline bool IsDisplayExpansions() const { return m_display_expansions; }
inline int GetMaxExpansions() const { return m_max_expansions_mask; }
inline bool IsUnregisteredAllowed() const { return m_allow_unregistered; }
inline void EncryptionMode(int m) { m_encryption_mode = m; }
inline int GetEncryptionMode() const { return m_encryption_mode; }
inline void RejectDuplicateServers(bool b) { m_reject_duplicate_servers = b; }
inline bool IsRejectingDuplicateServers() { return m_reject_duplicate_servers; }
inline void AllowTokenLogin(bool b) { m_allow_token_login = b; }
inline bool IsTokenLoginAllowed() const { return m_allow_token_login; }
inline void AutoCreateAccounts(bool b) { m_auto_create_accounts = b; }
inline bool CanAutoCreateAccounts() const { return m_auto_create_accounts; }
inline void EQEmuLoginServerAddress(const std::string &v) { m_eqemu_loginserver_address = v; }
inline std::string GetEQEmuLoginServerAddress() const { return m_eqemu_loginserver_address; }
inline void DefaultLoginServerName(const std::string &v) { m_default_loginserver_name = v; }
inline std::string GetDefaultLoginServerName() const { return m_default_loginserver_name; }
inline bool IsShowPlayerCountEnabled() const { return m_show_player_count; }
inline void SetShowPlayerCount(bool show_player_count) { show_player_count = show_player_count; }
inline bool IsWorldDevTestServersListBottom() const { return m_world_dev_list_bottom; }
inline void SetWorldDevTestServersListBottom(bool list_bottom) { m_world_dev_list_bottom = list_bottom; }
inline bool IsWorldSpecialCharacterStartListBottom() const { return m_special_char_list_bottom; }
inline void SetWorldSpecialCharacterStartListBottom(bool list_bottom) { m_special_char_list_bottom = list_bottom; }
private:
bool allow_unregistered;
bool display_expansions;
bool reject_duplicate_servers;
bool world_dev_test_servers_list_bottom;
bool world_special_character_start_list_bottom;
bool allow_token_login;
bool allow_password_login;
bool show_player_count;
bool auto_create_accounts;
bool auto_link_accounts;
bool update_insecure_passwords;
int encryption_mode;
int max_expansions_mask;
std::string eqemu_loginserver_address;
std::string default_loginserver_name;
bool m_allow_unregistered;
bool m_display_expansions;
bool m_reject_duplicate_servers;
bool m_world_dev_list_bottom;
bool m_special_char_list_bottom;
bool m_allow_token_login;
bool m_show_player_count;
bool m_auto_create_accounts;
int m_encryption_mode;
int m_max_expansions_mask;
std::string m_eqemu_loginserver_address;
std::string m_default_loginserver_name;
};

View File

@ -1,248 +0,0 @@
#include "server_manager.h"
#include "login_server.h"
#include "login_types.h"
#include <stdlib.h>
#include "../common/eqemu_logsys.h"
#include "../common/ip_util.h"
extern LoginServer server;
extern bool run_server;
ServerManager::ServerManager()
{
int listen_port = server.config.GetVariableInt("general", "listen_port", 5998);
m_server_connection = std::make_unique<EQ::Net::ServertalkServer>();
EQ::Net::ServertalkServerOptions opts;
opts.port = listen_port;
opts.ipv6 = false;
m_server_connection->Listen(opts);
LogInfo("Loginserver now listening on port [{0}]", listen_port);
m_server_connection->OnConnectionIdentified(
"World", [this](std::shared_ptr<EQ::Net::ServertalkServerConnection> world_connection) {
LogInfo(
"New World Server connection from {0}:{1}",
world_connection->Handle()->RemoteIP(),
world_connection->Handle()->RemotePort()
);
auto iter = m_world_servers.begin();
while (iter != m_world_servers.end()) {
if ((*iter)->GetConnection()->Handle()->RemoteIP().compare(world_connection->Handle()->RemoteIP()) ==
0 &&
(*iter)->GetConnection()->Handle()->RemotePort() == world_connection->Handle()->RemotePort()) {
LogInfo(
"World server already existed for {0}:{1}, removing existing connection.",
world_connection->Handle()->RemoteIP(),
world_connection->Handle()->RemotePort()
);
m_world_servers.erase(iter);
break;
}
++iter;
}
m_world_servers.push_back(std::make_unique<WorldServer>(world_connection));
}
);
m_server_connection->OnConnectionRemoved(
"World", [this](std::shared_ptr<EQ::Net::ServertalkServerConnection> c) {
auto iter = m_world_servers.begin();
while (iter != m_world_servers.end()) {
if ((*iter)->GetConnection()->GetUUID() == c->GetUUID()) {
LogInfo(
"World server {0} has been disconnected, removing.",
(*iter)->GetServerLongName()
);
m_world_servers.erase(iter);
return;
}
++iter;
}
}
);
}
ServerManager::~ServerManager() = default;
/**
* @param client
* @param sequence
* @return
*/
std::unique_ptr<EQApplicationPacket> ServerManager::CreateServerListPacket(Client *client, uint32 sequence)
{
unsigned int server_count = 0;
in_addr in{};
in.s_addr = client->GetConnection()->GetRemoteIP();
std::string client_ip = inet_ntoa(in);
LogDebug("ServerManager::CreateServerListPacket via client address [{0}]", client_ip);
for (const auto& world_server : m_world_servers)
{
if (world_server->IsAuthorized()) {
++server_count;
}
}
SerializeBuffer buf;
// LoginBaseMessage_Struct header
buf.WriteInt32(sequence);
buf.WriteInt8(0);
buf.WriteInt8(0);
buf.WriteInt32(0);
// LoginBaseReplyMessage_Struct
buf.WriteInt8(true); // success (no error)
buf.WriteInt32(0x65); // 101 "No Error" eqlsstr
buf.WriteString("");
// ServerListReply_Struct
buf.WriteInt32(server_count);
for (const auto& world_server : m_world_servers)
{
if (!world_server->IsAuthorized()) {
LogDebug(
"ServerManager::CreateServerListPacket | Server [{}] via IP [{}] is not authorized to be listed",
world_server->GetServerLongName(),
world_server->GetConnection()->Handle()->RemoteIP()
);
continue;
}
bool use_local_ip = false;
std::string world_ip = world_server->GetConnection()->Handle()->RemoteIP();
if (world_ip == client_ip || IpUtil::IsIpInPrivateRfc1918(client_ip)) {
use_local_ip = true;
}
LogDebug(
"CreateServerListPacket | Building list entry | Client [{}] IP [{}] Server Long Name [{}] Server IP [{}] ({})",
client->GetAccountName(),
client_ip,
world_server->GetServerLongName(),
use_local_ip ? world_server->GetLocalIP() : world_server->GetRemoteIP(),
use_local_ip ? "Local" : "Remote"
);
world_server->SerializeForClientServerList(buf, use_local_ip, client->GetClientVersion());
}
return std::make_unique<EQApplicationPacket>(OP_ServerListResponse, buf);
}
/**
* @param server_id
* @param client_account_id
* @param client_loginserver
*/
void ServerManager::SendUserToWorldRequest(
unsigned int server_id,
unsigned int client_account_id,
const std::string &client_loginserver
)
{
auto iter = m_world_servers.begin();
bool found = false;
while (iter != m_world_servers.end()) {
if ((*iter)->GetServerId() == server_id) {
EQ::Net::DynamicPacket outapp;
outapp.Resize(sizeof(UsertoWorldRequest_Struct));
auto *r = (UsertoWorldRequest_Struct *) outapp.Data();
r->worldid = server_id;
r->lsaccountid = client_account_id;
strncpy(r->login, &client_loginserver[0], 64);
(*iter)->GetConnection()->Send(ServerOP_UsertoWorldReq, outapp);
found = true;
LogNetcode(
"[UsertoWorldRequest] [Size: {}]\n{}",
outapp.Length(),
outapp.ToString()
);
}
++iter;
}
if (!found) {
LogError("Client requested a user to world but supplied an invalid id of {0}", server_id);
}
}
/**
* @param server_long_name
* @param server_short_name
* @param ignore
* @return
*/
bool ServerManager::ServerExists(
std::string server_long_name,
std::string server_short_name,
WorldServer *ignore
)
{
auto iter = m_world_servers.begin();
while (iter != m_world_servers.end()) {
if ((*iter).get() == ignore) {
++iter;
continue;
}
if ((*iter)->GetServerLongName() == server_long_name && (*iter)->GetServerShortName() == server_short_name) {
return true;
}
++iter;
}
return false;
}
/**
* @param server_long_name
* @param server_short_name
* @param ignore
*/
void ServerManager::DestroyServerByName(
std::string server_long_name,
std::string server_short_name,
WorldServer *ignore
)
{
auto iter = m_world_servers.begin();
while (iter != m_world_servers.end()) {
if ((*iter).get() == ignore) {
++iter;
continue;
}
if ((*iter)->GetServerLongName().compare(server_long_name) == 0 &&
(*iter)->GetServerShortName().compare(server_short_name) == 0) {
(*iter)->GetConnection()->Handle()->Disconnect();
iter = m_world_servers.erase(iter);
continue;
}
++iter;
}
}
/**
* @return
*/
const std::list<std::unique_ptr<WorldServer>> &ServerManager::getWorldServers() const
{
return m_world_servers;
}

View File

@ -1,81 +0,0 @@
#ifndef EQEMU_SERVERMANAGER_H
#define EQEMU_SERVERMANAGER_H
#include "../common/global_define.h"
#include "../common/servertalk.h"
#include "../common/packet_dump.h"
#include "../common/net/servertalk_server.h"
#include "world_server.h"
#include "client.h"
#include <list>
/**
* Server manager class, deals with management of the world servers
*/
class ServerManager {
public:
/**
* Constructor, sets up the TCP server and starts listening
*/
ServerManager();
/**
* Destructor, shuts down the TCP server.
*/
~ServerManager();
/**
* Sends a request to world to see if the client is banned or suspended
*
* @param server_id
* @param client_account_id
* @param client_loginserver
*/
void SendUserToWorldRequest(
unsigned int server_id,
unsigned int client_account_id,
const std::string &client_loginserver
);
/**
* Creates a server list packet for the client
*
* @param client
* @param sequence
* @return
*/
std::unique_ptr<EQApplicationPacket> CreateServerListPacket(Client *client, uint32 sequence);
/**
* Checks to see if there is a server exists with this name, ignoring option
*
* @param server_long_name
* @param server_short_name
* @param ignore
* @return
*/
bool ServerExists(std::string server_long_name, std::string server_short_name, WorldServer *ignore = nullptr);
/**
* Destroys a server with this name, ignoring option
*
* @param server_long_name
* @param server_short_name
* @param ignore
*/
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:
std::unique_ptr<EQ::Net::ServertalkServer> m_server_connection;
std::list<std::unique_ptr<WorldServer>> m_world_servers;
};
#endif

File diff suppressed because it is too large Load Diff

View File

@ -5,9 +5,10 @@
#include "../common/net/servertalk_server_connection.h"
#include "../common/servertalk.h"
#include "../common/packet_dump.h"
#include "database.h"
#include "../common/event/timer.h"
#include "login_types.h"
#include "client.h"
#include "../common/repositories/login_server_admins_repository.h"
#include <string>
#include <memory>
@ -17,25 +18,10 @@
class WorldServer {
public:
WorldServer(std::shared_ptr<EQ::Net::ServertalkServerConnection> worldserver_connection);
/**
* Destructor, frees our connection if it exists
*/
~WorldServer();
/**
* Resets the basic stats of this server.
*/
void Reset();
/**
* Accesses connection, it is intentional that this is not const (trust me).
*/
std::shared_ptr<EQ::Net::ServertalkServerConnection> GetConnection() { return m_connection; }
/**
* @return
*/
unsigned int GetServerId() const { return m_server_id; }
WorldServer *SetServerId(unsigned int id)
{
@ -43,25 +29,13 @@ public:
return this;
}
/**
* @return
*/
std::string GetServerLongName() const { return m_long_name; }
std::string GetServerShortName() const { return m_short_name; }
/**
* Gets whether the server is authorized to show up on the server list or not
* @return
*/
bool IsAuthorized() const { return m_is_server_authorized; }
std::string GetServerLongName() const { return m_server_long_name; }
std::string GetServerShortName() const { return m_server_short_name; }
bool IsAuthorizedToList() const { return m_is_server_authorized_to_list; }
std::string GetLocalIP() const { return m_local_ip; }
std::string GetRemoteIP() const { return m_remote_ip_address; }
/**
* Gets what kind of server this server is (legends, preferred, normal)
*
* @return
*/
// Gets what kind of server this server is (legends, preferred, normal)
unsigned int GetServerListID() const { return m_server_list_type_id; }
WorldServer *SetServerListTypeId(unsigned int in_server_list_id);
@ -69,97 +43,18 @@ public:
unsigned int GetZonesBooted() const { return m_zones_booted; }
unsigned int GetPlayersOnline() const { return m_players_online; }
/**
* Takes the info struct we received from world and processes it
*
* @param new_world_server_info_packet
*/
void Handle_NewLSInfo(ServerNewLSInfo_Struct *new_world_server_info_packet);
/**
* Takes the status struct we received from world and processes it
*
* @param server_login_status
*/
void Handle_LSStatus(ServerLSStatus_Struct *server_login_status);
bool HandleNewLoginserverInfoValidation(ServerNewLSInfo_Struct *new_world_server_info_packet);
/**
* Informs world that there is a client incoming with the following data.
*
* @param ip
* @param account
* @param key
* @param account_id
* @param loginserver_name
*/
void SendClientAuth(
std::string ip,
std::string account,
std::string key,
unsigned int account_id,
const std::string &loginserver_name
);
/**
* @param world_admin_id
* @param world_admin_username
* @param world_admin_password
* @param world_admin_password_hash
* @return
*/
void HandleNewWorldserver(LoginserverNewWorldRequest *req);
void HandleWorldserverStatusUpdate(LoginserverWorldStatusUpdate *u);
bool HandleNewWorldserverValidation(LoginserverNewWorldRequest *r);
void SendClientAuthToWorld(Client *c);
static bool ValidateWorldServerAdminLogin(
int world_admin_id,
const std::string &world_admin_username,
const std::string &world_admin_password,
const std::string &world_admin_password_hash
LoginWorldAdminAccountContext &c,
LoginServerAdminsRepository::LoginServerAdmins &admin
);
WorldServer *SetZonesBooted(unsigned int in_zones_booted);
WorldServer *SetPlayersOnline(unsigned int in_players_online);
WorldServer *SetServerStatus(int in_server_status);
WorldServer *SetServerProcessType(unsigned int in_server_process_type);
WorldServer *SetLongName(const std::string &in_long_name);
WorldServer *SetShortName(const std::string &in_short_name);
WorldServer *SetAccountName(const std::string &in_account_name);
WorldServer *SetAccountPassword(const std::string &in_account_password);
WorldServer *SetRemoteIp(const std::string &in_remote_ip);
WorldServer *SetLocalIp(const std::string &in_local_ip);
WorldServer *SetProtocol(const std::string &in_protocol);
WorldServer *SetVersion(const std::string &in_version);
WorldServer *SetServerDescription(const std::string &in_server_description);
WorldServer *SetIsServerAuthorized(bool in_is_server_authorized);
WorldServer *SetIsServerLoggedIn(bool in_is_server_logged_in);
WorldServer *SetIsServerTrusted(bool in_is_server_trusted);
bool IsServerAuthorized() const;
bool IsServerLoggedIn() const;
bool IsServerTrusted() const;
const std::string &GetAccountName() const;
const std::string &GetAccountPassword() const;
const std::string &GetLocalIp() const;
const std::string &GetProtocol() const;
const std::string &GetRemoteIp() const;
const std::string &GetServerDescription() const;
const std::string &GetVersion() const;
int GetServerStatus() const;
unsigned int GetServerListTypeId() const;
unsigned int GetServerProcessType() const;
bool HandleNewLoginserverRegisteredOnly(Database::DbWorldRegistration &world_registration);
bool HandleNewLoginserverInfoUnregisteredAllowed(Database::DbWorldRegistration &world_registration);
void SerializeForClientServerList(class SerializeBuffer& out, bool use_local_ip, LSClientVersion version) const;
void SerializeForClientServerList(class SerializeBuffer &out, bool use_local_ip, LSClientVersion version) const;
private:
/**
* Packet processing functions
*
* @param opcode
* @param packet
*/
void ProcessNewLSInfo(uint16_t opcode, const EQ::Net::Packet &packet);
void ProcessLSStatus(uint16_t opcode, const EQ::Net::Packet &packet);
void ProcessUserToWorldResponseLegacy(uint16_t opcode, const EQ::Net::Packet &packet);
@ -175,15 +70,15 @@ private:
unsigned int m_server_list_type_id;
unsigned int m_server_process_type;
std::string m_server_description;
std::string m_long_name;
std::string m_short_name;
std::string m_server_long_name;
std::string m_server_short_name;
std::string m_account_name;
std::string m_account_password;
std::string m_remote_ip_address;
std::string m_local_ip;
std::string m_protocol;
std::string m_version;
bool m_is_server_authorized;
std::string m_server_version;
bool m_is_server_authorized_to_list;
bool m_is_server_logged_in;
bool m_is_server_trusted;

View File

@ -0,0 +1,218 @@
#include "world_server_manager.h"
#include "login_server.h"
#include "login_types.h"
#include <stdlib.h>
#include "../common/eqemu_logsys.h"
#include "../common/ip_util.h"
extern LoginServer server;
extern bool run_server;
WorldServerManager::WorldServerManager()
{
int listen_port = server.config.GetVariableInt("general", "listen_port", 5998);
m_server_connection = std::make_unique<EQ::Net::ServertalkServer>();
EQ::Net::ServertalkServerOptions opts;
opts.port = listen_port;
opts.ipv6 = false;
m_server_connection->Listen(opts);
LogInfo("Loginserver now listening on port [{}]", listen_port);
m_server_connection->OnConnectionIdentified(
"World", [this](std::shared_ptr<EQ::Net::ServertalkServerConnection> c) {
LogInfo(
"New World Server connection from remote_ip [{}] port [{}]",
c->Handle()->RemoteIP(),
c->Handle()->RemotePort()
);
auto iter = std::find_if(
m_world_servers.begin(), m_world_servers.end(),
[&](const std::unique_ptr<WorldServer> &s) {
return s->GetConnection()->Handle()->RemoteIP() == c->Handle()->RemoteIP() &&
s->GetConnection()->Handle()->RemotePort() == c->Handle()->RemotePort();
}
);
if (iter != m_world_servers.end()) {
LogInfo(
"World server already existed for remote_ip [{}] port [{}] removing existing connection.",
c->Handle()->RemoteIP(),
c->Handle()->RemotePort()
);
m_world_servers.erase(iter);
}
m_world_servers.push_back(std::make_unique<WorldServer>(c));
}
);
m_server_connection->OnConnectionRemoved(
"World", [this](std::shared_ptr<EQ::Net::ServertalkServerConnection> c) {
auto iter = std::find_if(
m_world_servers.begin(), m_world_servers.end(),
[&](const std::unique_ptr<WorldServer> &server) {
return server->GetConnection()->GetUUID() == c->GetUUID();
}
);
if (iter != m_world_servers.end()) {
LogInfo(
"World server ID [{}] long_name [{}] short_name [{}] has been disconnected, removing.",
(*iter)->GetServerId(),
(*iter)->GetServerLongName(),
(*iter)->GetServerShortName()
);
m_world_servers.erase(iter);
}
}
);
}
WorldServerManager::~WorldServerManager() = default;
std::unique_ptr<EQApplicationPacket> WorldServerManager::CreateServerListPacket(Client *client, uint32 sequence)
{
unsigned int server_count = 0;
in_addr in{};
in.s_addr = client->GetConnection()->GetRemoteIP();
std::string client_ip = inet_ntoa(in);
LogDebug("ServerManager::CreateServerListPacket via client address [{}]", client_ip);
for (const auto &world_server: m_world_servers) {
if (world_server->IsAuthorizedToList()) {
++server_count;
}
}
SerializeBuffer buf;
// LoginBaseMessage_Struct header
buf.WriteInt32(sequence);
buf.WriteInt8(0);
buf.WriteInt8(0);
buf.WriteInt32(0);
// LoginBaseReplyMessage_Struct
buf.WriteInt8(true); // success (no error)
buf.WriteInt32(0x65); // 101 "No Error" eqlsstr
buf.WriteString("");
// ServerListReply_Struct
buf.WriteInt32(server_count);
for (const auto &s: m_world_servers) {
if (!s->IsAuthorizedToList()) {
LogDebug(
"ServerManager::CreateServerListPacket | Server [{}] via IP [{}] is not authorized to be listed",
s->GetServerLongName(),
s->GetConnection()->Handle()->RemoteIP()
);
continue;
}
bool use_local_ip = false;
std::string world_ip = s->GetConnection()->Handle()->RemoteIP();
if (world_ip == client_ip || IpUtil::IsIpInPrivateRfc1918(client_ip)) {
use_local_ip = true;
}
LogDebug(
"CreateServerListPacket | Building list entry | Client [{}] IP [{}] Server Long Name [{}] Server IP [{}] ({})",
client->GetAccountName(),
client_ip,
s->GetServerLongName(),
use_local_ip ? s->GetLocalIP() : s->GetRemoteIP(),
use_local_ip ? "Local" : "Remote"
);
s->SerializeForClientServerList(buf, use_local_ip, client->GetClientVersion());
}
return std::make_unique<EQApplicationPacket>(OP_ServerListResponse, buf);
}
void WorldServerManager::SendUserLoginToWorldRequest(
unsigned int server_id,
unsigned int client_account_id,
const std::string &client_loginserver
)
{
auto iter = std::find_if(
m_world_servers.begin(), m_world_servers.end(),
[&](const std::unique_ptr<WorldServer> &server) {
return server->GetServerId() == server_id;
}
);
if (iter != m_world_servers.end()) {
EQ::Net::DynamicPacket outapp;
outapp.Resize(sizeof(UsertoWorldRequest_Struct));
auto *r = reinterpret_cast<UsertoWorldRequest_Struct *>(outapp.Data());
r->worldid = server_id;
r->lsaccountid = client_account_id;
strncpy(r->login, client_loginserver.c_str(), 64);
(*iter)->GetConnection()->Send(ServerOP_UsertoWorldReq, outapp);
LogNetcode("[UsertoWorldRequest] [Size: {}]\n{}", outapp.Length(), outapp.ToString());
}
else {
LogError("Client requested a user to world but supplied an invalid id of {}", server_id);
}
}
bool WorldServerManager::DoesServerExist(
const std::string &server_long_name,
const std::string &server_short_name,
WorldServer *ignore
)
{
return std::any_of(
m_world_servers.begin(), m_world_servers.end(), [&](const std::unique_ptr<WorldServer> &s) {
return s.get() != ignore &&
s->GetServerLongName() == server_long_name &&
s->GetServerShortName() == server_short_name;
}
);
}
void WorldServerManager::DestroyServerByName(
std::string server_long_name,
std::string server_short_name,
WorldServer *ignore
)
{
std::erase_if(
m_world_servers, [&](const std::unique_ptr<WorldServer> &s) {
if (s.get() == ignore) {
return false;
}
if (s->GetServerLongName() == server_long_name &&
s->GetServerShortName() == server_short_name) {
s->GetConnection()->Handle()->Disconnect();
LogInfo(
"Removing world server ID [{}] long name [{}] short name [{}]",
s->GetServerId(),
server_long_name,
server_short_name
);
return true;
}
return false;
}
);
}
const std::list<std::unique_ptr<WorldServer>> &WorldServerManager::GetWorldServers() const
{
return m_world_servers;
}

View File

@ -0,0 +1,33 @@
#ifndef EQEMU_SERVERMANAGER_H
#define EQEMU_SERVERMANAGER_H
#include "../common/global_define.h"
#include "../common/servertalk.h"
#include "../common/packet_dump.h"
#include "../common/net/servertalk_server.h"
#include "world_server.h"
#include "client.h"
#include <list>
class WorldServerManager {
public:
WorldServerManager();
~WorldServerManager();
void SendUserLoginToWorldRequest(
unsigned int server_id,
unsigned int client_account_id,
const std::string &client_loginserver
);
std::unique_ptr<EQApplicationPacket> CreateServerListPacket(Client *client, uint32 sequence);
bool DoesServerExist(const std::string &s, const std::string &server_short_name, WorldServer *ignore = nullptr);
void DestroyServerByName(std::string s, std::string server_short_name, WorldServer *ignore = nullptr);
const std::list<std::unique_ptr<WorldServer>> &GetWorldServers() const;
private:
std::unique_ptr<EQ::Net::ServertalkServer> m_server_connection;
std::list<std::unique_ptr<WorldServer>> m_world_servers;
};
#endif

View File

@ -164,9 +164,9 @@ void ClientListEntry::LSUpdate(ZoneServer *iZS)
if (WorldConfig::get()->UpdateStats) {
auto pack = new ServerPacket;
pack->opcode = ServerOP_LSZoneInfo;
pack->size = sizeof(ZoneInfo_Struct);
pack->size = sizeof(LoginserverZoneInfoUpdate);
pack->pBuffer = new uchar[pack->size];
ZoneInfo_Struct *zone = (ZoneInfo_Struct *) pack->pBuffer;
auto *zone = (LoginserverZoneInfoUpdate *) pack->pBuffer;
zone->count = iZS->NumPlayers();
zone->zone = iZS->GetZoneID();
zone->zone_wid = iZS->GetID();
@ -174,6 +174,7 @@ void ClientListEntry::LSUpdate(ZoneServer *iZS)
safe_delete(pack);
}
}
void ClientListEntry::LSZoneChange(ZoneToZone_Struct *ztz)
{
if (WorldConfig::get()->UpdateStats) {
@ -181,7 +182,7 @@ void ClientListEntry::LSZoneChange(ZoneToZone_Struct *ztz)
pack->opcode = ServerOP_LSPlayerZoneChange;
pack->size = sizeof(ServerLSPlayerZoneChange_Struct);
pack->pBuffer = new uchar[pack->size];
ServerLSPlayerZoneChange_Struct *zonechange = (ServerLSPlayerZoneChange_Struct *) pack->pBuffer;
auto *zonechange = (ServerLSPlayerZoneChange_Struct *) pack->pBuffer;
zonechange->lsaccount_id = LSID();
zonechange->from = ztz->current_zone_id;
zonechange->to = ztz->requested_zone_id;

View File

@ -1,20 +1,3 @@
/* EQEMu: Everquest Server Emulator
Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org)
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 "../common/global_define.h"
#include <iostream>
#include <string.h>
@ -37,7 +20,6 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#include "cliententry.h"
#include "world_config.h"
extern ZSList zoneserver_list;
extern ClientList client_list;
extern uint32 numzones;
@ -609,11 +591,11 @@ void LoginServer::SendInfo()
auto pack = new ServerPacket;
pack->opcode = ServerOP_NewLSInfo;
pack->size = sizeof(ServerNewLSInfo_Struct);
pack->size = sizeof(LoginserverNewWorldRequest);
pack->pBuffer = new uchar[pack->size];
memset(pack->pBuffer, 0, pack->size);
auto *l = (ServerNewLSInfo_Struct *) pack->pBuffer;
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());
@ -657,10 +639,10 @@ void LoginServer::SendStatus()
auto pack = new ServerPacket;
pack->opcode = ServerOP_LSStatus;
pack->size = sizeof(ServerLSStatus_Struct);
pack->size = sizeof(LoginserverWorldStatusUpdate);
pack->pBuffer = new uchar[pack->size];
memset(pack->pBuffer, 0, pack->size);
auto loginserver_status = (ServerLSStatus_Struct *) pack->pBuffer;
auto loginserver_status = (LoginserverWorldStatusUpdate *) pack->pBuffer;
if (WorldConfig::get()->Locked) {
loginserver_status->status = -2;
@ -678,9 +660,6 @@ void LoginServer::SendStatus()
delete pack;
}
/**
* @param pack
*/
void LoginServer::SendPacket(ServerPacket *pack)
{
if (m_legacy_client) {
@ -698,15 +677,15 @@ void LoginServer::SendAccountUpdate(ServerPacket *pack)
return;
}
auto *ls_account_update = (ServerLSAccountUpdate_Struct *) pack->pBuffer;
auto *req = (LoginserverAccountUpdate *) pack->pBuffer;
if (CanUpdate()) {
LogInfo(
"Sending ServerOP_LSAccountUpdate packet to loginserver: [{0}]:[{1}]",
m_loginserver_address,
m_loginserver_port
);
strn0cpy(ls_account_update->worldaccount, m_login_account.c_str(), 30);
strn0cpy(ls_account_update->worldpassword, m_login_password.c_str(), 30);
strn0cpy(req->world_account, m_login_account.c_str(), 30);
strn0cpy(req->world_password, m_login_password.c_str(), 30);
SendPacket(pack);
}
}

View File

@ -14,14 +14,14 @@ void SetLoginserverInfo(Client *c, const Seperator *sep)
const std::string& email = sep->arg[2];
const std::string& password = sep->arg[3];
auto pack = new ServerPacket(ServerOP_LSAccountUpdate, sizeof(ServerLSAccountUpdate_Struct));
auto pack = new ServerPacket(ServerOP_LSAccountUpdate, sizeof(LoginserverAccountUpdate));
auto s = (ServerLSAccountUpdate_Struct *) pack->pBuffer;
auto s = (LoginserverAccountUpdate *) pack->pBuffer;
s->useraccountid = c->LSAccountID();
strn0cpy(s->useraccount, c->AccountName(), 30);
s->user_account_id = c->LSAccountID();
strn0cpy(s->user_account_name, c->AccountName(), 30);
strn0cpy(s->user_email, email.c_str(), 100);
strn0cpy(s->userpassword, password.c_str(), 50);
strn0cpy(s->user_account_password, password.c_str(), 50);
worldserver.SendPacket(pack);
safe_delete(pack);