Feature: authenticate to the server without sending the password

Either using password-authentication key exchange or via authorized keys
This commit is contained in:
Rubidium 2024-03-14 21:08:21 +01:00 committed by rubidium42
parent dd532cbc77
commit 5706801ea7
11 changed files with 184 additions and 79 deletions

View File

@ -2574,6 +2574,7 @@ STR_NETWORK_ERROR_BAD_PLAYER_NAME :{WHITE}Your pla
STR_NETWORK_ERROR_BAD_SERVER_NAME :{WHITE}Your server name has not been set. The name can be set at the top of the Multiplayer window STR_NETWORK_ERROR_BAD_SERVER_NAME :{WHITE}Your server name has not been set. The name can be set at the top of the Multiplayer window
STR_NETWORK_ERROR_WRONG_REVISION :{WHITE}The revision of this client does not match the server's revision STR_NETWORK_ERROR_WRONG_REVISION :{WHITE}The revision of this client does not match the server's revision
STR_NETWORK_ERROR_WRONG_PASSWORD :{WHITE}Wrong password STR_NETWORK_ERROR_WRONG_PASSWORD :{WHITE}Wrong password
STR_NETWORK_ERROR_NOT_ON_ALLOW_LIST :{WHITE}You are not on the list of allowed clients
STR_NETWORK_ERROR_SERVER_FULL :{WHITE}The server is full STR_NETWORK_ERROR_SERVER_FULL :{WHITE}The server is full
STR_NETWORK_ERROR_SERVER_BANNED :{WHITE}You are banned from this server STR_NETWORK_ERROR_SERVER_BANNED :{WHITE}You are banned from this server
STR_NETWORK_ERROR_KICKED :{WHITE}You were kicked out of the game STR_NETWORK_ERROR_KICKED :{WHITE}You were kicked out of the game
@ -2589,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_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 STR_NETWORK_ERROR_CLIENT_GUI_LOST_CONNECTION :{WHITE}The last {NUM} second{P "" s} no data has arrived from the server
###length 21 ###length 22
STR_NETWORK_ERROR_CLIENT_GENERAL :general error STR_NETWORK_ERROR_CLIENT_GENERAL :general error
STR_NETWORK_ERROR_CLIENT_DESYNC :desync error STR_NETWORK_ERROR_CLIENT_DESYNC :desync error
STR_NETWORK_ERROR_CLIENT_SAVEGAME :could not load map STR_NETWORK_ERROR_CLIENT_SAVEGAME :could not load map
@ -2601,6 +2602,7 @@ STR_NETWORK_ERROR_CLIENT_NOT_EXPECTED :received invali
STR_NETWORK_ERROR_CLIENT_WRONG_REVISION :wrong revision STR_NETWORK_ERROR_CLIENT_WRONG_REVISION :wrong revision
STR_NETWORK_ERROR_CLIENT_NAME_IN_USE :name already in use STR_NETWORK_ERROR_CLIENT_NAME_IN_USE :name already in use
STR_NETWORK_ERROR_CLIENT_WRONG_PASSWORD :wrong password STR_NETWORK_ERROR_CLIENT_WRONG_PASSWORD :wrong password
STR_NETWORK_ERROR_CLIENT_NOT_ON_ALLOW_LIST :not on allow list
STR_NETWORK_ERROR_CLIENT_COMPANY_MISMATCH :wrong company in DoCommand STR_NETWORK_ERROR_CLIENT_COMPANY_MISMATCH :wrong company in DoCommand
STR_NETWORK_ERROR_CLIENT_KICKED :kicked by server STR_NETWORK_ERROR_CLIENT_KICKED :kicked by server
STR_NETWORK_ERROR_CLIENT_CHEATER :was trying to use a cheat STR_NETWORK_ERROR_CLIENT_CHEATER :was trying to use a cheat

View File

@ -82,9 +82,10 @@ NetworkRecvStatus NetworkGameSocketHandler::HandlePacket(Packet &p)
case PACKET_SERVER_GAME_INFO: return this->Receive_SERVER_GAME_INFO(p); case PACKET_SERVER_GAME_INFO: return this->Receive_SERVER_GAME_INFO(p);
case PACKET_SERVER_CLIENT_INFO: return this->Receive_SERVER_CLIENT_INFO(p); case PACKET_SERVER_CLIENT_INFO: return this->Receive_SERVER_CLIENT_INFO(p);
case PACKET_CLIENT_IDENTIFY: return this->Receive_CLIENT_IDENTIFY(p); case PACKET_CLIENT_IDENTIFY: return this->Receive_CLIENT_IDENTIFY(p);
case PACKET_SERVER_NEED_GAME_PASSWORD: return this->Receive_SERVER_NEED_GAME_PASSWORD(p); case PACKET_SERVER_AUTH_REQUEST: return this->Receive_SERVER_AUTH_REQUEST(p);
case PACKET_SERVER_NEED_COMPANY_PASSWORD: return this->Receive_SERVER_NEED_COMPANY_PASSWORD(p); case PACKET_SERVER_NEED_COMPANY_PASSWORD: return this->Receive_SERVER_NEED_COMPANY_PASSWORD(p);
case PACKET_CLIENT_GAME_PASSWORD: return this->Receive_CLIENT_GAME_PASSWORD(p); case PACKET_CLIENT_AUTH_RESPONSE: return this->Receive_CLIENT_AUTH_RESPONSE(p);
case PACKET_SERVER_AUTH_COMPLETED: return this->Receive_SERVER_AUTH_COMPLETED(p);
case PACKET_CLIENT_COMPANY_PASSWORD: return this->Receive_CLIENT_COMPANY_PASSWORD(p); case PACKET_CLIENT_COMPANY_PASSWORD: return this->Receive_CLIENT_COMPANY_PASSWORD(p);
case PACKET_SERVER_WELCOME: return this->Receive_SERVER_WELCOME(p); case PACKET_SERVER_WELCOME: return this->Receive_SERVER_WELCOME(p);
case PACKET_CLIENT_GETMAP: return this->Receive_CLIENT_GETMAP(p); case PACKET_CLIENT_GETMAP: return this->Receive_CLIENT_GETMAP(p);
@ -164,9 +165,10 @@ NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_GAME_INFO(Packet &) {
NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_GAME_INFO(Packet &) { return this->ReceiveInvalidPacket(PACKET_SERVER_GAME_INFO); } NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_GAME_INFO(Packet &) { return this->ReceiveInvalidPacket(PACKET_SERVER_GAME_INFO); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_CLIENT_INFO(Packet &) { return this->ReceiveInvalidPacket(PACKET_SERVER_CLIENT_INFO); } NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_CLIENT_INFO(Packet &) { return this->ReceiveInvalidPacket(PACKET_SERVER_CLIENT_INFO); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_IDENTIFY(Packet &) { return this->ReceiveInvalidPacket(PACKET_CLIENT_IDENTIFY); } NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_IDENTIFY(Packet &) { return this->ReceiveInvalidPacket(PACKET_CLIENT_IDENTIFY); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_NEED_GAME_PASSWORD(Packet &) { return this->ReceiveInvalidPacket(PACKET_SERVER_NEED_GAME_PASSWORD); } NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_AUTH_REQUEST(Packet &) { return this->ReceiveInvalidPacket(PACKET_SERVER_AUTH_REQUEST); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_NEED_COMPANY_PASSWORD(Packet &) { return this->ReceiveInvalidPacket(PACKET_SERVER_NEED_COMPANY_PASSWORD); } NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_NEED_COMPANY_PASSWORD(Packet &) { return this->ReceiveInvalidPacket(PACKET_SERVER_NEED_COMPANY_PASSWORD); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_GAME_PASSWORD(Packet &) { return this->ReceiveInvalidPacket(PACKET_CLIENT_GAME_PASSWORD); } NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_AUTH_RESPONSE(Packet &) { return this->ReceiveInvalidPacket(PACKET_CLIENT_AUTH_RESPONSE); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_AUTH_COMPLETED(Packet &) { return this->ReceiveInvalidPacket(PACKET_SERVER_AUTH_COMPLETED); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_COMPANY_PASSWORD(Packet &) { return this->ReceiveInvalidPacket(PACKET_CLIENT_COMPANY_PASSWORD); } NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_COMPANY_PASSWORD(Packet &) { return this->ReceiveInvalidPacket(PACKET_CLIENT_COMPANY_PASSWORD); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_WELCOME(Packet &) { return this->ReceiveInvalidPacket(PACKET_SERVER_WELCOME); } NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_WELCOME(Packet &) { return this->ReceiveInvalidPacket(PACKET_SERVER_WELCOME); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_GETMAP(Packet &) { return this->ReceiveInvalidPacket(PACKET_CLIENT_GETMAP); } NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_GETMAP(Packet &) { return this->ReceiveInvalidPacket(PACKET_CLIENT_GETMAP); }

View File

@ -15,6 +15,7 @@
#include "os_abstraction.h" #include "os_abstraction.h"
#include "tcp.h" #include "tcp.h"
#include "../network_type.h" #include "../network_type.h"
#include "../network_crypto.h"
#include "../../core/pool_type.hpp" #include "../../core/pool_type.hpp"
#include <chrono> #include <chrono>
@ -56,16 +57,19 @@ enum PacketGameType : uint8_t {
* the map and other important data. * the map and other important data.
*/ */
/* After the initial join, the next step is identification. */ /* After the join step, the first perform game authentication and enabling encryption. */
PACKET_SERVER_AUTH_REQUEST, ///< The server requests the client to authenticate using a number of methods.
PACKET_CLIENT_AUTH_RESPONSE, ///< The client responds to the authentication request.
PACKET_SERVER_AUTH_COMPLETED, ///< The server indicates the authentication is completed.
/* After the authentication is done, the next step is identification. */
PACKET_CLIENT_IDENTIFY, ///< Client telling the server the client's name and requested company. PACKET_CLIENT_IDENTIFY, ///< Client telling the server the client's name and requested company.
/* After the identify step, the next is checking NewGRFs. */ /* After the identify step, the next is checking NewGRFs. */
PACKET_SERVER_CHECK_NEWGRFS, ///< Server sends NewGRF IDs and MD5 checksums for the client to check. PACKET_SERVER_CHECK_NEWGRFS, ///< Server sends NewGRF IDs and MD5 checksums for the client to check.
PACKET_CLIENT_NEWGRFS_CHECKED, ///< Client acknowledges that it has all required NewGRFs. PACKET_CLIENT_NEWGRFS_CHECKED, ///< Client acknowledges that it has all required NewGRFs.
/* Checking the game, and then company passwords. */ /* Checking the company passwords. */
PACKET_SERVER_NEED_GAME_PASSWORD, ///< Server requests the (hashed) game password.
PACKET_CLIENT_GAME_PASSWORD, ///< Clients sends the (hashed) game password.
PACKET_SERVER_NEED_COMPANY_PASSWORD, ///< Server requests the (hashed) company password. PACKET_SERVER_NEED_COMPANY_PASSWORD, ///< Server requests the (hashed) company password.
PACKET_CLIENT_COMPANY_PASSWORD, ///< Client sends the (hashed) company password. PACKET_CLIENT_COMPANY_PASSWORD, ///< Client sends the (hashed) company password.
@ -214,10 +218,13 @@ protected:
virtual NetworkRecvStatus Receive_CLIENT_IDENTIFY(Packet &p); virtual NetworkRecvStatus Receive_CLIENT_IDENTIFY(Packet &p);
/** /**
* Indication to the client that the server needs a game password. * Indication to the client that it needs to authenticate:
* bool Whether to use the password in the key exchange.
* 32 * uint8_t Public key of the server.
* 24 * uint8_t Nonce for the key exchange.
* @param p The packet that was just received. * @param p The packet that was just received.
*/ */
virtual NetworkRecvStatus Receive_SERVER_NEED_GAME_PASSWORD(Packet &p); virtual NetworkRecvStatus Receive_SERVER_AUTH_REQUEST(Packet &p);
/** /**
* Indication to the client that the server needs a company password: * Indication to the client that the server needs a company password:
@ -228,12 +235,19 @@ protected:
virtual NetworkRecvStatus Receive_SERVER_NEED_COMPANY_PASSWORD(Packet &p); virtual NetworkRecvStatus Receive_SERVER_NEED_COMPANY_PASSWORD(Packet &p);
/** /**
* Send a password to the server to authorize: * Send the response to the authentication request:
* uint8_t Password type (see NetworkPasswordType). * 32 * uint8_t Public key of the client.
* string The password. * 8 * uint8_t Random message that got encoded and signed.
* 16 * uint8_t Message authentication code.
* @param p The packet that was just received. * @param p The packet that was just received.
*/ */
virtual NetworkRecvStatus Receive_CLIENT_GAME_PASSWORD(Packet &p); virtual NetworkRecvStatus Receive_CLIENT_AUTH_RESPONSE(Packet &p);
/**
* Indication to the client that authentication has completed.
* @param p The packet that was just received.
*/
virtual NetworkRecvStatus Receive_SERVER_AUTH_COMPLETED(Packet &p);
/** /**
* Send a password to the server to authorize * Send a password to the server to authorize

View File

@ -321,6 +321,7 @@ StringID GetNetworkErrorMsg(NetworkErrorCode err)
STR_NETWORK_ERROR_CLIENT_TIMEOUT_MAP, STR_NETWORK_ERROR_CLIENT_TIMEOUT_MAP,
STR_NETWORK_ERROR_CLIENT_TIMEOUT_JOIN, STR_NETWORK_ERROR_CLIENT_TIMEOUT_JOIN,
STR_NETWORK_ERROR_CLIENT_INVALID_CLIENT_NAME, STR_NETWORK_ERROR_CLIENT_INVALID_CLIENT_NAME,
STR_NETWORK_ERROR_CLIENT_NOT_ON_ALLOW_LIST,
}; };
static_assert(lengthof(network_error_strings) == NETWORK_ERROR_END); static_assert(lengthof(network_error_strings) == NETWORK_ERROR_END);

View File

@ -349,7 +349,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendJoin()
p->Send_uint32(_openttd_newgrf_version); p->Send_uint32(_openttd_newgrf_version);
my_client->SendPacket(std::move(p)); my_client->SendPacket(std::move(p));
return ClientNetworkGameSocketHandler::SendIdentify(); return NETWORK_RECV_STATUS_OKAY;
} }
NetworkRecvStatus ClientNetworkGameSocketHandler::SendIdentify() NetworkRecvStatus ClientNetworkGameSocketHandler::SendIdentify()
@ -377,13 +377,14 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendNewGRFsOk()
* Set the game password as requested. * Set the game password as requested.
* @param password The game password. * @param password The game password.
*/ */
NetworkRecvStatus ClientNetworkGameSocketHandler::SendGamePassword(const std::string &password) NetworkRecvStatus ClientNetworkGameSocketHandler::SendAuthResponse()
{ {
Debug(net, 9, "Client::SendGamePassword()"); Debug(net, 9, "Client::SendAuthResponse()");
auto p = std::make_unique<Packet>(my_client, PACKET_CLIENT_GAME_PASSWORD); auto p = std::make_unique<Packet>(my_client, PACKET_CLIENT_AUTH_RESPONSE);
p->Send_string(password); my_client->authentication_handler->SendResponse(*p);
my_client->SendPacket(std::move(p)); my_client->SendPacket(std::move(p));
return NETWORK_RECV_STATUS_OKAY; return NETWORK_RECV_STATUS_OKAY;
} }
@ -680,6 +681,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_ERROR(Packet &p
STR_NETWORK_ERROR_TIMEOUT_MAP, // NETWORK_ERROR_TIMEOUT_MAP STR_NETWORK_ERROR_TIMEOUT_MAP, // NETWORK_ERROR_TIMEOUT_MAP
STR_NETWORK_ERROR_TIMEOUT_JOIN, // NETWORK_ERROR_TIMEOUT_JOIN STR_NETWORK_ERROR_TIMEOUT_JOIN, // NETWORK_ERROR_TIMEOUT_JOIN
STR_NETWORK_ERROR_INVALID_CLIENT_NAME, // NETWORK_ERROR_INVALID_CLIENT_NAME STR_NETWORK_ERROR_INVALID_CLIENT_NAME, // NETWORK_ERROR_INVALID_CLIENT_NAME
STR_NETWORK_ERROR_NOT_ON_ALLOW_LIST, // NETWORK_ERROR_NOT_ON_ALLOW_LIST
}; };
static_assert(lengthof(network_error_strings) == NETWORK_ERROR_END); static_assert(lengthof(network_error_strings) == NETWORK_ERROR_END);
@ -705,7 +707,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_ERROR(Packet &p
NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_CHECK_NEWGRFS(Packet &p) NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_CHECK_NEWGRFS(Packet &p)
{ {
if (this->status != STATUS_JOIN) return NETWORK_RECV_STATUS_MALFORMED_PACKET; if (this->status != STATUS_AUTHENTICATED) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
uint grf_count = p.Recv_uint8(); uint grf_count = p.Recv_uint8();
NetworkRecvStatus ret = NETWORK_RECV_STATUS_OKAY; NetworkRecvStatus ret = NETWORK_RECV_STATUS_OKAY;
@ -736,26 +738,67 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_CHECK_NEWGRFS(P
return ret; return ret;
} }
NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_NEED_GAME_PASSWORD(Packet &) class ClientGamePasswordRequestHandler : public NetworkAuthenticationPasswordRequestHandler {
virtual void SendResponse() override { MyClient::SendAuthResponse(); }
virtual void AskUserForPassword(std::shared_ptr<NetworkAuthenticationPasswordRequest> request) override
{
if (!_network_join.server_password.empty()) {
request->Reply(_network_join.server_password);
} else {
ShowNetworkNeedPassword(NETWORK_GAME_PASSWORD, request);
}
}
};
NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_AUTH_REQUEST(Packet &p)
{ {
if (this->status < STATUS_JOIN || this->status >= STATUS_AUTH_GAME) return NETWORK_RECV_STATUS_MALFORMED_PACKET; if (this->status != STATUS_JOIN && this->status != STATUS_AUTH_GAME) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
Debug(net, 9, "Client::status = AUTH_GAME"); Debug(net, 9, "Client::status = AUTH_GAME");
this->status = STATUS_AUTH_GAME; this->status = STATUS_AUTH_GAME;
Debug(net, 9, "Client::Receive_SERVER_NEED_GAME_PASSWORD()"); Debug(net, 9, "Client::Receive_SERVER_AUTH_REQUEST()");
if (!_network_join.server_password.empty()) { if (this->authentication_handler == nullptr) {
return SendGamePassword(_network_join.server_password); this->authentication_handler = NetworkAuthenticationClientHandler::Create(std::make_shared<ClientGamePasswordRequestHandler>(),
_settings_client.network.client_secret_key, _settings_client.network.client_public_key);
} }
switch (this->authentication_handler->ReceiveRequest(p)) {
case NetworkAuthenticationClientHandler::READY_FOR_RESPONSE:
return SendAuthResponse();
ShowNetworkNeedPassword(NETWORK_GAME_PASSWORD); case NetworkAuthenticationClientHandler::AWAIT_USER_INPUT:
return NETWORK_RECV_STATUS_OKAY;
return NETWORK_RECV_STATUS_OKAY; case NetworkAuthenticationClientHandler::INVALID:
default:
return NETWORK_RECV_STATUS_MALFORMED_PACKET;
}
} }
NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_AUTH_COMPLETED(Packet &)
{
if (this->status != STATUS_AUTH_GAME || this->authentication_handler == nullptr) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
Debug(net, 9, "Client::Receive_SERVER_AUTH_COMPLETED()");
this->authentication_handler = nullptr;
Debug(net, 9, "Client::status = AUTHENTICATED");
this->status = STATUS_AUTHENTICATED;
return this->SendIdentify();
}
class CompanyPasswordRequest : public NetworkAuthenticationPasswordRequest {
virtual void Reply(const std::string &password) override
{
MyClient::SendCompanyPassword(password);
}
};
NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_NEED_COMPANY_PASSWORD(Packet &p) NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_NEED_COMPANY_PASSWORD(Packet &p)
{ {
if (this->status < STATUS_JOIN || this->status >= STATUS_AUTH_COMPANY) return NETWORK_RECV_STATUS_MALFORMED_PACKET; if (this->status < STATUS_AUTHENTICATED || this->status >= STATUS_AUTH_COMPANY) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
Debug(net, 9, "Client::status = AUTH_COMPANY"); Debug(net, 9, "Client::status = AUTH_COMPANY");
this->status = STATUS_AUTH_COMPANY; this->status = STATUS_AUTH_COMPANY;
@ -769,14 +812,14 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_NEED_COMPANY_PA
return SendCompanyPassword(_network_join.company_password); return SendCompanyPassword(_network_join.company_password);
} }
ShowNetworkNeedPassword(NETWORK_COMPANY_PASSWORD); ShowNetworkNeedPassword(NETWORK_COMPANY_PASSWORD, std::make_shared<CompanyPasswordRequest>());
return NETWORK_RECV_STATUS_OKAY; return NETWORK_RECV_STATUS_OKAY;
} }
NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_WELCOME(Packet &p) NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_WELCOME(Packet &p)
{ {
if (this->status < STATUS_JOIN || this->status >= STATUS_AUTHORIZED) return NETWORK_RECV_STATUS_MALFORMED_PACKET; if (this->status < STATUS_AUTHENTICATED || this->status >= STATUS_AUTHORIZED) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
Debug(net, 9, "Client::status = AUTHORIZED"); Debug(net, 9, "Client::status = AUTHORIZED");
this->status = STATUS_AUTHORIZED; this->status = STATUS_AUTHORIZED;

View File

@ -15,6 +15,7 @@
/** Class for handling the client side of the game connection. */ /** Class for handling the client side of the game connection. */
class ClientNetworkGameSocketHandler : public ZeroedMemoryAllocator, public NetworkGameSocketHandler { class ClientNetworkGameSocketHandler : public ZeroedMemoryAllocator, public NetworkGameSocketHandler {
private: private:
std::unique_ptr<class NetworkAuthenticationClientHandler> authentication_handler; ///< The handler for the authentication.
std::string connection_string; ///< Address we are connected to. std::string connection_string; ///< Address we are connected to.
std::shared_ptr<struct PacketReader> savegame; ///< Packet reader for reading the savegame. std::shared_ptr<struct PacketReader> savegame; ///< Packet reader for reading the savegame.
uint8_t token; ///< The token we need to send back to the server to prove we're the right client. uint8_t token; ///< The token we need to send back to the server to prove we're the right client.
@ -23,8 +24,9 @@ private:
enum ServerStatus { enum ServerStatus {
STATUS_INACTIVE, ///< The client is not connected nor active. STATUS_INACTIVE, ///< The client is not connected nor active.
STATUS_JOIN, ///< We are trying to join a server. STATUS_JOIN, ///< We are trying to join a server.
STATUS_NEWGRFS_CHECK, ///< Last action was checking NewGRFs.
STATUS_AUTH_GAME, ///< Last action was requesting game (server) password. STATUS_AUTH_GAME, ///< Last action was requesting game (server) password.
STATUS_AUTHENTICATED, ///< The game authentication has completed.
STATUS_NEWGRFS_CHECK, ///< Last action was checking NewGRFs.
STATUS_AUTH_COMPANY, ///< Last action was requesting company password. STATUS_AUTH_COMPANY, ///< Last action was requesting company password.
STATUS_AUTHORIZED, ///< The client is authorized at the server. STATUS_AUTHORIZED, ///< The client is authorized at the server.
STATUS_MAP_WAIT, ///< The client is waiting as someone else is downloading the map. STATUS_MAP_WAIT, ///< The client is waiting as someone else is downloading the map.
@ -44,7 +46,8 @@ protected:
NetworkRecvStatus Receive_SERVER_BANNED(Packet &p) override; NetworkRecvStatus Receive_SERVER_BANNED(Packet &p) override;
NetworkRecvStatus Receive_SERVER_ERROR(Packet &p) override; NetworkRecvStatus Receive_SERVER_ERROR(Packet &p) override;
NetworkRecvStatus Receive_SERVER_CLIENT_INFO(Packet &p) override; NetworkRecvStatus Receive_SERVER_CLIENT_INFO(Packet &p) override;
NetworkRecvStatus Receive_SERVER_NEED_GAME_PASSWORD(Packet &p) override; NetworkRecvStatus Receive_SERVER_AUTH_REQUEST(Packet &p) override;
NetworkRecvStatus Receive_SERVER_AUTH_COMPLETED(Packet &p) override;
NetworkRecvStatus Receive_SERVER_NEED_COMPANY_PASSWORD(Packet &p) override; NetworkRecvStatus Receive_SERVER_NEED_COMPANY_PASSWORD(Packet &p) override;
NetworkRecvStatus Receive_SERVER_WELCOME(Packet &p) override; NetworkRecvStatus Receive_SERVER_WELCOME(Packet &p) override;
NetworkRecvStatus Receive_SERVER_WAIT(Packet &p) override; NetworkRecvStatus Receive_SERVER_WAIT(Packet &p) override;
@ -86,7 +89,7 @@ public:
static NetworkRecvStatus SendQuit(); static NetworkRecvStatus SendQuit();
static NetworkRecvStatus SendAck(); static NetworkRecvStatus SendAck();
static NetworkRecvStatus SendGamePassword(const std::string &password); static NetworkRecvStatus SendAuthResponse();
static NetworkRecvStatus SendCompanyPassword(const std::string &password); static NetworkRecvStatus SendCompanyPassword(const std::string &password);
static NetworkRecvStatus SendChat(NetworkAction action, DestType type, int dest, const std::string &msg, int64_t data); static NetworkRecvStatus SendChat(NetworkAction action, DestType type, int dest, const std::string &msg, int64_t data);

View File

@ -2103,7 +2103,7 @@ uint32_t _network_join_bytes; ///< The number of bytes we already do
uint32_t _network_join_bytes_total; ///< The total number of bytes to download. uint32_t _network_join_bytes_total; ///< The total number of bytes to download.
struct NetworkJoinStatusWindow : Window { struct NetworkJoinStatusWindow : Window {
NetworkPasswordType password_type; std::shared_ptr<NetworkAuthenticationPasswordRequest> request;
NetworkJoinStatusWindow(WindowDesc *desc) : Window(desc) NetworkJoinStatusWindow(WindowDesc *desc) : Window(desc)
{ {
@ -2199,16 +2199,12 @@ struct NetworkJoinStatusWindow : Window {
void OnQueryTextFinished(char *str) override void OnQueryTextFinished(char *str) override
{ {
if (StrEmpty(str)) { if (StrEmpty(str) || this->request == nullptr) {
NetworkDisconnect(); NetworkDisconnect();
return; return;
} }
switch (this->password_type) { this->request->Reply(str);
case NETWORK_GAME_PASSWORD: MyClient::SendGamePassword (str); break;
case NETWORK_COMPANY_PASSWORD: MyClient::SendCompanyPassword(str); break;
default: NOT_REACHED();
}
} }
}; };
@ -2236,11 +2232,11 @@ void ShowJoinStatusWindow()
new NetworkJoinStatusWindow(&_network_join_status_window_desc); new NetworkJoinStatusWindow(&_network_join_status_window_desc);
} }
void ShowNetworkNeedPassword(NetworkPasswordType npt) void ShowNetworkNeedPassword(NetworkPasswordType npt, std::shared_ptr<NetworkAuthenticationPasswordRequest> request)
{ {
NetworkJoinStatusWindow *w = (NetworkJoinStatusWindow *)FindWindowById(WC_NETWORK_STATUS_WINDOW, WN_NETWORK_STATUS_WINDOW_JOIN); NetworkJoinStatusWindow *w = (NetworkJoinStatusWindow *)FindWindowById(WC_NETWORK_STATUS_WINDOW, WN_NETWORK_STATUS_WINDOW_JOIN);
if (w == nullptr) return; if (w == nullptr) return;
w->password_type = npt; w->request = request;
StringID caption; StringID caption;
switch (npt) { switch (npt) {

View File

@ -17,7 +17,7 @@
#include "network_type.h" #include "network_type.h"
#include "network_gamelist.h" #include "network_gamelist.h"
void ShowNetworkNeedPassword(NetworkPasswordType npt); void ShowNetworkNeedPassword(NetworkPasswordType npt, std::shared_ptr<class NetworkAuthenticationPasswordRequest> request);
void ShowNetworkChatQueryWindow(DestType type, int dest); void ShowNetworkChatQueryWindow(DestType type, int dest);
void ShowJoinStatusWindow(); void ShowJoinStatusWindow();
void ShowNetworkGameWindow(); void ShowNetworkGameWindow();

View File

@ -58,6 +58,10 @@ INSTANTIATE_POOL_METHODS(NetworkClientSocket)
/** Instantiate the listen sockets. */ /** Instantiate the listen sockets. */
template SocketList TCPListenHandler<ServerNetworkGameSocketHandler, PACKET_SERVER_FULL, PACKET_SERVER_BANNED>::sockets; template SocketList TCPListenHandler<ServerNetworkGameSocketHandler, PACKET_SERVER_FULL, PACKET_SERVER_BANNED>::sockets;
static NetworkAuthenticationDefaultPasswordProvider _password_provider(_settings_client.network.server_password); ///< Provides the password validation for the game's password.
static NetworkAuthenticationDefaultAuthorizedKeyHandler _authorized_key_handler(_settings_client.network.server_authorized_keys); ///< Provides the authorized key handling for the game authentication.
/** Writing a savegame directly to a number of packets. */ /** Writing a savegame directly to a number of packets. */
struct PacketWriter : SaveFilter { struct PacketWriter : SaveFilter {
ServerNetworkGameSocketHandler *cs; ///< Socket we are associated with. ServerNetworkGameSocketHandler *cs; ///< Socket we are associated with.
@ -407,8 +411,8 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendNewGRFCheck()
this->status = STATUS_NEWGRFS_CHECK; this->status = STATUS_NEWGRFS_CHECK;
if (_grfconfig == nullptr) { if (_grfconfig == nullptr) {
/* There are no NewGRFs, continue with the game password. */ /* There are no NewGRFs, continue with the company password. */
return this->SendNeedGamePassword(); return this->SendNeedCompanyPassword();
} }
auto p = std::make_unique<Packet>(this, PACKET_SERVER_CHECK_NEWGRFS, TCP_MTU); auto p = std::make_unique<Packet>(this, PACKET_SERVER_CHECK_NEWGRFS, TCP_MTU);
@ -429,25 +433,39 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendNewGRFCheck()
} }
/** Request the game password. */ /** Request the game password. */
NetworkRecvStatus ServerNetworkGameSocketHandler::SendNeedGamePassword() NetworkRecvStatus ServerNetworkGameSocketHandler::SendAuthRequest()
{ {
Debug(net, 9, "client[{}] SendNeedGamePassword()", this->client_id); Debug(net, 9, "client[{}] SendAuthRequest()", this->client_id);
/* Invalid packet when status is anything but STATUS_NEWGRFS_CHECK. */ /* Invalid packet when status is anything but STATUS_INACTIVE or STATUS_AUTH_GAME. */
if (this->status != STATUS_NEWGRFS_CHECK) return this->CloseConnection(NETWORK_RECV_STATUS_MALFORMED_PACKET); if (this->status != STATUS_INACTIVE && status != STATUS_AUTH_GAME) return this->CloseConnection(NETWORK_RECV_STATUS_MALFORMED_PACKET);
Debug(net, 9, "client[{}] status = AUTH_GAME", this->client_id); Debug(net, 9, "client[{}] status = AUTH_GAME", this->client_id);
this->status = STATUS_AUTH_GAME; this->status = STATUS_AUTH_GAME;
if (_settings_client.network.server_password.empty()) {
/* Do not actually need a game password, continue with the company password. */
return this->SendNeedCompanyPassword();
}
/* Reset 'lag' counters */ /* Reset 'lag' counters */
this->last_frame = this->last_frame_server = _frame_counter; this->last_frame = this->last_frame_server = _frame_counter;
auto p = std::make_unique<Packet>(this, PACKET_SERVER_NEED_GAME_PASSWORD); if (this->authentication_handler == nullptr) {
this->authentication_handler = NetworkAuthenticationServerHandler::Create(&_password_provider, &_authorized_key_handler);
}
auto p = std::make_unique<Packet>(this, PACKET_SERVER_AUTH_REQUEST);
this->authentication_handler->SendRequest(*p);
this->SendPacket(std::move(p));
return NETWORK_RECV_STATUS_OKAY;
}
/** Notify the client that the authentication has completed. */
NetworkRecvStatus ServerNetworkGameSocketHandler::SendAuthCompleted()
{
Debug(net, 9, "client[{}] SendAuthCompleted()", this->client_id);
/* Invalid packet when status is anything but STATUS_AUTH_GAME. */
if (this->status != STATUS_AUTH_GAME) return this->CloseConnection(NETWORK_RECV_STATUS_MALFORMED_PACKET);
auto p = std::make_unique<Packet>(this, PACKET_SERVER_AUTH_COMPLETED);
this->SendPacket(std::move(p)); this->SendPacket(std::move(p));
return NETWORK_RECV_STATUS_OKAY; return NETWORK_RECV_STATUS_OKAY;
} }
@ -457,8 +475,8 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendNeedCompanyPassword()
{ {
Debug(net, 9, "client[{}] SendNeedCompanyPassword()", this->client_id); Debug(net, 9, "client[{}] SendNeedCompanyPassword()", this->client_id);
/* Invalid packet when status is anything but STATUS_AUTH_GAME. */ /* Invalid packet when status is anything but STATUS_NEWGRFS_CHECK. */
if (this->status != STATUS_AUTH_GAME) return this->CloseConnection(NETWORK_RECV_STATUS_MALFORMED_PACKET); if (this->status != STATUS_NEWGRFS_CHECK) return this->CloseConnection(NETWORK_RECV_STATUS_MALFORMED_PACKET);
Debug(net, 9, "client[{}] status = AUTH_COMPANY", this->client_id); Debug(net, 9, "client[{}] status = AUTH_COMPANY", this->client_id);
this->status = STATUS_AUTH_COMPANY; this->status = STATUS_AUTH_COMPANY;
@ -865,7 +883,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_NEWGRFS_CHECKED
Debug(net, 9, "client[{}] Receive_CLIENT_NEWGRFS_CHECKED()", this->client_id); Debug(net, 9, "client[{}] Receive_CLIENT_NEWGRFS_CHECKED()", this->client_id);
return this->SendNeedGamePassword(); return this->SendNeedCompanyPassword();
} }
NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_JOIN(Packet &p) NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_JOIN(Packet &p)
@ -891,13 +909,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_JOIN(Packet &p)
return this->SendError(NETWORK_ERROR_WRONG_REVISION); return this->SendError(NETWORK_ERROR_WRONG_REVISION);
} }
Debug(net, 9, "client[{}] status = IDENTIFY", this->client_id); return this->SendAuthRequest();
this->status = STATUS_IDENTIFY;
/* Reset 'lag' counters */
this->last_frame = this->last_frame_server = _frame_counter;
return NETWORK_RECV_STATUS_OKAY;
} }
NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_IDENTIFY(Packet &p) NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_IDENTIFY(Packet &p)
@ -953,24 +965,52 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_IDENTIFY(Packet
return this->SendNewGRFCheck(); return this->SendNewGRFCheck();
} }
NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_GAME_PASSWORD(Packet &p) static NetworkErrorCode GetErrorForAuthenticationMethod(NetworkAuthenticationMethod method)
{
switch (method) {
case NETWORK_AUTH_METHOD_X25519_PAKE:
return NETWORK_ERROR_WRONG_PASSWORD;
case NETWORK_AUTH_METHOD_X25519_AUTHORIZED_KEY:
return NETWORK_ERROR_NOT_ON_ALLOW_LIST;
default:
NOT_REACHED();
}
}
NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_AUTH_RESPONSE(Packet &p)
{ {
if (this->status != STATUS_AUTH_GAME) { if (this->status != STATUS_AUTH_GAME) {
return this->SendError(NETWORK_ERROR_NOT_EXPECTED); return this->SendError(NETWORK_ERROR_NOT_EXPECTED);
} }
Debug(net, 9, "client[{}] Receive_CLIENT_GAME_PASSWORD()", this->client_id); Debug(net, 9, "client[{}] Receive_CLIENT_AUTH_RESPONSE()", this->client_id);
std::string password = p.Recv_string(NETWORK_PASSWORD_LENGTH); auto authentication_method = this->authentication_handler->GetAuthenticationMethod();
switch (this->authentication_handler->ReceiveResponse(p)) {
case NetworkAuthenticationServerHandler::AUTHENTICATED:
break;
/* Check game password. Allow joining if we cleared the password meanwhile */ case NetworkAuthenticationServerHandler::RETRY_NEXT_METHOD:
if (!_settings_client.network.server_password.empty() && return this->SendAuthRequest();
_settings_client.network.server_password.compare(password) != 0) {
/* Password is invalid */ case NetworkAuthenticationServerHandler::NOT_AUTHENTICATED:
return this->SendError(NETWORK_ERROR_WRONG_PASSWORD); default:
return this->SendError(GetErrorForAuthenticationMethod(authentication_method));
} }
return this->SendNeedCompanyPassword(); NetworkRecvStatus status = this->SendAuthCompleted();
if (status != NETWORK_RECV_STATUS_OKAY) return status;
this->authentication_handler = nullptr;
Debug(net, 9, "client[{}] status = IDENTIFY", this->client_id);
this->status = STATUS_IDENTIFY;
/* Reset 'lag' counters */
this->last_frame = this->last_frame_server = _frame_counter;
return NETWORK_RECV_STATUS_OKAY;
} }
NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_COMPANY_PASSWORD(Packet &p) NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_COMPANY_PASSWORD(Packet &p)
@ -1978,9 +2018,9 @@ void NetworkServerShowStatusToConsole()
{ {
static const char * const stat_str[] = { static const char * const stat_str[] = {
"inactive", "inactive",
"authorizing (server password)",
"identifing client", "identifing client",
"checking NewGRFs", "checking NewGRFs",
"authorizing (server password)",
"authorizing (company password)", "authorizing (company password)",
"authorized", "authorized",
"waiting", "waiting",

View File

@ -23,10 +23,12 @@ extern NetworkClientSocketPool _networkclientsocket_pool;
/** Class for handling the server side of the game connection. */ /** Class for handling the server side of the game connection. */
class ServerNetworkGameSocketHandler : public NetworkClientSocketPool::PoolItem<&_networkclientsocket_pool>, public NetworkGameSocketHandler, public TCPListenHandler<ServerNetworkGameSocketHandler, PACKET_SERVER_FULL, PACKET_SERVER_BANNED> { class ServerNetworkGameSocketHandler : public NetworkClientSocketPool::PoolItem<&_networkclientsocket_pool>, public NetworkGameSocketHandler, public TCPListenHandler<ServerNetworkGameSocketHandler, PACKET_SERVER_FULL, PACKET_SERVER_BANNED> {
protected: protected:
std::unique_ptr<class NetworkAuthenticationServerHandler> authentication_handler; ///< The handler for the authentication.
NetworkRecvStatus Receive_CLIENT_JOIN(Packet &p) override; NetworkRecvStatus Receive_CLIENT_JOIN(Packet &p) override;
NetworkRecvStatus Receive_CLIENT_IDENTIFY(Packet &p) override; NetworkRecvStatus Receive_CLIENT_IDENTIFY(Packet &p) override;
NetworkRecvStatus Receive_CLIENT_GAME_INFO(Packet &p) override; NetworkRecvStatus Receive_CLIENT_GAME_INFO(Packet &p) override;
NetworkRecvStatus Receive_CLIENT_GAME_PASSWORD(Packet &p) override; NetworkRecvStatus Receive_CLIENT_AUTH_RESPONSE(Packet &p) override;
NetworkRecvStatus Receive_CLIENT_COMPANY_PASSWORD(Packet &p) override; NetworkRecvStatus Receive_CLIENT_COMPANY_PASSWORD(Packet &p) override;
NetworkRecvStatus Receive_CLIENT_GETMAP(Packet &p) override; NetworkRecvStatus Receive_CLIENT_GETMAP(Packet &p) override;
NetworkRecvStatus Receive_CLIENT_MAP_OK(Packet &p) override; NetworkRecvStatus Receive_CLIENT_MAP_OK(Packet &p) override;
@ -44,16 +46,17 @@ protected:
NetworkRecvStatus SendGameInfo(); NetworkRecvStatus SendGameInfo();
NetworkRecvStatus SendNewGRFCheck(); NetworkRecvStatus SendNewGRFCheck();
NetworkRecvStatus SendWelcome(); NetworkRecvStatus SendWelcome();
NetworkRecvStatus SendNeedGamePassword(); NetworkRecvStatus SendAuthRequest();
NetworkRecvStatus SendAuthCompleted();
NetworkRecvStatus SendNeedCompanyPassword(); NetworkRecvStatus SendNeedCompanyPassword();
public: public:
/** Status of a client */ /** Status of a client */
enum ClientStatus { enum ClientStatus {
STATUS_INACTIVE, ///< The client is not connected nor active. STATUS_INACTIVE, ///< The client is not connected nor active.
STATUS_AUTH_GAME, ///< The client is authorizing with game (server) password.
STATUS_IDENTIFY, ///< The client is identifying itself. STATUS_IDENTIFY, ///< The client is identifying itself.
STATUS_NEWGRFS_CHECK, ///< The client is checking NewGRFs. STATUS_NEWGRFS_CHECK, ///< The client is checking NewGRFs.
STATUS_AUTH_GAME, ///< The client is authorizing with game (server) password.
STATUS_AUTH_COMPANY, ///< The client is authorizing with company password. STATUS_AUTH_COMPANY, ///< The client is authorizing with company password.
STATUS_AUTHORIZED, ///< The client is authorized. STATUS_AUTHORIZED, ///< The client is authorized.
STATUS_MAP_WAIT, ///< The client is waiting as someone else is downloading the map. STATUS_MAP_WAIT, ///< The client is waiting as someone else is downloading the map.

View File

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