diff --git a/src/lang/english.txt b/src/lang/english.txt index 6832c13636..3fff8c6939 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -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_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_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_BANNED :{WHITE}You are banned from this server 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 :{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_DESYNC :desync error 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_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_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 diff --git a/src/network/core/tcp_game.cpp b/src/network/core/tcp_game.cpp index f1b4b05e2a..9e179362bc 100644 --- a/src/network/core/tcp_game.cpp +++ b/src/network/core/tcp_game.cpp @@ -82,9 +82,10 @@ NetworkRecvStatus NetworkGameSocketHandler::HandlePacket(Packet &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_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_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_SERVER_WELCOME: return this->Receive_SERVER_WELCOME(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_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_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_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_SERVER_WELCOME(Packet &) { return this->ReceiveInvalidPacket(PACKET_SERVER_WELCOME); } NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_GETMAP(Packet &) { return this->ReceiveInvalidPacket(PACKET_CLIENT_GETMAP); } diff --git a/src/network/core/tcp_game.h b/src/network/core/tcp_game.h index 14310f0017..2e5f33dfdb 100644 --- a/src/network/core/tcp_game.h +++ b/src/network/core/tcp_game.h @@ -15,6 +15,7 @@ #include "os_abstraction.h" #include "tcp.h" #include "../network_type.h" +#include "../network_crypto.h" #include "../../core/pool_type.hpp" #include @@ -56,16 +57,19 @@ enum PacketGameType : uint8_t { * 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. /* 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_CLIENT_NEWGRFS_CHECKED, ///< Client acknowledges that it has all required NewGRFs. - /* Checking the game, and then company passwords. */ - PACKET_SERVER_NEED_GAME_PASSWORD, ///< Server requests the (hashed) game password. - PACKET_CLIENT_GAME_PASSWORD, ///< Clients sends the (hashed) game password. + /* Checking the company passwords. */ PACKET_SERVER_NEED_COMPANY_PASSWORD, ///< Server requests 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); /** - * 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. */ - 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: @@ -228,12 +235,19 @@ protected: virtual NetworkRecvStatus Receive_SERVER_NEED_COMPANY_PASSWORD(Packet &p); /** - * Send a password to the server to authorize: - * uint8_t Password type (see NetworkPasswordType). - * string The password. + * Send the response to the authentication request: + * 32 * uint8_t Public key of the client. + * 8 * uint8_t Random message that got encoded and signed. + * 16 * uint8_t Message authentication code. * @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 diff --git a/src/network/network.cpp b/src/network/network.cpp index 9d47c2f964..784c5a8f37 100644 --- a/src/network/network.cpp +++ b/src/network/network.cpp @@ -321,6 +321,7 @@ StringID GetNetworkErrorMsg(NetworkErrorCode err) STR_NETWORK_ERROR_CLIENT_TIMEOUT_MAP, STR_NETWORK_ERROR_CLIENT_TIMEOUT_JOIN, STR_NETWORK_ERROR_CLIENT_INVALID_CLIENT_NAME, + STR_NETWORK_ERROR_CLIENT_NOT_ON_ALLOW_LIST, }; static_assert(lengthof(network_error_strings) == NETWORK_ERROR_END); diff --git a/src/network/network_client.cpp b/src/network/network_client.cpp index 43eb46cb5c..f3fd5fe6f0 100644 --- a/src/network/network_client.cpp +++ b/src/network/network_client.cpp @@ -349,7 +349,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendJoin() p->Send_uint32(_openttd_newgrf_version); my_client->SendPacket(std::move(p)); - return ClientNetworkGameSocketHandler::SendIdentify(); + return NETWORK_RECV_STATUS_OKAY; } NetworkRecvStatus ClientNetworkGameSocketHandler::SendIdentify() @@ -377,13 +377,14 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendNewGRFsOk() * Set the game password as requested. * @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(my_client, PACKET_CLIENT_GAME_PASSWORD); - p->Send_string(password); + auto p = std::make_unique(my_client, PACKET_CLIENT_AUTH_RESPONSE); + my_client->authentication_handler->SendResponse(*p); my_client->SendPacket(std::move(p)); + 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_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 }; 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) { - 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(); NetworkRecvStatus ret = NETWORK_RECV_STATUS_OKAY; @@ -736,26 +738,67 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_CHECK_NEWGRFS(P 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 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"); 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()) { - return SendGamePassword(_network_join.server_password); + if (this->authentication_handler == nullptr) { + this->authentication_handler = NetworkAuthenticationClientHandler::Create(std::make_shared(), + _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) { - 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"); this->status = STATUS_AUTH_COMPANY; @@ -769,14 +812,14 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_NEED_COMPANY_PA return SendCompanyPassword(_network_join.company_password); } - ShowNetworkNeedPassword(NETWORK_COMPANY_PASSWORD); + ShowNetworkNeedPassword(NETWORK_COMPANY_PASSWORD, std::make_shared()); return NETWORK_RECV_STATUS_OKAY; } 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"); this->status = STATUS_AUTHORIZED; diff --git a/src/network/network_client.h b/src/network/network_client.h index f6c0eb8a61..ac80393651 100644 --- a/src/network/network_client.h +++ b/src/network/network_client.h @@ -15,6 +15,7 @@ /** Class for handling the client side of the game connection. */ class ClientNetworkGameSocketHandler : public ZeroedMemoryAllocator, public NetworkGameSocketHandler { private: + std::unique_ptr authentication_handler; ///< The handler for the authentication. std::string connection_string; ///< Address we are connected to. std::shared_ptr 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. @@ -23,8 +24,9 @@ private: enum ServerStatus { STATUS_INACTIVE, ///< The client is not connected nor active. 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_AUTHENTICATED, ///< The game authentication has completed. + STATUS_NEWGRFS_CHECK, ///< Last action was checking NewGRFs. STATUS_AUTH_COMPANY, ///< Last action was requesting company password. STATUS_AUTHORIZED, ///< The client is authorized at the server. 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_ERROR(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_WELCOME(Packet &p) override; NetworkRecvStatus Receive_SERVER_WAIT(Packet &p) override; @@ -86,7 +89,7 @@ public: static NetworkRecvStatus SendQuit(); static NetworkRecvStatus SendAck(); - static NetworkRecvStatus SendGamePassword(const std::string &password); + static NetworkRecvStatus SendAuthResponse(); static NetworkRecvStatus SendCompanyPassword(const std::string &password); static NetworkRecvStatus SendChat(NetworkAction action, DestType type, int dest, const std::string &msg, int64_t data); diff --git a/src/network/network_gui.cpp b/src/network/network_gui.cpp index 4c21dec535..29b6c18433 100644 --- a/src/network/network_gui.cpp +++ b/src/network/network_gui.cpp @@ -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. struct NetworkJoinStatusWindow : Window { - NetworkPasswordType password_type; + std::shared_ptr request; NetworkJoinStatusWindow(WindowDesc *desc) : Window(desc) { @@ -2199,16 +2199,12 @@ struct NetworkJoinStatusWindow : Window { void OnQueryTextFinished(char *str) override { - if (StrEmpty(str)) { + if (StrEmpty(str) || this->request == nullptr) { NetworkDisconnect(); return; } - switch (this->password_type) { - case NETWORK_GAME_PASSWORD: MyClient::SendGamePassword (str); break; - case NETWORK_COMPANY_PASSWORD: MyClient::SendCompanyPassword(str); break; - default: NOT_REACHED(); - } + this->request->Reply(str); } }; @@ -2236,11 +2232,11 @@ void ShowJoinStatusWindow() new NetworkJoinStatusWindow(&_network_join_status_window_desc); } -void ShowNetworkNeedPassword(NetworkPasswordType npt) +void ShowNetworkNeedPassword(NetworkPasswordType npt, std::shared_ptr request) { NetworkJoinStatusWindow *w = (NetworkJoinStatusWindow *)FindWindowById(WC_NETWORK_STATUS_WINDOW, WN_NETWORK_STATUS_WINDOW_JOIN); if (w == nullptr) return; - w->password_type = npt; + w->request = request; StringID caption; switch (npt) { diff --git a/src/network/network_gui.h b/src/network/network_gui.h index 74d6d153e6..02283e9ae7 100644 --- a/src/network/network_gui.h +++ b/src/network/network_gui.h @@ -17,7 +17,7 @@ #include "network_type.h" #include "network_gamelist.h" -void ShowNetworkNeedPassword(NetworkPasswordType npt); +void ShowNetworkNeedPassword(NetworkPasswordType npt, std::shared_ptr request); void ShowNetworkChatQueryWindow(DestType type, int dest); void ShowJoinStatusWindow(); void ShowNetworkGameWindow(); diff --git a/src/network/network_server.cpp b/src/network/network_server.cpp index eb3a1e3b18..6c7d4192ee 100644 --- a/src/network/network_server.cpp +++ b/src/network/network_server.cpp @@ -58,6 +58,10 @@ INSTANTIATE_POOL_METHODS(NetworkClientSocket) /** Instantiate the listen sockets. */ template SocketList TCPListenHandler::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. */ struct PacketWriter : SaveFilter { ServerNetworkGameSocketHandler *cs; ///< Socket we are associated with. @@ -407,8 +411,8 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendNewGRFCheck() this->status = STATUS_NEWGRFS_CHECK; if (_grfconfig == nullptr) { - /* There are no NewGRFs, continue with the game password. */ - return this->SendNeedGamePassword(); + /* There are no NewGRFs, continue with the company password. */ + return this->SendNeedCompanyPassword(); } auto p = std::make_unique(this, PACKET_SERVER_CHECK_NEWGRFS, TCP_MTU); @@ -429,25 +433,39 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendNewGRFCheck() } /** 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. */ - if (this->status != STATUS_NEWGRFS_CHECK) return this->CloseConnection(NETWORK_RECV_STATUS_MALFORMED_PACKET); + /* Invalid packet when status is anything but STATUS_INACTIVE or STATUS_AUTH_GAME. */ + 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); 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 */ this->last_frame = this->last_frame_server = _frame_counter; - auto p = std::make_unique(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(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(this, PACKET_SERVER_AUTH_COMPLETED); this->SendPacket(std::move(p)); return NETWORK_RECV_STATUS_OKAY; } @@ -457,8 +475,8 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendNeedCompanyPassword() { Debug(net, 9, "client[{}] SendNeedCompanyPassword()", 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); + /* Invalid packet when status is anything but STATUS_NEWGRFS_CHECK. */ + if (this->status != STATUS_NEWGRFS_CHECK) return this->CloseConnection(NETWORK_RECV_STATUS_MALFORMED_PACKET); Debug(net, 9, "client[{}] status = AUTH_COMPANY", this->client_id); 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); - return this->SendNeedGamePassword(); + return this->SendNeedCompanyPassword(); } 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); } - 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; + return this->SendAuthRequest(); } NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_IDENTIFY(Packet &p) @@ -953,24 +965,52 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_IDENTIFY(Packet 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) { 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 */ - if (!_settings_client.network.server_password.empty() && - _settings_client.network.server_password.compare(password) != 0) { - /* Password is invalid */ - return this->SendError(NETWORK_ERROR_WRONG_PASSWORD); + case NetworkAuthenticationServerHandler::RETRY_NEXT_METHOD: + return this->SendAuthRequest(); + + case NetworkAuthenticationServerHandler::NOT_AUTHENTICATED: + 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) @@ -1978,9 +2018,9 @@ void NetworkServerShowStatusToConsole() { static const char * const stat_str[] = { "inactive", + "authorizing (server password)", "identifing client", "checking NewGRFs", - "authorizing (server password)", "authorizing (company password)", "authorized", "waiting", diff --git a/src/network/network_server.h b/src/network/network_server.h index f4ce50842a..b0e21f26f6 100644 --- a/src/network/network_server.h +++ b/src/network/network_server.h @@ -23,10 +23,12 @@ extern NetworkClientSocketPool _networkclientsocket_pool; /** Class for handling the server side of the game connection. */ class ServerNetworkGameSocketHandler : public NetworkClientSocketPool::PoolItem<&_networkclientsocket_pool>, public NetworkGameSocketHandler, public TCPListenHandler { protected: + std::unique_ptr authentication_handler; ///< The handler for the authentication. + NetworkRecvStatus Receive_CLIENT_JOIN(Packet &p) override; NetworkRecvStatus Receive_CLIENT_IDENTIFY(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_GETMAP(Packet &p) override; NetworkRecvStatus Receive_CLIENT_MAP_OK(Packet &p) override; @@ -44,16 +46,17 @@ protected: NetworkRecvStatus SendGameInfo(); NetworkRecvStatus SendNewGRFCheck(); NetworkRecvStatus SendWelcome(); - NetworkRecvStatus SendNeedGamePassword(); + NetworkRecvStatus SendAuthRequest(); + NetworkRecvStatus SendAuthCompleted(); NetworkRecvStatus SendNeedCompanyPassword(); public: /** Status of a client */ enum ClientStatus { 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_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_AUTHORIZED, ///< The client is authorized. STATUS_MAP_WAIT, ///< The client is waiting as someone else is downloading the map. diff --git a/src/network/network_type.h b/src/network/network_type.h index 40a990d472..faa81a8d35 100644 --- a/src/network/network_type.h +++ b/src/network/network_type.h @@ -145,6 +145,7 @@ enum NetworkErrorCode { NETWORK_ERROR_TIMEOUT_MAP, NETWORK_ERROR_TIMEOUT_JOIN, NETWORK_ERROR_INVALID_CLIENT_NAME, + NETWORK_ERROR_NOT_ON_ALLOW_LIST, NETWORK_ERROR_END, };