support chat commands on proxy server
This commit is contained in:
+176
-293
@@ -29,6 +29,7 @@
|
||||
#include "ProxyCommands.hh"
|
||||
|
||||
using namespace std;
|
||||
using namespace std::placeholders;
|
||||
|
||||
|
||||
|
||||
@@ -37,13 +38,6 @@ static const uint32_t UNLICENSED_SESSION_TIMEOUT_USECS = 10 * 1000000; // 10 sec
|
||||
|
||||
|
||||
|
||||
static void flush_and_free_bufferevent(struct bufferevent* bev) {
|
||||
bufferevent_flush(bev, EV_READ | EV_WRITE, BEV_FINISHED);
|
||||
bufferevent_free(bev);
|
||||
}
|
||||
|
||||
|
||||
|
||||
ProxyServer::ProxyServer(
|
||||
shared_ptr<struct event_base> base,
|
||||
shared_ptr<ServerState> state)
|
||||
@@ -134,7 +128,8 @@ void ProxyServer::connect_client(struct bufferevent* bev, uint16_t server_port)
|
||||
} catch (const out_of_range&) {
|
||||
this->log(INFO, "Virtual connection received on unregistered port %hu; closing it",
|
||||
server_port);
|
||||
flush_and_free_bufferevent(bev);
|
||||
bufferevent_flush(bev, EV_READ | EV_WRITE, BEV_FINISHED);
|
||||
bufferevent_free(bev);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -166,7 +161,9 @@ void ProxyServer::on_client_connect(
|
||||
}
|
||||
auto session = emplace_ret.first->second;
|
||||
session->log(INFO, "Opened linked session");
|
||||
session->resume(bev);
|
||||
|
||||
Channel ch(bev, version, nullptr, nullptr, session.get(), "", TerminalFormat::FG_YELLOW, TerminalFormat::FG_GREEN);
|
||||
session->resume(move(ch));
|
||||
|
||||
// If no default destination exists, or the client is not a patch client,
|
||||
// create an unlinked session - we'll have to get the destination from the
|
||||
@@ -195,22 +192,15 @@ void ProxyServer::on_client_connect(
|
||||
uint32_t client_key = random_object<uint32_t>();
|
||||
auto cmd = prepare_server_init_contents_dc_pc_gc(
|
||||
false, server_key, client_key);
|
||||
send_command(
|
||||
session->bev.get(),
|
||||
session->version,
|
||||
session->crypt_out.get(),
|
||||
0x02,
|
||||
0,
|
||||
&cmd,
|
||||
sizeof(cmd),
|
||||
"unlinked proxy client");
|
||||
bufferevent_flush(session->bev.get(), EV_READ | EV_WRITE, BEV_FLUSH);
|
||||
session->channel.send(0x02, 0x00, &cmd, sizeof(cmd));
|
||||
// TODO: Is this actually needed?
|
||||
// bufferevent_flush(session->channel.bev.get(), EV_READ | EV_WRITE, BEV_FLUSH);
|
||||
if (version == GameVersion::PC) {
|
||||
session->crypt_out.reset(new PSOPCEncryption(server_key));
|
||||
session->crypt_in.reset(new PSOPCEncryption(client_key));
|
||||
session->channel.crypt_out.reset(new PSOPCEncryption(server_key));
|
||||
session->channel.crypt_in.reset(new PSOPCEncryption(client_key));
|
||||
} else {
|
||||
session->crypt_out.reset(new PSOGCEncryption(server_key));
|
||||
session->crypt_in.reset(new PSOGCEncryption(client_key));
|
||||
session->channel.crypt_out.reset(new PSOGCEncryption(server_key));
|
||||
session->channel.crypt_in.reset(new PSOGCEncryption(client_key));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -220,22 +210,14 @@ void ProxyServer::on_client_connect(
|
||||
random_data(server_key.data(), server_key.bytes());
|
||||
random_data(client_key.data(), client_key.bytes());
|
||||
auto cmd = prepare_server_init_contents_bb(server_key, client_key);
|
||||
send_command(
|
||||
session->bev.get(),
|
||||
session->version,
|
||||
session->crypt_out.get(),
|
||||
0x03,
|
||||
0,
|
||||
&cmd,
|
||||
sizeof(cmd),
|
||||
"unlinked proxy client");
|
||||
bufferevent_flush(session->bev.get(), EV_READ | EV_WRITE, BEV_FLUSH);
|
||||
|
||||
session->channel.send(0x03, 0x00, &cmd, sizeof(cmd));
|
||||
// TODO: Is this actually needed?
|
||||
// bufferevent_flush(session->bev.get(), EV_READ | EV_WRITE, BEV_FLUSH);
|
||||
static const string expected_first_data("\xB4\x00\x93\x00\x00\x00\x00\x00", 8);
|
||||
session->detector_crypt.reset(new PSOBBMultiKeyDetectorEncryption(
|
||||
this->state->bb_private_keys, expected_first_data, cmd.client_key.data(), sizeof(cmd.client_key)));
|
||||
session->crypt_in = session->detector_crypt;
|
||||
session->crypt_out.reset(new PSOBBMultiKeyImitatorEncryption(
|
||||
session->channel.crypt_in = session->detector_crypt;
|
||||
session->channel.crypt_out.reset(new PSOBBMultiKeyImitatorEncryption(
|
||||
session->detector_crypt, cmd.server_key.data(), sizeof(cmd.server_key), true));
|
||||
break;
|
||||
}
|
||||
@@ -251,27 +233,23 @@ ProxyServer::UnlinkedSession::UnlinkedSession(
|
||||
ProxyServer* server, struct bufferevent* bev, uint16_t local_port, GameVersion version)
|
||||
: server(server),
|
||||
log(string_printf("[ProxyServer:UnlinkedSession:%p] ", bev)),
|
||||
bev(bev, flush_and_free_bufferevent),
|
||||
channel(
|
||||
bev,
|
||||
version,
|
||||
ProxyServer::UnlinkedSession::on_input,
|
||||
ProxyServer::UnlinkedSession::on_error,
|
||||
this,
|
||||
string_printf("UnlinkedSession:%p", bev),
|
||||
TerminalFormat::FG_YELLOW,
|
||||
TerminalFormat::FG_GREEN),
|
||||
local_port(local_port),
|
||||
version(version) {
|
||||
memset(&this->next_destination, 0, sizeof(this->next_destination));
|
||||
bufferevent_setcb(this->bev.get(),
|
||||
&UnlinkedSession::dispatch_on_client_input, nullptr,
|
||||
&UnlinkedSession::dispatch_on_client_error, this);
|
||||
bufferevent_enable(this->bev.get(), EV_READ | EV_WRITE);
|
||||
}
|
||||
|
||||
void ProxyServer::UnlinkedSession::dispatch_on_client_input(
|
||||
struct bufferevent*, void* ctx) {
|
||||
reinterpret_cast<UnlinkedSession*>(ctx)->on_client_input();
|
||||
}
|
||||
void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint32_t, std::string& data) {
|
||||
auto* session = reinterpret_cast<UnlinkedSession*>(ch.context_obj);
|
||||
|
||||
void ProxyServer::UnlinkedSession::dispatch_on_client_error(
|
||||
struct bufferevent*, short events, void* ctx) {
|
||||
reinterpret_cast<UnlinkedSession*>(ctx)->on_client_error(events);
|
||||
}
|
||||
|
||||
void ProxyServer::UnlinkedSession::on_client_input() {
|
||||
bool should_close_unlinked_session = false;
|
||||
shared_ptr<const License> license;
|
||||
uint32_t sub_version = 0;
|
||||
@@ -280,58 +258,54 @@ void ProxyServer::UnlinkedSession::on_client_input() {
|
||||
string login_command_bb;
|
||||
|
||||
try {
|
||||
for_each_received_command(this->bev.get(), this->version, this->crypt_in.get(),
|
||||
[&](uint16_t command, uint32_t flag, const string& data) {
|
||||
print_received_command(command, flag, data.data(), data.size(),
|
||||
this->version, "unlinked proxy client");
|
||||
if (session->version == GameVersion::PC) {
|
||||
// We should only get a 9D while the session is unlinked; if we get
|
||||
// anything else, disconnect
|
||||
if (command != 0x9D) {
|
||||
throw runtime_error("command is not 9D");
|
||||
}
|
||||
const auto& cmd = check_size_t<C_Login_PC_9D>(
|
||||
data, sizeof(C_Login_PC_9D), sizeof(C_LoginWithUnusedSpace_PC_9D));
|
||||
license = session->server->state->license_manager->verify_pc(
|
||||
stoul(cmd.serial_number, nullptr, 16), cmd.access_key);
|
||||
sub_version = cmd.sub_version;
|
||||
character_name = cmd.name;
|
||||
|
||||
if (this->version == GameVersion::PC) {
|
||||
// We should only get a 9D while the session is unlinked; if we get
|
||||
// anything else, disconnect
|
||||
if (command != 0x9D) {
|
||||
throw runtime_error("command is not 9D");
|
||||
}
|
||||
const auto& cmd = check_size_t<C_Login_PC_9D>(data, sizeof(C_Login_PC_9D), sizeof(C_LoginWithUnusedSpace_PC_9D));
|
||||
license = this->server->state->license_manager->verify_pc(
|
||||
stoul(cmd.serial_number, nullptr, 16), cmd.access_key);
|
||||
sub_version = cmd.sub_version;
|
||||
character_name = cmd.name;
|
||||
} else if (session->version == GameVersion::GC) {
|
||||
// We should only get a 9E while the session is unlinked; if we get
|
||||
// anything else, disconnect
|
||||
if (command != 0x9E) {
|
||||
throw runtime_error("command is not 9E");
|
||||
}
|
||||
const auto& cmd = check_size_t<C_Login_GC_9E>(
|
||||
data, sizeof(C_Login_GC_9E), sizeof(C_LoginWithUnusedSpace_GC_9E));
|
||||
license = session->server->state->license_manager->verify_gc(
|
||||
stoul(cmd.serial_number, nullptr, 16), cmd.access_key);
|
||||
sub_version = cmd.sub_version;
|
||||
character_name = cmd.name;
|
||||
client_config.cfg = cmd.client_config.cfg;
|
||||
|
||||
} else if (this->version == GameVersion::GC) {
|
||||
// We should only get a 9E while the session is unlinked; if we get
|
||||
// anything else, disconnect
|
||||
if (command != 0x9E) {
|
||||
throw runtime_error("command is not 9E");
|
||||
}
|
||||
const auto& cmd = check_size_t<C_Login_GC_9E>(data, sizeof(C_Login_GC_9E), sizeof(C_LoginWithUnusedSpace_GC_9E));
|
||||
license = this->server->state->license_manager->verify_gc(
|
||||
stoul(cmd.serial_number, nullptr, 16), cmd.access_key);
|
||||
sub_version = cmd.sub_version;
|
||||
character_name = cmd.name;
|
||||
client_config.cfg = cmd.client_config.cfg;
|
||||
} else if (session->version == GameVersion::BB) {
|
||||
// We should only get a 93 while the session is unlinked; if we get
|
||||
// anything else, disconnect
|
||||
if (command != 0x93) {
|
||||
throw runtime_error("command is not 93");
|
||||
}
|
||||
const auto& cmd = check_size_t<C_Login_BB_93>(data);
|
||||
license = session->server->state->license_manager->verify_bb(
|
||||
cmd.username, cmd.password);
|
||||
login_command_bb = move(data);
|
||||
|
||||
} else if (this->version == GameVersion::BB) {
|
||||
// We should only get a 93 while the session is unlinked; if we get
|
||||
// anything else, disconnect
|
||||
if (command != 0x93) {
|
||||
throw runtime_error("command is not 93");
|
||||
}
|
||||
const auto& cmd = check_size_t<C_Login_BB_93>(data);
|
||||
license = this->server->state->license_manager->verify_bb(
|
||||
cmd.username, cmd.password);
|
||||
login_command_bb = data;
|
||||
|
||||
} else {
|
||||
throw logic_error("unsupported unlinked session version");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
throw logic_error("unsupported unlinked session version");
|
||||
}
|
||||
|
||||
} catch (const exception& e) {
|
||||
this->log(ERROR, "Failed to process command from unlinked client: %s", e.what());
|
||||
session->log(ERROR, "Failed to process command from unlinked client: %s", e.what());
|
||||
should_close_unlinked_session = true;
|
||||
}
|
||||
|
||||
struct bufferevent* session_key = this->bev.get();
|
||||
struct bufferevent* session_key = ch.bev.get();
|
||||
|
||||
// If license is non-null, then the client has a password and can be connected
|
||||
// to the remote lobby server.
|
||||
@@ -341,75 +315,80 @@ void ProxyServer::UnlinkedSession::on_client_input() {
|
||||
should_close_unlinked_session = true;
|
||||
|
||||
// Look up the linked session for this license (if any)
|
||||
shared_ptr<LinkedSession> session;
|
||||
shared_ptr<LinkedSession> linked_session;
|
||||
try {
|
||||
session = this->server->id_to_session.at(license->serial_number);
|
||||
session->log(INFO, "Resuming linked session from unlinked session");
|
||||
linked_session = session->server->id_to_session.at(license->serial_number);
|
||||
linked_session->log(INFO, "Resuming linked session from unlinked session");
|
||||
|
||||
} catch (const out_of_range&) {
|
||||
// If there's no open session for this license, then there must be a valid
|
||||
// destination somewhere - either in the client config or in the unlinked
|
||||
// session
|
||||
if (client_config.cfg.magic == CLIENT_CONFIG_MAGIC) {
|
||||
session.reset(new LinkedSession(
|
||||
this->server,
|
||||
this->local_port,
|
||||
this->version,
|
||||
linked_session.reset(new LinkedSession(
|
||||
session->server,
|
||||
session->local_port,
|
||||
session->version,
|
||||
license,
|
||||
client_config));
|
||||
session->log(INFO, "Opened licensed session for unlinked session based on client config");
|
||||
} else if (this->next_destination.ss_family == AF_INET) {
|
||||
session.reset(new LinkedSession(
|
||||
this->server,
|
||||
this->local_port,
|
||||
this->version,
|
||||
linked_session->log(INFO, "Opened licensed session for unlinked session based on client config");
|
||||
} else if (session->next_destination.ss_family == AF_INET) {
|
||||
linked_session.reset(new LinkedSession(
|
||||
session->server,
|
||||
session->local_port,
|
||||
session->version,
|
||||
license,
|
||||
this->next_destination));
|
||||
session->log(INFO, "Opened licensed session for unlinked session based on unlinked default destination");
|
||||
session->next_destination));
|
||||
linked_session->log(INFO, "Opened licensed session for unlinked session based on unlinked default destination");
|
||||
} else {
|
||||
this->log(ERROR, "Cannot open linked session: no valid destination in client config or unlinked session");
|
||||
session->log(ERROR, "Cannot open linked session: no valid destination in client config or unlinked session");
|
||||
}
|
||||
}
|
||||
|
||||
if (session.get()) {
|
||||
this->server->id_to_session.emplace(license->serial_number, session);
|
||||
if (session->version != this->version) {
|
||||
session->log(ERROR, "Linked session has different game version");
|
||||
if (linked_session.get()) {
|
||||
session->server->id_to_session.emplace(license->serial_number, linked_session);
|
||||
if (linked_session->version != session->version) {
|
||||
linked_session->log(ERROR, "Linked session has different game version");
|
||||
} else {
|
||||
// Resume the linked session using the unlinked session
|
||||
try {
|
||||
if (this->version == GameVersion::BB) {
|
||||
session->resume(move(this->bev), this->crypt_in, this->crypt_out,
|
||||
this->detector_crypt, move(login_command_bb));
|
||||
if (session->version == GameVersion::BB) {
|
||||
linked_session->resume(
|
||||
move(session->channel),
|
||||
session->detector_crypt,
|
||||
move(login_command_bb));
|
||||
} else {
|
||||
session->resume(move(this->bev), this->crypt_in, this->crypt_out,
|
||||
this->detector_crypt, sub_version, character_name);
|
||||
linked_session->resume(
|
||||
move(session->channel),
|
||||
session->detector_crypt,
|
||||
sub_version,
|
||||
character_name);
|
||||
}
|
||||
this->crypt_in.reset();
|
||||
this->crypt_out.reset();
|
||||
} catch (const exception& e) {
|
||||
session->log(ERROR, "Failed to resume linked session: %s", e.what());
|
||||
linked_session->log(ERROR, "Failed to resume linked session: %s", e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (should_close_unlinked_session) {
|
||||
this->log(INFO, "Closing session");
|
||||
this->server->bev_to_unlinked_session.erase(session_key);
|
||||
session->log(INFO, "Closing session");
|
||||
session->server->bev_to_unlinked_session.erase(session_key);
|
||||
// At this point, (*this) is destroyed! We must be careful not to touch it.
|
||||
}
|
||||
}
|
||||
|
||||
void ProxyServer::UnlinkedSession::on_client_error(short events) {
|
||||
void ProxyServer::UnlinkedSession::on_error(Channel& ch, short events) {
|
||||
auto* session = reinterpret_cast<UnlinkedSession*>(ch.context_obj);
|
||||
|
||||
if (events & BEV_EVENT_ERROR) {
|
||||
int err = EVUTIL_SOCKET_ERROR();
|
||||
this->log(WARNING, "Error %d (%s) in unlinked client stream", err,
|
||||
session->log(WARNING, "Error %d (%s) in unlinked client stream", err,
|
||||
evutil_socket_error_to_string(err));
|
||||
}
|
||||
if (events & (BEV_EVENT_ERROR | BEV_EVENT_EOF)) {
|
||||
this->log(WARNING, "Unlinked client has disconnected");
|
||||
this->server->bev_to_unlinked_session.erase(this->bev.get());
|
||||
session->log(WARNING, "Unlinked client has disconnected");
|
||||
session->server->bev_to_unlinked_session.erase(session->channel.bev.get());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,23 +401,34 @@ ProxyServer::LinkedSession::LinkedSession(
|
||||
GameVersion version)
|
||||
: server(server),
|
||||
id(id),
|
||||
client_name(string_printf("LinkedSession:%08" PRIX64 ":client", this->id)),
|
||||
server_name(string_printf("LinkedSession:%08" PRIX64 ":server", this->id)),
|
||||
log(string_printf("[ProxyServer:LinkedSession:%08" PRIX64 "] ", this->id)),
|
||||
timeout_event(event_new(this->server->base.get(), -1, EV_TIMEOUT,
|
||||
&LinkedSession::dispatch_on_timeout, this), event_free),
|
||||
license(nullptr),
|
||||
client_bev(nullptr, flush_and_free_bufferevent),
|
||||
server_bev(nullptr, flush_and_free_bufferevent),
|
||||
client_channel(
|
||||
version,
|
||||
nullptr,
|
||||
nullptr,
|
||||
this,
|
||||
string_printf("LinkedSession:%08" PRIX64 ":client", this->id),
|
||||
TerminalFormat::FG_YELLOW,
|
||||
TerminalFormat::FG_GREEN),
|
||||
server_channel(
|
||||
version,
|
||||
nullptr,
|
||||
nullptr,
|
||||
this,
|
||||
string_printf("LinkedSession:%08" PRIX64 ":server", this->id),
|
||||
TerminalFormat::FG_YELLOW,
|
||||
TerminalFormat::FG_RED),
|
||||
local_port(local_port),
|
||||
remote_ip_crc(0),
|
||||
enable_remote_ip_crc_patch(false),
|
||||
version(version),
|
||||
sub_version(0), // This is set during resume()
|
||||
remote_guild_card_number(0),
|
||||
suppress_newserv_commands(true),
|
||||
enable_chat_filter(true),
|
||||
enable_switch_assist(false),
|
||||
switch_assist(false),
|
||||
infinite_hp(false),
|
||||
infinite_tp(false),
|
||||
save_files(false),
|
||||
@@ -490,60 +480,48 @@ ProxyServer::LinkedSession::LinkedSession(
|
||||
}
|
||||
|
||||
void ProxyServer::LinkedSession::resume(
|
||||
unique_ptr<struct bufferevent, void(*)(struct bufferevent*)>&& client_bev,
|
||||
shared_ptr<PSOEncryption> client_input_crypt,
|
||||
shared_ptr<PSOEncryption> client_output_crypt,
|
||||
Channel&& client_channel,
|
||||
shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt,
|
||||
uint32_t sub_version,
|
||||
const string& character_name) {
|
||||
this->sub_version = sub_version;
|
||||
this->character_name = character_name;
|
||||
this->resume_inner(move(client_bev), client_input_crypt, client_output_crypt,
|
||||
detector_crypt);
|
||||
this->resume_inner(move(client_channel), detector_crypt);
|
||||
}
|
||||
|
||||
void ProxyServer::LinkedSession::resume(
|
||||
unique_ptr<struct bufferevent, void(*)(struct bufferevent*)>&& client_bev,
|
||||
shared_ptr<PSOEncryption> client_input_crypt,
|
||||
shared_ptr<PSOEncryption> client_output_crypt,
|
||||
Channel&& client_channel,
|
||||
shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt,
|
||||
string&& login_command_bb) {
|
||||
this->login_command_bb = move(login_command_bb);
|
||||
this->resume_inner(move(client_bev), client_input_crypt, client_output_crypt,
|
||||
detector_crypt);
|
||||
this->resume_inner(move(client_channel), detector_crypt);
|
||||
}
|
||||
|
||||
void ProxyServer::LinkedSession::resume(struct bufferevent* client_bev) {
|
||||
unique_ptr<struct bufferevent, void(*)(struct bufferevent*)> bev_unique(
|
||||
client_bev, flush_and_free_bufferevent);
|
||||
void ProxyServer::LinkedSession::resume(Channel&& client_channel) {
|
||||
this->sub_version = 0;
|
||||
this->character_name.clear();
|
||||
this->resume_inner(move(bev_unique), nullptr, nullptr, nullptr);
|
||||
this->resume_inner(move(client_channel), nullptr);
|
||||
}
|
||||
|
||||
void ProxyServer::LinkedSession::resume_inner(
|
||||
unique_ptr<struct bufferevent, void(*)(struct bufferevent*)>&& client_bev,
|
||||
shared_ptr<PSOEncryption> client_input_crypt,
|
||||
shared_ptr<PSOEncryption> client_output_crypt,
|
||||
Channel&& client_channel,
|
||||
shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt) {
|
||||
if (this->client_bev.get()) {
|
||||
if (this->client_channel.connected()) {
|
||||
throw runtime_error("client connection is already open for this session");
|
||||
}
|
||||
if (this->next_destination.ss_family != AF_INET) {
|
||||
throw logic_error("attempted to resume an unlicensed linked session without destination set");
|
||||
}
|
||||
|
||||
this->client_bev = move(client_bev);
|
||||
bufferevent_setcb(this->client_bev.get(),
|
||||
&ProxyServer::LinkedSession::dispatch_on_client_input, nullptr,
|
||||
&ProxyServer::LinkedSession::dispatch_on_client_error, this);
|
||||
bufferevent_enable(this->client_bev.get(), EV_READ | EV_WRITE);
|
||||
this->client_channel.replace_with(
|
||||
move(client_channel),
|
||||
ProxyServer::LinkedSession::on_input,
|
||||
ProxyServer::LinkedSession::on_error,
|
||||
this,
|
||||
string_printf("LinkedSession:%08" PRIX64 ":client", this->id));
|
||||
|
||||
this->detector_crypt = detector_crypt;
|
||||
this->client_input_crypt = client_input_crypt;
|
||||
this->client_output_crypt = client_output_crypt;
|
||||
this->server_input_crypt.reset();
|
||||
this->server_output_crypt.reset();
|
||||
this->server_channel.disconnect();
|
||||
this->saving_files.clear();
|
||||
|
||||
this->connect();
|
||||
@@ -552,9 +530,6 @@ void ProxyServer::LinkedSession::resume_inner(
|
||||
void ProxyServer::LinkedSession::connect() {
|
||||
// Connect to the remote server. The command handlers will do the login steps
|
||||
// and set up forwarding
|
||||
this->server_bev.reset(bufferevent_socket_new(this->server->base.get(), -1,
|
||||
BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS));
|
||||
|
||||
struct sockaddr_storage local_ss;
|
||||
struct sockaddr_in* local_sin = reinterpret_cast<struct sockaddr_in*>(&local_ss);
|
||||
memset(local_sin, 0, sizeof(*local_sin));
|
||||
@@ -568,14 +543,17 @@ void ProxyServer::LinkedSession::connect() {
|
||||
|
||||
string netloc_str = render_sockaddr_storage(local_ss);
|
||||
this->log(INFO, "Connecting to %s", netloc_str.c_str());
|
||||
if (bufferevent_socket_connect(this->server_bev.get(),
|
||||
|
||||
this->server_channel.set_bufferevent(bufferevent_socket_new(
|
||||
this->server->base.get(), -1, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS));
|
||||
if (bufferevent_socket_connect(this->server_channel.bev.get(),
|
||||
reinterpret_cast<const sockaddr*>(local_sin), sizeof(*local_sin)) != 0) {
|
||||
throw runtime_error(string_printf("failed to connect (%d)", EVUTIL_SOCKET_ERROR()));
|
||||
}
|
||||
bufferevent_setcb(this->server_bev.get(),
|
||||
&ProxyServer::LinkedSession::dispatch_on_server_input, nullptr,
|
||||
&ProxyServer::LinkedSession::dispatch_on_server_error, this);
|
||||
bufferevent_enable(this->server_bev.get(), EV_READ | EV_WRITE);
|
||||
|
||||
this->server_channel.on_command_received = ProxyServer::LinkedSession::on_input;
|
||||
this->server_channel.on_error = ProxyServer::LinkedSession::on_error;
|
||||
this->server_channel.context_obj = this;
|
||||
|
||||
// Cancel the session delete timeout
|
||||
event_del(this->timeout_event.get());
|
||||
@@ -594,26 +572,6 @@ ProxyServer::LinkedSession::SavingFile::SavingFile(
|
||||
|
||||
|
||||
|
||||
void ProxyServer::LinkedSession::dispatch_on_client_input(
|
||||
struct bufferevent*, void* ctx) {
|
||||
reinterpret_cast<LinkedSession*>(ctx)->on_client_input();
|
||||
}
|
||||
|
||||
void ProxyServer::LinkedSession::dispatch_on_client_error(
|
||||
struct bufferevent*, short events, void* ctx) {
|
||||
reinterpret_cast<LinkedSession*>(ctx)->on_stream_error(events, false);
|
||||
}
|
||||
|
||||
void ProxyServer::LinkedSession::dispatch_on_server_input(
|
||||
struct bufferevent*, void* ctx) {
|
||||
reinterpret_cast<LinkedSession*>(ctx)->on_server_input();
|
||||
}
|
||||
|
||||
void ProxyServer::LinkedSession::dispatch_on_server_error(
|
||||
struct bufferevent*, short events, void* ctx) {
|
||||
reinterpret_cast<LinkedSession*>(ctx)->on_stream_error(events, true);
|
||||
}
|
||||
|
||||
void ProxyServer::LinkedSession::dispatch_on_timeout(
|
||||
evutil_socket_t, short, void* ctx) {
|
||||
reinterpret_cast<LinkedSession*>(ctx)->on_timeout();
|
||||
@@ -628,31 +586,27 @@ void ProxyServer::LinkedSession::on_timeout() {
|
||||
|
||||
|
||||
|
||||
void ProxyServer::LinkedSession::on_stream_error(
|
||||
short events, bool is_server_stream) {
|
||||
void ProxyServer::LinkedSession::on_error(Channel& ch, short events) {
|
||||
auto* session = reinterpret_cast<LinkedSession*>(ch.context_obj);
|
||||
bool is_server_stream = (&ch == &session->server_channel);
|
||||
|
||||
if (events & BEV_EVENT_ERROR) {
|
||||
int err = EVUTIL_SOCKET_ERROR();
|
||||
this->log(WARNING, "Error %d (%s) in %s stream",
|
||||
session->log(WARNING, "Error %d (%s) in %s stream",
|
||||
err, evutil_socket_error_to_string(err),
|
||||
is_server_stream ? "server" : "client");
|
||||
}
|
||||
if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
|
||||
this->log(INFO, "%s has disconnected",
|
||||
session->log(INFO, "%s has disconnected",
|
||||
is_server_stream ? "Server" : "Client");
|
||||
this->disconnect();
|
||||
session->disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
void ProxyServer::LinkedSession::disconnect() {
|
||||
// Forward the disconnection to the other end
|
||||
this->server_bev.reset();
|
||||
this->client_bev.reset();
|
||||
|
||||
// Disable encryption for the next connection
|
||||
this->server_input_crypt.reset();
|
||||
this->server_output_crypt.reset();
|
||||
this->client_input_crypt.reset();
|
||||
this->client_output_crypt.reset();
|
||||
this->client_channel.disconnect();
|
||||
this->server_channel.disconnect();
|
||||
|
||||
// Set a timeout to delete the session entirely (in case the client doesn't
|
||||
// reconnect)
|
||||
@@ -662,105 +616,34 @@ void ProxyServer::LinkedSession::disconnect() {
|
||||
}
|
||||
|
||||
bool ProxyServer::LinkedSession::is_connected() const {
|
||||
return (this->server_bev.get() && this->client_bev.get());
|
||||
return (this->server_channel.connected() && this->client_channel.connected());
|
||||
}
|
||||
|
||||
|
||||
|
||||
void ProxyServer::LinkedSession::on_client_input() {
|
||||
void ProxyServer::LinkedSession::on_input(Channel& ch, uint16_t command, uint32_t flag, std::string& data) {
|
||||
auto* session = reinterpret_cast<LinkedSession*>(ch.context_obj);
|
||||
bool is_server_stream = (&ch == &session->server_channel);
|
||||
|
||||
try {
|
||||
for_each_received_command(this->client_bev.get(), this->version, this->client_input_crypt.get(),
|
||||
[&](uint16_t command, uint32_t flag, string& data) {
|
||||
print_received_command(command, flag, data.data(), data.size(),
|
||||
this->version, this->client_name.c_str());
|
||||
process_proxy_command(
|
||||
this->server->state,
|
||||
*this,
|
||||
false, // from_server
|
||||
command,
|
||||
flag,
|
||||
data);
|
||||
});
|
||||
if (is_server_stream) {
|
||||
size_t bytes_to_save = min<size_t>(data.size(), sizeof(session->prev_server_command_bytes));
|
||||
memcpy(session->prev_server_command_bytes, data.data(), bytes_to_save);
|
||||
}
|
||||
process_proxy_command(
|
||||
session->server->state,
|
||||
*session,
|
||||
is_server_stream,
|
||||
command,
|
||||
flag,
|
||||
data);
|
||||
} catch (const exception& e) {
|
||||
this->log(ERROR, "Failed to process command from client: %s", e.what());
|
||||
this->disconnect();
|
||||
session->log(ERROR, "Failed to process command from %s: %s",
|
||||
is_server_stream ? "server" : "client", e.what());
|
||||
session->disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
void ProxyServer::LinkedSession::on_server_input() {
|
||||
try {
|
||||
for_each_received_command(this->server_bev.get(), this->version, this->server_input_crypt.get(),
|
||||
[&](uint16_t command, uint32_t flag, string& data) {
|
||||
print_received_command(command, flag, data.data(), data.size(),
|
||||
this->version, this->server_name.c_str(), TerminalFormat::FG_RED);
|
||||
size_t bytes_to_save = min<size_t>(data.size(), sizeof(this->prev_server_command_bytes));
|
||||
memcpy(this->prev_server_command_bytes, data.data(), bytes_to_save);
|
||||
process_proxy_command(
|
||||
this->server->state,
|
||||
*this,
|
||||
true, // from_server
|
||||
command,
|
||||
flag,
|
||||
data);
|
||||
});
|
||||
} catch (const exception& e) {
|
||||
this->log(ERROR, "Failed to process command from server: %s", e.what());
|
||||
this->disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
void ProxyServer::LinkedSession::send_to_end(
|
||||
bool to_server,
|
||||
uint16_t command,
|
||||
uint32_t flag,
|
||||
const void* data,
|
||||
size_t size) {
|
||||
if (size & 3) {
|
||||
throw runtime_error("command size is not a multiple of 4");
|
||||
}
|
||||
string name = string_printf("LinkedSession:%08" PRIX64 ":synthetic:%s",
|
||||
this->id, to_server ? "server" : "client");
|
||||
|
||||
auto* bev = to_server ? this->server_bev.get() : this->client_bev.get();
|
||||
if (!bev) {
|
||||
throw runtime_error("session endpoint is not connected");
|
||||
}
|
||||
send_command(
|
||||
bev,
|
||||
this->version,
|
||||
to_server ? this->server_output_crypt.get() : this->client_output_crypt.get(),
|
||||
command,
|
||||
flag,
|
||||
data,
|
||||
size,
|
||||
name.c_str());
|
||||
}
|
||||
|
||||
void ProxyServer::LinkedSession::send_to_end(
|
||||
bool to_server, uint16_t command, uint32_t flag, const string& data) {
|
||||
this->send_to_end(to_server, command, flag, data.data(), data.size());
|
||||
}
|
||||
|
||||
void ProxyServer::LinkedSession::send_to_end_with_header(
|
||||
bool to_server, const void* data, size_t size) {
|
||||
size_t header_size = PSOCommandHeader::header_size(this->version);
|
||||
if (size < header_size) {
|
||||
throw runtime_error("command is too small for header");
|
||||
}
|
||||
const auto* header = reinterpret_cast<const PSOCommandHeader*>(data);
|
||||
this->send_to_end(
|
||||
to_server,
|
||||
header->command(this->version),
|
||||
header->flag(this->version),
|
||||
reinterpret_cast<const uint8_t*>(data) + header_size,
|
||||
size - header_size);
|
||||
}
|
||||
|
||||
void ProxyServer::LinkedSession::send_to_end_with_header(
|
||||
bool to_server, const string& data) {
|
||||
this->send_to_end_with_header(to_server, data.data(), data.size());
|
||||
}
|
||||
|
||||
shared_ptr<ProxyServer::LinkedSession> ProxyServer::get_session() {
|
||||
if (this->id_to_session.empty()) {
|
||||
throw runtime_error("no sessions exist");
|
||||
|
||||
Reference in New Issue
Block a user