diff --git a/src/AccountSync.cc b/src/AccountSync.cc index a6680947..335de790 100644 --- a/src/AccountSync.cc +++ b/src/AccountSync.cc @@ -170,6 +170,59 @@ void configure_from_json(const phosg::JSON& json) { configure(cfg); } +asio::awaitable acquire_login_lock( + uint32_t account_id, + const std::string& version_name, + const std::string& existing_session_nonce) { + auto cfg = get_config(); + + LoginLockAcquireResult ret; + if (!cfg.enabled || !cfg.enable_login_locks) { + co_return ret; + } + + if (!existing_session_nonce.empty()) { + ret.session_nonce = existing_session_nonce; + co_return ret; + } + + ret.session_nonce = std::format("{}-{}-{}", source_label(cfg), account_id, now_usecs()); + std::fprintf(stderr, + "[AccountSync] warning login_locks enabled but coordinator acquire is not implemented; allowing account_id=%010u source=%s version=%s nonce=%s\n", + static_cast(account_id), + source_label(cfg).c_str(), + version_name.c_str(), + ret.session_nonce.c_str()); + + co_return ret; +} + +void notify_login_session_end( + uint32_t account_id, + const std::string& session_nonce, + const std::string& version_name) { + auto cfg = get_config(); + if (!cfg.enabled || !cfg.enable_login_locks) { + return; + } + + std::fprintf(stderr, + "[AccountSync] event=login_session_end source=%s source_region=%s source_ship=%s account_store=%s account_id=%010u session_nonce=%s version=%s\n", + source_label(cfg).c_str(), + cfg.source_region.c_str(), + cfg.source_ship.c_str(), + cfg.account_store.c_str(), + static_cast(account_id), + session_nonce.c_str(), + version_name.c_str()); + + auto line = base_event_json(cfg, "login_session_end", account_id) + + std::format(",\"session_nonce\":\"{}\",\"version\":\"{}\"}}", + json_escape(session_nonce), + json_escape(version_name)); + append_spool_line(cfg, line); +} + void notify_account_saved(uint32_t account_id, const std::string& filename) { auto cfg = get_config(); if (!cfg.enabled || !cfg.notify_account_saves) { diff --git a/src/AccountSync.hh b/src/AccountSync.hh index cbfeb78d..54c538cb 100644 --- a/src/AccountSync.hh +++ b/src/AccountSync.hh @@ -2,6 +2,8 @@ #include #include + +#include #include #include @@ -33,6 +35,24 @@ struct Config { void configure(const Config& cfg); void configure_from_json(const phosg::JSON& json); +struct LoginLockAcquireResult { + bool allowed = true; + bool fail_open_used = false; + std::string session_nonce; + std::string message; + std::string holder_source; +}; + +asio::awaitable acquire_login_lock( + uint32_t account_id, + const std::string& version_name, + const std::string& existing_session_nonce); + +void notify_login_session_end( + uint32_t account_id, + const std::string& session_nonce, + const std::string& version_name); + void notify_account_saved(uint32_t account_id, const std::string& filename); void notify_backup_saved(uint32_t account_id, size_t slot, const std::string& filename); diff --git a/src/Client.cc b/src/Client.cc index 177c7468..eb130905 100644 --- a/src/Client.cc +++ b/src/Client.cc @@ -278,6 +278,15 @@ Client::~Client() { this->bb_character_index); } } + + if (this->account_sync_lock_acquired && this->login && this->login->account) { + AccountSync::notify_login_session_end( + this->login->account->account_id, + this->account_sync_session_nonce, + phosg::name_for_enum(this->version())); + this->account_sync_lock_acquired = false; + } + this->log.info_f("Deleted"); } diff --git a/src/Client.hh b/src/Client.hh index 5bdf509d..40a04b2f 100644 --- a/src/Client.hh +++ b/src/Client.hh @@ -132,6 +132,9 @@ public: uint64_t xb_user_id = 0; uint32_t xb_unknown_a1b = 0; std::shared_ptr login; + bool account_sync_lock_acquired = false; + uint32_t account_sync_lock_account_id = 0; + std::string account_sync_session_nonce; std::shared_ptr proxy_session; // Patch server state (only used for PC_PATCH and BB_PATCH versions) diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index f32a0718..326b95af 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -558,6 +558,25 @@ asio::awaitable start_login_server_procedure(std::shared_ptr c) { static asio::awaitable on_login_complete(std::shared_ptr c) { auto s = c->require_server_state(); + if (c->login && c->login->account) { + auto lock_res = co_await AccountSync::acquire_login_lock( + c->login->account->account_id, + phosg::name_for_enum(c->version()), + c->account_sync_session_nonce); + + if (!lock_res.allowed) { + c->log.info_f("Login lock denied: {}", lock_res.message); + c->channel->disconnect(); + co_return; + } + + if (!lock_res.session_nonce.empty()) { + c->account_sync_lock_acquired = true; + c->account_sync_lock_account_id = c->login->account->account_id; + c->account_sync_session_nonce = lock_res.session_nonce; + } + } + c->convert_account_to_temporary_if_nte(); if (!is_v4(c->version())) {