#include "Server.hh" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Loggers.hh" #include "PSOProtocol.hh" #include "ReceiveCommands.hh" using namespace std; using namespace std::placeholders; void Server::disconnect_client(shared_ptr c) { if (c->channel.is_virtual_connection) { server_log.info( "Client disconnected: C-%" PRIX64 " on virtual connection %p", c->id, c->channel.bev.get()); } else { server_log.info( "Client disconnected: C-%" PRIX64 " on fd %d", c->id, bufferevent_getfd(c->channel.bev.get())); } this->channel_to_client.erase(&c->channel); c->channel.disconnect(); try { on_disconnect(this->state, c); } catch (const exception& e) { server_log.warning("Error during client disconnect cleanup: %s", e.what()); } // We can't just let c be destroyed here, since disconnect_client can be // called from within the client's channel's receive handler. So, we instead // move it to another set, which we'll clear in an immediately-enqueued // callback after the current event. This will also call the client's // disconnect hooks (if any). this->clients_to_destroy.insert(std::move(c)); this->enqueue_destroy_clients(); } void Server::enqueue_destroy_clients() { auto tv = usecs_to_timeval(0); event_add(this->destroy_clients_ev.get(), &tv); } void Server::dispatch_destroy_clients(evutil_socket_t, short, void* ctx) { reinterpret_cast(ctx)->destroy_clients(); } void Server::destroy_clients() { for (auto c_it = this->clients_to_destroy.begin(); c_it != this->clients_to_destroy.end(); c_it = this->clients_to_destroy.erase(c_it)) { auto c = *c_it; // Note: It's important to move the disconnect hooks out of the client here // because the hooks could modify c->disconnect_hooks while it's being // iterated here, which would invalidate these iterators. unordered_map> hooks = std::move(c->disconnect_hooks); for (auto h_it : hooks) { try { h_it.second(); } catch (const exception& e) { c->log.warning("Disconnect hook %s failed: %s", h_it.first.c_str(), e.what()); } } } } void Server::dispatch_on_listen_accept( struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr* address, int socklen, void* ctx) { reinterpret_cast(ctx)->on_listen_accept(listener, fd, address, socklen); } void Server::dispatch_on_listen_error(struct evconnlistener* listener, void* ctx) { reinterpret_cast(ctx)->on_listen_error(listener); } void Server::on_listen_accept(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr*, int) { int listen_fd = evconnlistener_get_fd(listener); ListeningSocket* listening_socket; try { listening_socket = &this->listening_sockets.at(listen_fd); } catch (const out_of_range& e) { server_log.warning("Can\'t determine version for socket %d; disconnecting client", listen_fd); close(fd); return; } struct bufferevent* bev = bufferevent_socket_new(this->base.get(), fd, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS); shared_ptr c(new Client( bev, listening_socket->version, listening_socket->behavior)); c->game_data.should_save = this->state->allow_saving; c->channel.on_command_received = Server::on_client_input; c->channel.on_error = Server::on_client_error; c->channel.context_obj = this; this->channel_to_client.emplace(&c->channel, c); server_log.info("Client connected: C-%" PRIX64 " on fd %d via %d (%s)", c->id, fd, listen_fd, listening_socket->addr_str.c_str()); try { on_connect(this->state, c); } catch (const exception& e) { server_log.warning("Error during client initialization: %s", e.what()); this->disconnect_client(c); } } void Server::connect_client( struct bufferevent* bev, uint32_t address, uint16_t client_port, uint16_t server_port, GameVersion version, ServerBehavior initial_state) { shared_ptr c(new Client(bev, version, initial_state)); c->game_data.should_save = this->state->allow_saving; c->channel.on_command_received = Server::on_client_input; c->channel.on_error = Server::on_client_error; c->channel.context_obj = this; server_log.info( "Client connected: C-%" PRIX64 " on virtual connection %p via T-%hu-%s-%s-VI", c->id, bev, server_port, name_for_version(version), name_for_server_behavior(initial_state)); this->channel_to_client.emplace(&c->channel, c); // Manually set the remote address, since the bufferevent has no fd and the // Channel constructor can't figure out the virtual remote address auto* remote_sin = reinterpret_cast(&c->channel.remote_addr); remote_sin->sin_family = AF_INET; remote_sin->sin_addr.s_addr = htonl(address); remote_sin->sin_port = htons(client_port); try { on_connect(this->state, c); } catch (const exception& e) { server_log.error("Error during client initialization: %s", e.what()); this->disconnect_client(c); } } void Server::on_listen_error(struct evconnlistener* listener) { int err = EVUTIL_SOCKET_ERROR(); server_log.error("Failure on listening socket %d: %d (%s)", evconnlistener_get_fd(listener), err, evutil_socket_error_to_string(err)); event_base_loopexit(this->base.get(), nullptr); } void Server::on_client_input(Channel& ch, uint16_t command, uint32_t flag, std::string& data) { Server* server = reinterpret_cast(ch.context_obj); shared_ptr c = server->channel_to_client.at(&ch); if (c->should_disconnect) { server->disconnect_client(c); } else { if (server->state->catch_handler_exceptions) { try { on_command(server->state, c, command, flag, data); } catch (const exception& e) { server_log.warning("Error processing client command: %s", e.what()); c->should_disconnect = true; } } else { on_command(server->state, c, command, flag, data); } if (c->should_disconnect) { server->disconnect_client(c); } } } void Server::on_client_error(Channel& ch, short events) { Server* server = reinterpret_cast(ch.context_obj); shared_ptr c = server->channel_to_client.at(&ch); if (events & BEV_EVENT_ERROR) { int err = EVUTIL_SOCKET_ERROR(); server_log.warning("Client caused error %d (%s)", err, evutil_socket_error_to_string(err)); } if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) { server->disconnect_client(c); } } Server::Server( shared_ptr base, shared_ptr state) : base(base), destroy_clients_ev(event_new(this->base.get(), -1, EV_TIMEOUT, &Server::dispatch_destroy_clients, this), event_free), state(state) {} void Server::listen( const std::string& addr_str, const string& socket_path, GameVersion version, ServerBehavior behavior) { int fd = ::listen(socket_path, 0, SOMAXCONN); server_log.info("Listening on Unix socket %s on fd %d as %s", socket_path.c_str(), fd, addr_str.c_str()); this->add_socket(addr_str, fd, version, behavior); } void Server::listen( const std::string& addr_str, const string& addr, int port, GameVersion version, ServerBehavior behavior) { int fd = ::listen(addr, port, SOMAXCONN); string netloc_str = render_netloc(addr, port); server_log.info("Listening on TCP interface %s on fd %d as %s", netloc_str.c_str(), fd, addr_str.c_str()); this->add_socket(addr_str, fd, version, behavior); } void Server::listen(const std::string& addr_str, int port, GameVersion version, ServerBehavior behavior) { this->listen(addr_str, "", port, version, behavior); } Server::ListeningSocket::ListeningSocket( Server* s, const std::string& addr_str, int fd, GameVersion version, ServerBehavior behavior) : addr_str(addr_str), fd(fd), version(version), behavior(behavior), listener(evconnlistener_new( s->base.get(), Server::dispatch_on_listen_accept, s, LEV_OPT_REUSEABLE, 0, this->fd), evconnlistener_free) { evconnlistener_set_error_cb( this->listener.get(), Server::dispatch_on_listen_error); } void Server::add_socket( const std::string& addr_str, int fd, GameVersion version, ServerBehavior behavior) { this->listening_sockets.emplace( piecewise_construct, forward_as_tuple(fd), forward_as_tuple(this, addr_str, fd, version, behavior)); } shared_ptr Server::get_client() const { if (this->channel_to_client.empty()) { throw runtime_error("no clients on game server"); } if (this->channel_to_client.size() > 1) { throw runtime_error("multiple clients on game server"); } return this->channel_to_client.begin()->second; } vector> Server::get_clients_by_identifier(const string& ident) const { int64_t serial_number_hex = -1; int64_t serial_number_dec = -1; try { serial_number_dec = stoul(ident, nullptr, 10); } catch (const invalid_argument&) { } try { serial_number_hex = stoul(ident, nullptr, 16); } catch (const invalid_argument&) { } u16string u16name = decode_sjis(ident); // TODO: It's kind of not great that we do a linear search here, but this is // only used in the shell, so it should be pretty rare. vector> results; for (const auto& it : this->channel_to_client) { auto c = it.second; if (c->license && c->license->serial_number == serial_number_dec) { results.emplace_back(std::move(c)); continue; } if (c->license && c->license->serial_number == serial_number_hex) { results.emplace_back(std::move(c)); continue; } if (c->license && c->license->username == ident) { results.emplace_back(std::move(c)); continue; } auto p = c->game_data.player(false); if (p && p->disp.name == u16name) { results.emplace_back(std::move(c)); continue; } if (c->channel.name == ident) { results.emplace_back(std::move(c)); continue; } if (starts_with(c->channel.name, ident + " ")) { results.emplace_back(std::move(c)); continue; } } return results; } shared_ptr Server::get_base() const { return this->base; }