This commit is contained in:
rubidium42 2024-04-23 19:34:23 +02:00 committed by GitHub
commit 4a36f8b962
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 213 additions and 22 deletions

View File

@ -1,6 +1,6 @@
# OpenTTD's admin network
Last updated: 2011-01-20
Last updated: 2024-03-26
## Table of contents
@ -49,10 +49,29 @@ Last updated: 2011-01-20
Create a TCP connection to the server on port 3977. The application is
expected to authenticate within 10 seconds.
To authenticate send a `ADMIN_PACKET_ADMIN_JOIN` packet.
To authenticate send either an `ADMIN_PACKET_ADMIN_JOIN` or an
`ADMIN_PACKET_ADMIN_JOIN_SECURE` packet.
The server will reply with `ADMIN_PACKET_SERVER_PROTOCOL` followed directly by
`ADMIN_PACKET_SERVER_WELCOME`.
The `ADMIN_PACKET_ADMIN_JOIN` packet sends the password without any
encryption or safeguards over the connection, and as such has been disabled
by default.
The `ADMIN_PACKET_ADMIN_JOIN_SECURE` packet initiates a key exchange
authentication schema which tells te server which methods the client
supports and the server makes a choice. The server will then send an
`ADMIN_PACKET_SERVER_AUTH_REQUEST` packet to which the client has to respond
with an `ADMIN_PACKET_ADMIN_AUTH_RESPONSE` packet.
The current choices for secure authentication are authorized keys, where
the client has a private key and the server a list of authorized public
keys, and a so-called password-authenticated key exchange which allows to
authenticate using a password without actually sending the password.
The server falls back to password authentication when the client's key is
not in the list of authorized keys.
When authentication has succeeded for either of the `JOIN` schemas, the
server will reply with `ADMIN_PACKET_SERVER_PROTOCOL` followed directly
by `ADMIN_PACKET_SERVER_WELCOME`.
`ADMIN_PACKET_SERVER_PROTOCOL` contains details about the protocol version.
It is the job of your application to check this number and decide whether

View File

@ -1964,6 +1964,7 @@ DEF_CONSOLE_CMD(ConCompanyPassword)
/** All the known authorized keys with their name. */
static std::vector<std::pair<std::string_view, NetworkAuthorizedKeys *>> _console_cmd_authorized_keys{
{ "admin", &_settings_client.network.admin_authorized_keys },
{ "rcon", &_settings_client.network.rcon_authorized_keys },
{ "server", &_settings_client.network.server_authorized_keys },
};

View File

@ -2590,7 +2590,7 @@ STR_NETWORK_ERROR_INVALID_CLIENT_NAME :{WHITE}Your pla
STR_NETWORK_ERROR_CLIENT_GUI_LOST_CONNECTION_CAPTION :{WHITE}Possible connection loss
STR_NETWORK_ERROR_CLIENT_GUI_LOST_CONNECTION :{WHITE}The last {NUM} second{P "" s} no data has arrived from the server
###length 22
###length 23
STR_NETWORK_ERROR_CLIENT_GENERAL :general error
STR_NETWORK_ERROR_CLIENT_DESYNC :desync error
STR_NETWORK_ERROR_CLIENT_SAVEGAME :could not load map
@ -2603,6 +2603,7 @@ STR_NETWORK_ERROR_CLIENT_WRONG_REVISION :wrong revision
STR_NETWORK_ERROR_CLIENT_NAME_IN_USE :name already in use
STR_NETWORK_ERROR_CLIENT_WRONG_PASSWORD :wrong password
STR_NETWORK_ERROR_CLIENT_NOT_ON_ALLOW_LIST :not on allow list
STR_NETWORK_ERROR_CLIENT_NO_AUTHENTICATION_METHOD_AVAILABLE :none of the requested authentication methods is available
STR_NETWORK_ERROR_CLIENT_COMPANY_MISMATCH :wrong company in DoCommand
STR_NETWORK_ERROR_CLIENT_KICKED :kicked by server
STR_NETWORK_ERROR_CLIENT_CHEATER :was trying to use a cheat

View File

@ -63,6 +63,8 @@ NetworkRecvStatus NetworkAdminSocketHandler::HandlePacket(Packet &p)
case ADMIN_PACKET_ADMIN_RCON: return this->Receive_ADMIN_RCON(p);
case ADMIN_PACKET_ADMIN_GAMESCRIPT: return this->Receive_ADMIN_GAMESCRIPT(p);
case ADMIN_PACKET_ADMIN_PING: return this->Receive_ADMIN_PING(p);
case ADMIN_PACKET_ADMIN_JOIN_SECURE: return this->Receive_ADMIN_JOIN_SECURE(p);
case ADMIN_PACKET_ADMIN_AUTH_RESPONSE: return this->Receive_ADMIN_AUTH_RESPONSE(p);
case ADMIN_PACKET_SERVER_FULL: return this->Receive_SERVER_FULL(p);
case ADMIN_PACKET_SERVER_BANNED: return this->Receive_SERVER_BANNED(p);
@ -91,6 +93,8 @@ NetworkRecvStatus NetworkAdminSocketHandler::HandlePacket(Packet &p)
case ADMIN_PACKET_SERVER_CMD_LOGGING: return this->Receive_SERVER_CMD_LOGGING(p);
case ADMIN_PACKET_SERVER_RCON_END: return this->Receive_SERVER_RCON_END(p);
case ADMIN_PACKET_SERVER_PONG: return this->Receive_SERVER_PONG(p);
case ADMIN_PACKET_SERVER_AUTH_REQUEST: return this->Receive_SERVER_AUTH_REQUEST(p);
case ADMIN_PACKET_SERVER_ENABLE_ENCRYPTION: return this->Receive_SERVER_ENABLE_ENCRYPTION(p);
default:
Debug(net, 0, "[tcp/admin] Received invalid packet type {} from '{}' ({})", type, this->admin_name, this->admin_version);
@ -137,6 +141,8 @@ NetworkRecvStatus NetworkAdminSocketHandler::Receive_ADMIN_EXTERNAL_CHAT(Packet
NetworkRecvStatus NetworkAdminSocketHandler::Receive_ADMIN_RCON(Packet &) { return this->ReceiveInvalidPacket(ADMIN_PACKET_ADMIN_RCON); }
NetworkRecvStatus NetworkAdminSocketHandler::Receive_ADMIN_GAMESCRIPT(Packet &) { return this->ReceiveInvalidPacket(ADMIN_PACKET_ADMIN_GAMESCRIPT); }
NetworkRecvStatus NetworkAdminSocketHandler::Receive_ADMIN_PING(Packet &) { return this->ReceiveInvalidPacket(ADMIN_PACKET_ADMIN_PING); }
NetworkRecvStatus NetworkAdminSocketHandler::Receive_ADMIN_JOIN_SECURE(Packet &) { return this->ReceiveInvalidPacket(ADMIN_PACKET_ADMIN_JOIN_SECURE); }
NetworkRecvStatus NetworkAdminSocketHandler::Receive_ADMIN_AUTH_RESPONSE(Packet &) { return this->ReceiveInvalidPacket(ADMIN_PACKET_ADMIN_AUTH_RESPONSE); }
NetworkRecvStatus NetworkAdminSocketHandler::Receive_SERVER_FULL(Packet &) { return this->ReceiveInvalidPacket(ADMIN_PACKET_SERVER_FULL); }
NetworkRecvStatus NetworkAdminSocketHandler::Receive_SERVER_BANNED(Packet &) { return this->ReceiveInvalidPacket(ADMIN_PACKET_SERVER_BANNED); }
@ -165,3 +171,5 @@ NetworkRecvStatus NetworkAdminSocketHandler::Receive_SERVER_CMD_NAMES(Packet &)
NetworkRecvStatus NetworkAdminSocketHandler::Receive_SERVER_CMD_LOGGING(Packet &) { return this->ReceiveInvalidPacket(ADMIN_PACKET_SERVER_CMD_LOGGING); }
NetworkRecvStatus NetworkAdminSocketHandler::Receive_SERVER_RCON_END(Packet &) { return this->ReceiveInvalidPacket(ADMIN_PACKET_SERVER_RCON_END); }
NetworkRecvStatus NetworkAdminSocketHandler::Receive_SERVER_PONG(Packet &) { return this->ReceiveInvalidPacket(ADMIN_PACKET_SERVER_PONG); }
NetworkRecvStatus NetworkAdminSocketHandler::Receive_SERVER_AUTH_REQUEST(Packet &) { return this->ReceiveInvalidPacket(ADMIN_PACKET_SERVER_AUTH_REQUEST); }
NetworkRecvStatus NetworkAdminSocketHandler::Receive_SERVER_ENABLE_ENCRYPTION(Packet &) { return this->ReceiveInvalidPacket(ADMIN_PACKET_SERVER_ENABLE_ENCRYPTION); }

View File

@ -22,7 +22,7 @@
* This protocol may only be extended to ensure stability.
*/
enum PacketAdminType : uint8_t {
ADMIN_PACKET_ADMIN_JOIN, ///< The admin announces and authenticates itself to the server.
ADMIN_PACKET_ADMIN_JOIN, ///< The admin announces and authenticates itself to the server using an unsecured passwords.
ADMIN_PACKET_ADMIN_QUIT, ///< The admin tells the server that it is quitting.
ADMIN_PACKET_ADMIN_UPDATE_FREQUENCY, ///< The admin tells the server the update frequency of a particular piece of information.
ADMIN_PACKET_ADMIN_POLL, ///< The admin explicitly polls for a piece of information.
@ -31,6 +31,8 @@ enum PacketAdminType : uint8_t {
ADMIN_PACKET_ADMIN_GAMESCRIPT, ///< The admin sends a JSON string for the GameScript.
ADMIN_PACKET_ADMIN_PING, ///< The admin sends a ping to the server, expecting a ping-reply (PONG) packet.
ADMIN_PACKET_ADMIN_EXTERNAL_CHAT, ///< The admin sends a chat message from external source.
ADMIN_PACKET_ADMIN_JOIN_SECURE, ///< The admin announces and starts a secure authentication handshake.
ADMIN_PACKET_ADMIN_AUTH_RESPONSE, ///< The admin responds to the authentication request.
ADMIN_PACKET_SERVER_FULL = 100, ///< The server tells the admin it cannot accept the admin.
ADMIN_PACKET_SERVER_BANNED, ///< The server tells the admin it is banned.
@ -61,6 +63,8 @@ enum PacketAdminType : uint8_t {
ADMIN_PACKET_SERVER_RCON_END, ///< The server indicates that the remote console command has completed.
ADMIN_PACKET_SERVER_PONG, ///< The server replies to a ping request from the admin.
ADMIN_PACKET_SERVER_CMD_LOGGING, ///< The server gives the admin copies of incoming command packets.
ADMIN_PACKET_SERVER_AUTH_REQUEST, ///< The server gives the admin the used authentication method and required parameters.
ADMIN_PACKET_SERVER_ENABLE_ENCRYPTION, ///< The server tells that authentication has completed and requests to enable encryption with the keys of the last \c ADMIN_PACKET_ADMIN_AUTH_RESPONSE.
INVALID_ADMIN_PACKET = 0xFF, ///< An invalid marker for admin packets.
};
@ -68,6 +72,7 @@ enum PacketAdminType : uint8_t {
/** Status of an admin. */
enum AdminStatus {
ADMIN_STATUS_INACTIVE, ///< The admin is not connected nor active.
ADMIN_STATUS_AUTHENTICATE, ///< The admin is connected and working on authentication.
ADMIN_STATUS_ACTIVE, ///< The admin is active.
ADMIN_STATUS_END, ///< Must ALWAYS be on the end of this list!! (period)
};
@ -118,8 +123,8 @@ protected:
NetworkRecvStatus ReceiveInvalidPacket(PacketAdminType type);
/**
* Join the admin network:
* string Password the server is expecting for this network.
* Join the admin network using an unsecured password exchange:
* string Unsecured password the server is expecting for this network.
* string Name of the application being used to connect.
* string Version string of the application being used to connect.
* @param p The packet that was just received.
@ -200,6 +205,32 @@ protected:
*/
virtual NetworkRecvStatus Receive_ADMIN_PING(Packet &p);
/**
* Join the admin network using a secure authentication method:
* string Name of the application being used to connect.
* string Version string of the application being used to connect.
* uint16_t Bitmask of supported authentication methods. See \c NetworkAuthenticationMethod for the supported methods.
*
* The server will determine which of the authentication methods supplied by the client will be used.
* When there is no supported authentication method, an \c ADMIN_PACKET_SERVER_ERROR packet will be
* sent with \c NETWORK_ERROR_NO_AUTHENTICATION_METHOD_AVAILABLE as error.
* @param p The packet that was just received.
* @return The state the network should have.
*/
virtual NetworkRecvStatus Receive_ADMIN_JOIN_SECURE(Packet &p);
/**
* Admin responds to \c ADMIN_PACKET_SERVER_AUTH_REQUEST with the appropriate
* data given the agreed upon \c NetworkAuthenticationMethod.
* With \c NETWORK_AUTH_METHOD_X25519_PAKE and \c NETWORK_AUTH_METHOD_X25519_AUTHORIZED_KEY:
* 32 * uint8_t Public key of the client.
* 16 * uint8_t Message authentication code (mac).
* 8 * uint8_t Encrypted message of the authentication (just random bytes).
* @param p The packet that was just received.
* @return The state the network should have.
*/
virtual NetworkRecvStatus Receive_ADMIN_AUTH_RESPONSE(Packet &p);
/**
* The server is full (connection gets closed).
* @param p The packet that was just received.
@ -472,6 +503,25 @@ protected:
*/
virtual NetworkRecvStatus Receive_SERVER_CMD_LOGGING(Packet &p);
/**
* Server requests authentication challenge from the admin.
* uint8_t The chosen authentication method from \c NetworkAuthenticationMethod.
* With \c NETWORK_AUTH_METHOD_X25519_PAKE and \c NETWORK_AUTH_METHOD_X25519_AUTHORIZED_KEY:
* 32 * uint8_t Public key of the server.
* 24 * uint8_t Nonce to use for the encryption.
* @param p The packet that was just received.
* @return The state the network should have.
*/
virtual NetworkRecvStatus Receive_SERVER_AUTH_REQUEST(Packet &p);
/**
* Indication to the client that authentication is complete and encryption has to be used from here on forward.
* The encryption uses the shared keys generated by the last AUTH_REQUEST key exchange.
* 24 * uint8_t Nonce for encrypted connection.
* @param p The packet that was just received.
*/
virtual NetworkRecvStatus Receive_SERVER_ENABLE_ENCRYPTION(Packet &p);
/**
* Send a ping-reply (pong) to the admin that sent us the ping packet.
* uint32_t Integer identifier - should be the same as read from the admins ping packet.

View File

@ -379,6 +379,7 @@ StringID GetNetworkErrorMsg(NetworkErrorCode err)
STR_NETWORK_ERROR_CLIENT_TIMEOUT_JOIN,
STR_NETWORK_ERROR_CLIENT_INVALID_CLIENT_NAME,
STR_NETWORK_ERROR_CLIENT_NOT_ON_ALLOW_LIST,
STR_NETWORK_ERROR_CLIENT_NO_AUTHENTICATION_METHOD_AVAILABLE,
};
static_assert(lengthof(network_error_strings) == NETWORK_ERROR_END);
@ -948,8 +949,8 @@ bool NetworkServerStart()
Debug(net, 5, "Starting listeners for clients");
if (!ServerNetworkGameSocketHandler::Listen(_settings_client.network.server_port)) return false;
/* Only listen for admins when the password isn't empty. */
if (!_settings_client.network.admin_password.empty()) {
/* Only listen for admins when the authentication is configured. */
if (_settings_client.network.AdminAuthenticationConfigured()) {
Debug(net, 5, "Starting listeners for admins");
if (!ServerNetworkAdminSocketHandler::Listen(_settings_client.network.server_admin_port)) return false;
}

View File

@ -38,6 +38,9 @@ uint8_t _network_admins_connected = 0;
NetworkAdminSocketPool _networkadminsocket_pool("NetworkAdminSocket");
INSTANTIATE_POOL_METHODS(NetworkAdminSocket)
static NetworkAuthenticationDefaultPasswordProvider _admin_password_provider(_settings_client.network.admin_password); ///< Provides the password validation for the game's password.
static NetworkAuthenticationDefaultAuthorizedKeyHandler _admin_authorized_key_handler(_settings_client.network.admin_authorized_keys); ///< Provides the authorized key handling for the game authentication.
/** The timeout for authorisation of the client. */
static const std::chrono::seconds ADMIN_AUTHORISATION_TIMEOUT(10);
@ -90,7 +93,7 @@ ServerNetworkAdminSocketHandler::~ServerNetworkAdminSocketHandler()
*/
/* static */ bool ServerNetworkAdminSocketHandler::AllowConnection()
{
bool accept = !_settings_client.network.admin_password.empty() && _network_admins_connected < MAX_ADMINS;
bool accept = _settings_client.network.AdminAuthenticationConfigured() && _network_admins_connected < MAX_ADMINS;
/* We can't go over the MAX_ADMINS limit here. However, if we accept
* the connection, there has to be space in the pool. */
static_assert(NetworkAdminSocketPool::MAX_SIZE == MAX_ADMINS);
@ -102,7 +105,7 @@ ServerNetworkAdminSocketHandler::~ServerNetworkAdminSocketHandler()
/* static */ void ServerNetworkAdminSocketHandler::Send()
{
for (ServerNetworkAdminSocketHandler *as : ServerNetworkAdminSocketHandler::Iterate()) {
if (as->status == ADMIN_STATUS_INACTIVE && std::chrono::steady_clock::now() > as->connect_time + ADMIN_AUTHORISATION_TIMEOUT) {
if (as->status <= ADMIN_STATUS_AUTHENTICATE && std::chrono::steady_clock::now() > as->connect_time + ADMIN_AUTHORISATION_TIMEOUT) {
Debug(net, 2, "[admin] Admin did not send its authorisation within {} seconds", std::chrono::duration_cast<std::chrono::seconds>(ADMIN_AUTHORISATION_TIMEOUT).count());
as->CloseConnection(true);
continue;
@ -134,6 +137,9 @@ ServerNetworkAdminSocketHandler::~ServerNetworkAdminSocketHandler()
*/
NetworkRecvStatus ServerNetworkAdminSocketHandler::SendError(NetworkErrorCode error)
{
/* Whatever the error might be, authentication (keys) must be released as soon as possible. */
this->authentication_handler = nullptr;
auto p = std::make_unique<Packet>(this, ADMIN_PACKET_SERVER_ERROR);
p->Send_uint8(error);
@ -149,6 +155,8 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendError(NetworkErrorCode er
/** Send the protocol version to the admin. */
NetworkRecvStatus ServerNetworkAdminSocketHandler::SendProtocol()
{
this->status = ADMIN_STATUS_ACTIVE;
auto p = std::make_unique<Packet>(this, ADMIN_PACKET_SERVER_PROTOCOL);
/* announce the protocol version */
@ -491,7 +499,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendRcon(uint16_t colour, con
NetworkRecvStatus ServerNetworkAdminSocketHandler::Receive_ADMIN_RCON(Packet &p)
{
if (this->status == ADMIN_STATUS_INACTIVE) return this->SendError(NETWORK_ERROR_NOT_EXPECTED);
if (this->status <= ADMIN_STATUS_AUTHENTICATE) return this->SendError(NETWORK_ERROR_NOT_EXPECTED);
std::string command = p.Recv_string(NETWORK_RCONCOMMAND_LENGTH);
@ -505,7 +513,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::Receive_ADMIN_RCON(Packet &p)
NetworkRecvStatus ServerNetworkAdminSocketHandler::Receive_ADMIN_GAMESCRIPT(Packet &p)
{
if (this->status == ADMIN_STATUS_INACTIVE) return this->SendError(NETWORK_ERROR_NOT_EXPECTED);
if (this->status <= ADMIN_STATUS_AUTHENTICATE) return this->SendError(NETWORK_ERROR_NOT_EXPECTED);
std::string json = p.Recv_string(NETWORK_GAMESCRIPT_JSON_LENGTH);
@ -517,7 +525,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::Receive_ADMIN_GAMESCRIPT(Pack
NetworkRecvStatus ServerNetworkAdminSocketHandler::Receive_ADMIN_PING(Packet &p)
{
if (this->status == ADMIN_STATUS_INACTIVE) return this->SendError(NETWORK_ERROR_NOT_EXPECTED);
if (this->status <= ADMIN_STATUS_AUTHENTICATE) return this->SendError(NETWORK_ERROR_NOT_EXPECTED);
uint32_t d1 = p.Recv_uint32();
@ -631,6 +639,11 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::Receive_ADMIN_JOIN(Packet &p)
{
if (this->status != ADMIN_STATUS_INACTIVE) return this->SendError(NETWORK_ERROR_NOT_EXPECTED);
if (!_settings_client.network.allow_insecure_admin_login) {
/* You're not authorized to login using this method. */
return this->SendError(NETWORK_ERROR_NOT_AUTHORIZED);
}
std::string password = p.Recv_string(NETWORK_PASSWORD_LENGTH);
if (_settings_client.network.admin_password.empty() ||
@ -647,8 +660,6 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::Receive_ADMIN_JOIN(Packet &p)
return this->SendError(NETWORK_ERROR_ILLEGAL_PACKET);
}
this->status = ADMIN_STATUS_ACTIVE;
Debug(net, 3, "[admin] '{}' ({}) has connected", this->admin_name, this->admin_version);
return this->SendProtocol();
@ -662,7 +673,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::Receive_ADMIN_QUIT(Packet &)
NetworkRecvStatus ServerNetworkAdminSocketHandler::Receive_ADMIN_UPDATE_FREQUENCY(Packet &p)
{
if (this->status == ADMIN_STATUS_INACTIVE) return this->SendError(NETWORK_ERROR_NOT_EXPECTED);
if (this->status <= ADMIN_STATUS_AUTHENTICATE) return this->SendError(NETWORK_ERROR_NOT_EXPECTED);
AdminUpdateType type = (AdminUpdateType)p.Recv_uint16();
AdminUpdateFrequency freq = (AdminUpdateFrequency)p.Recv_uint16();
@ -682,7 +693,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::Receive_ADMIN_UPDATE_FREQUENC
NetworkRecvStatus ServerNetworkAdminSocketHandler::Receive_ADMIN_POLL(Packet &p)
{
if (this->status == ADMIN_STATUS_INACTIVE) return this->SendError(NETWORK_ERROR_NOT_EXPECTED);
if (this->status <= ADMIN_STATUS_AUTHENTICATE) return this->SendError(NETWORK_ERROR_NOT_EXPECTED);
AdminUpdateType type = (AdminUpdateType)p.Recv_uint8();
uint32_t d1 = p.Recv_uint32();
@ -748,7 +759,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::Receive_ADMIN_POLL(Packet &p)
NetworkRecvStatus ServerNetworkAdminSocketHandler::Receive_ADMIN_CHAT(Packet &p)
{
if (this->status == ADMIN_STATUS_INACTIVE) return this->SendError(NETWORK_ERROR_NOT_EXPECTED);
if (this->status <= ADMIN_STATUS_AUTHENTICATE) return this->SendError(NETWORK_ERROR_NOT_EXPECTED);
NetworkAction action = (NetworkAction)p.Recv_uint8();
DestType desttype = (DestType)p.Recv_uint8();
@ -774,7 +785,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::Receive_ADMIN_CHAT(Packet &p)
NetworkRecvStatus ServerNetworkAdminSocketHandler::Receive_ADMIN_EXTERNAL_CHAT(Packet &p)
{
if (this->status == ADMIN_STATUS_INACTIVE) return this->SendError(NETWORK_ERROR_NOT_EXPECTED);
if (this->status <= ADMIN_STATUS_AUTHENTICATE) return this->SendError(NETWORK_ERROR_NOT_EXPECTED);
std::string source = p.Recv_string(NETWORK_CHAT_LENGTH);
TextColour colour = (TextColour)p.Recv_uint16();
@ -791,6 +802,86 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::Receive_ADMIN_EXTERNAL_CHAT(P
return NETWORK_RECV_STATUS_OKAY;
}
/*
* Secure authentication send and receive methods.
*/
NetworkRecvStatus ServerNetworkAdminSocketHandler::Receive_ADMIN_JOIN_SECURE(Packet &p)
{
if (this->status != ADMIN_STATUS_INACTIVE) return this->SendError(NETWORK_ERROR_NOT_EXPECTED);
this->admin_name = p.Recv_string(NETWORK_CLIENT_NAME_LENGTH);
this->admin_version = p.Recv_string(NETWORK_REVISION_LENGTH);
NetworkAuthenticationMethodMask method_mask = p.Recv_uint16();
/* Always exclude key exchange only, as that provides no credential checking. */
ClrBit(method_mask, NETWORK_AUTH_METHOD_X25519_KEY_EXCHANGE_ONLY);
if (this->admin_name.empty() || this->admin_version.empty()) {
/* No name or version supplied. */
return this->SendError(NETWORK_ERROR_ILLEGAL_PACKET);
}
auto handler = NetworkAuthenticationServerHandler::Create(&_admin_password_provider, &_admin_authorized_key_handler, method_mask);
if (!handler->CanBeUsed()) return this->SendError(NETWORK_ERROR_NO_AUTHENTICATION_METHOD_AVAILABLE);
this->authentication_handler = std::move(handler);
Debug(net, 3, "[admin] '{}' ({}) has connected", this->admin_name, this->admin_version);
return this->SendAuthRequest();
}
NetworkRecvStatus ServerNetworkAdminSocketHandler::SendAuthRequest()
{
this->status = ADMIN_STATUS_AUTHENTICATE;
Debug(net, 6, "[admin] '{}' ({}) authenticating using {}", this->admin_name, this->admin_version, this->authentication_handler->GetName());
auto p = std::make_unique<Packet>(this, ADMIN_PACKET_SERVER_AUTH_REQUEST);
this->authentication_handler->SendRequest(*p);
this->SendPacket(std::move(p));
return NETWORK_RECV_STATUS_OKAY;
}
NetworkRecvStatus ServerNetworkAdminSocketHandler::SendEnableEncryption()
{
if (this->status != ADMIN_STATUS_AUTHENTICATE) return this->SendError(NETWORK_ERROR_NOT_EXPECTED);
auto p = std::make_unique<Packet>(this, ADMIN_PACKET_SERVER_ENABLE_ENCRYPTION);
this->authentication_handler->SendEnableEncryption(*p);
this->SendPacket(std::move(p));
return NETWORK_RECV_STATUS_OKAY;
}
NetworkRecvStatus ServerNetworkAdminSocketHandler::Receive_ADMIN_AUTH_RESPONSE(Packet &p)
{
if (this->status != ADMIN_STATUS_AUTHENTICATE) return this->SendError(NETWORK_ERROR_NOT_EXPECTED);
switch (this->authentication_handler->ReceiveResponse(p)) {
case NetworkAuthenticationServerHandler::AUTHENTICATED:
Debug(net, 3, "[admin] '{}' ({}) authenticated", this->admin_name, this->admin_version);
this->SendEnableEncryption();
this->receive_encryption_handler = this->authentication_handler->CreateClientToServerEncryptionHandler();
this->send_encryption_handler = this->authentication_handler->CreateServerToClientEncryptionHandler();
this->authentication_handler = nullptr;
return this->SendProtocol();
case NetworkAuthenticationServerHandler::RETRY_NEXT_METHOD:
Debug(net, 6, "[admin] '{}' ({}) authentication failed, trying next method", this->admin_name, this->admin_version);
return this->SendAuthRequest();
case NetworkAuthenticationServerHandler::NOT_AUTHENTICATED:
default:
Debug(net, 3, "[admin] '{}' ({}) authentication failed", this->admin_name, this->admin_version);
return this->SendError(NETWORK_ERROR_WRONG_PASSWORD);
}
}
/*
* Useful wrapper functions
*/

View File

@ -23,6 +23,8 @@ extern NetworkAdminSocketPool _networkadminsocket_pool;
/** Class for handling the server side of the game connection. */
class ServerNetworkAdminSocketHandler : public NetworkAdminSocketPool::PoolItem<&_networkadminsocket_pool>, public NetworkAdminSocketHandler, public TCPListenHandler<ServerNetworkAdminSocketHandler, ADMIN_PACKET_SERVER_FULL, ADMIN_PACKET_SERVER_BANNED> {
private:
std::unique_ptr<NetworkAuthenticationServerHandler> authentication_handler; ///< The handler for the authentication.
protected:
NetworkRecvStatus Receive_ADMIN_JOIN(Packet &p) override;
NetworkRecvStatus Receive_ADMIN_QUIT(Packet &p) override;
@ -33,9 +35,13 @@ protected:
NetworkRecvStatus Receive_ADMIN_RCON(Packet &p) override;
NetworkRecvStatus Receive_ADMIN_GAMESCRIPT(Packet &p) override;
NetworkRecvStatus Receive_ADMIN_PING(Packet &p) override;
NetworkRecvStatus Receive_ADMIN_JOIN_SECURE(Packet &p) override;
NetworkRecvStatus Receive_ADMIN_AUTH_RESPONSE(Packet &p) override;
NetworkRecvStatus SendProtocol();
NetworkRecvStatus SendPong(uint32_t d1);
NetworkRecvStatus SendAuthRequest();
NetworkRecvStatus SendEnableEncryption();
public:
AdminUpdateFrequency update_frequency[ADMIN_UPDATE_END]; ///< Admin requested update intervals.
std::chrono::steady_clock::time_point connect_time; ///< Time of connection.

View File

@ -682,6 +682,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_ERROR(Packet &p
STR_NETWORK_ERROR_TIMEOUT_JOIN, // NETWORK_ERROR_TIMEOUT_JOIN
STR_NETWORK_ERROR_INVALID_CLIENT_NAME, // NETWORK_ERROR_INVALID_CLIENT_NAME
STR_NETWORK_ERROR_NOT_ON_ALLOW_LIST, // NETWORK_ERROR_NOT_ON_ALLOW_LIST
STR_NETWORK_ERROR_SERVER_ERROR, // NETWORK_ERROR_NO_AUTHENTICATION_METHOD_AVAILABLE
};
static_assert(lengthof(network_error_strings) == NETWORK_ERROR_END);

View File

@ -175,7 +175,7 @@ public:
/** The authentication method that can be used. */
enum NetworkAuthenticationMethod : uint8_t {
NETWORK_AUTH_METHOD_X25519_KEY_EXCHANGE_ONLY, ///< No actual authentication is taking place, just perform a x25519 key exchange.
NETWORK_AUTH_METHOD_X25519_KEY_EXCHANGE_ONLY, ///< No actual authentication is taking place, just perform a x25519 key exchange. This method is not supported for the admin connection.
NETWORK_AUTH_METHOD_X25519_PAKE, ///< Authentication using x25519 password-authenticated key agreement.
NETWORK_AUTH_METHOD_X25519_AUTHORIZED_KEY, ///< Authentication using x22519 key exchange and authorized keys.
NETWORK_AUTH_METHOD_END, ///< Must ALWAYS be on the end of this list!! (period)

View File

@ -145,6 +145,7 @@ enum NetworkErrorCode {
NETWORK_ERROR_TIMEOUT_JOIN,
NETWORK_ERROR_INVALID_CLIENT_NAME,
NETWORK_ERROR_NOT_ON_ALLOW_LIST,
NETWORK_ERROR_NO_AUTHENTICATION_METHOD_AVAILABLE,
NETWORK_ERROR_END,
};

View File

@ -140,6 +140,7 @@ private:
"server_bind_addresses",
"server_authorized_keys",
"rcon_authorized_keys",
"admin_authorized_keys"
};
public:
@ -1266,6 +1267,7 @@ static void HandleSettingDescs(IniFile &generic_ini, IniFile &private_ini, IniFi
proc_list(private_ini, "bans", _network_ban_list);
proc_list(private_ini, "server_authorized_keys", _settings_client.network.server_authorized_keys);
proc_list(private_ini, "rcon_authorized_keys", _settings_client.network.rcon_authorized_keys);
proc_list(private_ini, "admin_authorized_keys", _settings_client.network.admin_authorized_keys);
}
}

View File

@ -316,7 +316,9 @@ struct NetworkSettings {
NetworkAuthorizedKeys server_authorized_keys; ///< Public keys of clients that are authorized to connect to the game.
std::string rcon_password; ///< password for rconsole (server side)
NetworkAuthorizedKeys rcon_authorized_keys; ///< Public keys of clients that are authorized to use the rconsole (server side).
bool allow_insecure_admin_login; ///< Whether to allow logging in as admin using the insecure old JOIN packet.
std::string admin_password; ///< password for the admin network
NetworkAuthorizedKeys admin_authorized_keys; ///< Public keys of clients that are authorized to use the admin network.
std::string client_name; ///< name of the player (as client)
std::string client_secret_key; ///< The secret key of the client for authorized key logins.
std::string client_public_key; ///< The public key of the client for authorized key logins.
@ -336,6 +338,8 @@ struct NetworkSettings {
std::string last_joined; ///< Last joined server
UseRelayService use_relay_service; ///< Use relay service?
ParticipateSurvey participate_survey; ///< Participate in the automated survey
bool AdminAuthenticationConfigured() const { return !this->admin_password.empty() || !this->admin_authorized_keys.empty(); }
};
/** Settings related to the creation of games. */

View File

@ -176,6 +176,12 @@ flags = SF_NOT_IN_SAVE | SF_NO_NETWORK_SYNC | SF_NETWORK_ONLY
def = true
cat = SC_EXPERT
[SDTC_BOOL]
var = network.allow_insecure_admin_login
flags = SF_NOT_IN_SAVE | SF_NO_NETWORK_SYNC | SF_NETWORK_ONLY
def = false
cat = SC_EXPERT
[SDTC_OMANY]
var = network.server_game_type
type = SLE_UINT8