Codechange: introduce allow list infrastructure for companies

This commit is contained in:
Rubidium 2024-03-23 11:53:08 +01:00 committed by rubidium42
parent 1250ce8fdc
commit 66354ab9eb
13 changed files with 63 additions and 8 deletions

View File

@ -294,6 +294,7 @@ enum Commands : uint16_t {
CMD_CREATE_SUBSIDY, ///< create a new subsidy
CMD_COMPANY_CTRL, ///< used in multiplayer to create a new companies etc.
CMD_COMPANY_ADD_ALLOW_LIST, ///< Used in multiplayer to add a client's public key to the company's allow list.
CMD_CUSTOM_NEWS_ITEM, ///< create a custom news message
CMD_CREATE_GOAL, ///< create a new goal
CMD_REMOVE_GOAL, ///< remove a goal

View File

@ -75,6 +75,8 @@ struct CompanyProperties {
uint32_t president_name_2; ///< Parameter of #president_name_1
std::string president_name; ///< Name of the president if the user changed it.
NetworkAuthorizedKeys allow_list; ///< Public keys of clients that are allowed to join this company.
CompanyManagerFace face; ///< Face description of the president.
Money money; ///< Money owned by the company.

View File

@ -980,6 +980,24 @@ CommandCost CmdCompanyCtrl(DoCommandFlag flags, CompanyCtrlAction cca, CompanyID
return CommandCost();
}
/**
* Add the given public key to the allow list of this company.
* @param flags Operation to perform.
* @param public_key The public key of the client to add.
* @return The cost of this operation or an error.
*/
CommandCost CmdCompanyAddAllowList(DoCommandFlag flags, const std::string &public_key)
{
if (flags & DC_EXEC) {
if (Company::Get(_current_company)->allow_list.Add(public_key)) {
InvalidateWindowData(WC_CLIENT_LIST, 0);
SetWindowDirty(WC_COMPANY, _current_company);
}
}
return CommandCost();
}
/**
* Change the company manager's face.
* @param flags operation to perform

View File

@ -18,6 +18,7 @@ enum ClientID : uint32_t;
enum Colours : uint8_t;
CommandCost CmdCompanyCtrl(DoCommandFlag flags, CompanyCtrlAction cca, CompanyID company_id, CompanyRemoveReason reason, ClientID client_id);
CommandCost CmdCompanyAddAllowList(DoCommandFlag flags, const std::string &public_key);
CommandCost CmdGiveMoney(DoCommandFlag flags, Money money, CompanyID dest_company);
CommandCost CmdRenameCompany(DoCommandFlag flags, const std::string &text);
CommandCost CmdRenamePresident(DoCommandFlag flags, const std::string &text);
@ -25,6 +26,7 @@ CommandCost CmdSetCompanyManagerFace(DoCommandFlag flags, CompanyManagerFace cmf
CommandCost CmdSetCompanyColour(DoCommandFlag flags, LiveryScheme scheme, bool primary, Colours colour);
DEF_CMD_TRAIT(CMD_COMPANY_CTRL, CmdCompanyCtrl, CMD_SPECTATOR | CMD_CLIENT_ID | CMD_NO_EST, CMDT_SERVER_SETTING)
DEF_CMD_TRAIT(CMD_COMPANY_ADD_ALLOW_LIST, CmdCompanyAddAllowList, CMD_NO_EST, CMDT_SERVER_SETTING)
DEF_CMD_TRAIT(CMD_GIVE_MONEY, CmdGiveMoney, 0, CMDT_MONEY_MANAGEMENT)
DEF_CMD_TRAIT(CMD_RENAME_COMPANY, CmdRenameCompany, 0, CMDT_OTHER_MANAGEMENT)
DEF_CMD_TRAIT(CMD_RENAME_PRESIDENT, CmdRenamePresident, 0, CMDT_OTHER_MANAGEMENT)

View File

@ -96,5 +96,10 @@ static const uint NETWORK_MAX_GRF_COUNT = 255;
* This is related to \c X25519_KEY_SIZE in the network crypto internals.
*/
static const uint NETWORK_SECRET_KEY_LENGTH = 32 * 2 + 1;
/**
* The maximum length of the hexadecimal encoded public keys, in bytes including '\0'.
* This is related to \c X25519_KEY_SIZE in the network crypto internals.
*/
static const uint NETWORK_PUBLIC_KEY_LENGTH = 32 * 2 + 1;
#endif /* NETWORK_CORE_CONFIG_H */

View File

@ -202,7 +202,8 @@ protected:
* Send information about a client:
* uint32_t ID of the client (always unique on a server. 1 = server, 0 is invalid).
* uint8_t ID of the company the client is playing as (255 for spectators).
* string Name of the client.
* string Name of the client.
* string Public key of the client.
* @param p The packet that was just received.
*/
virtual NetworkRecvStatus Receive_SERVER_CLIENT_INFO(Packet &p);

View File

@ -165,10 +165,12 @@ bool NetworkAuthorizedKeys::Contains(std::string_view key) const
/**
* Add the given key to the authorized keys, when it is not already contained.
* @param key The key to add.
* @return \c true when the key was added, \c false when the key already existed.
* @return \c true when the key was added, \c false when the key already existed or the key was empty.
*/
bool NetworkAuthorizedKeys::Add(std::string_view key)
{
if (key.empty()) return false;
auto iter = FindKey(this, key);
if (iter != this->end()) return false;

View File

@ -24,6 +24,7 @@ extern NetworkClientInfoPool _networkclientinfo_pool;
struct NetworkClientInfo : NetworkClientInfoPool::PoolItem<&_networkclientinfo_pool> {
ClientID client_id; ///< Client identifier (same as ClientState->client_id)
std::string client_name; ///< Name of the client
std::string public_key; ///< The public key of the client.
CompanyID client_playas; ///< As which company is this client playing (CompanyID)
TimerGameEconomy::Date join_date; ///< Gamedate the client has joined

View File

@ -608,6 +608,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_CLIENT_INFO(Pac
Debug(net, 9, "Client::Receive_SERVER_CLIENT_INFO(): client_id={}, playas={}", client_id, playas);
std::string name = p.Recv_string(NETWORK_NAME_LENGTH);
std::string public_key = p.Recv_string(NETWORK_PUBLIC_KEY_LENGTH);
if (this->status < STATUS_AUTHORIZED) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
if (this->HasClientQuit()) return NETWORK_RECV_STATUS_CLIENT_QUIT;
@ -632,6 +633,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_CLIENT_INFO(Pac
ci->client_playas = playas;
ci->client_name = name;
ci->public_key = public_key;
InvalidateWindowData(WC_CLIENT_LIST, 0);
@ -651,6 +653,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_CLIENT_INFO(Pac
if (client_id == _network_own_client_id) this->SetInfo(ci);
ci->client_name = name;
ci->public_key = public_key;
InvalidateWindowData(WC_CLIENT_LIST, 0);

View File

@ -332,6 +332,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendClientInfo(NetworkClientIn
p->Send_uint32(ci->client_id);
p->Send_uint8 (ci->client_playas);
p->Send_string(ci->client_name);
p->Send_string(ci->public_key);
this->SendPacket(std::move(p));
}
@ -959,6 +960,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_IDENTIFY(Packet
ci->join_date = TimerGameEconomy::date;
ci->client_name = client_name;
ci->client_playas = playas;
ci->public_key = this->peer_public_key;
Debug(desync, 1, "client: {:08x}; {:02x}; {:02x}; {:02x}", TimerGameEconomy::date, TimerGameEconomy::date_fract, (int)ci->client_playas, (int)ci->index);
/* Make sure companies to which people try to join are not autocleaned */
@ -1177,6 +1179,23 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_COMMAND(Packet
}
}
if (cp.cmd == CMD_COMPANY_ADD_ALLOW_LIST) {
/* Maybe the client just got moved before allowing? */
if (ci->client_id != CLIENT_ID_SERVER && ci->client_playas != cp.company) return NETWORK_RECV_STATUS_OKAY;
std::string public_key = std::get<0>(EndianBufferReader::ToValue<CommandTraits<CMD_COMPANY_ADD_ALLOW_LIST>::Args>(cp.data));
bool found = false;
for (const NetworkClientInfo *info : NetworkClientInfo::Iterate()) {
if (info->public_key == public_key) {
found = true;
break;
}
}
/* Maybe the client just left? */
if (!found) return NETWORK_RECV_STATUS_OKAY;
}
if (GetCommandFlags(cp.cmd) & CMD_CLIENT_ID) NetworkReplaceCommandClientId(cp, this->client_id);
this->incoming_queue.push_back(cp);
@ -1541,7 +1560,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_MOVE(Packet &p)
if (company_id != COMPANY_SPECTATOR && !Company::IsValidHumanID(company_id)) return NETWORK_RECV_STATUS_OKAY;
/* Check if we require a password for this company */
if (company_id != COMPANY_SPECTATOR && !_network_company_states[company_id].password.empty()) {
if (company_id != COMPANY_SPECTATOR && !Company::Get(company_id)->allow_list.Contains(this->peer_public_key) && !_network_company_states[company_id].password.empty()) {
/* we need a password from the client - should be in this packet */
std::string password = p.Recv_string(NETWORK_PASSWORD_LENGTH);
@ -2275,13 +2294,9 @@ void NetworkServerNewCompany(const Company *c, NetworkClientInfo *ci)
/* ci is nullptr when replaying, or for AIs. In neither case there is a client. */
ci->client_playas = c->index;
NetworkUpdateClientInfo(ci->client_id);
Command<CMD_COMPANY_ADD_ALLOW_LIST>::SendNet(STR_NULL, c->index, ci->public_key);
Command<CMD_RENAME_PRESIDENT>::SendNet(STR_NULL, c->index, ci->client_name);
}
if (ci != nullptr) {
/* ci is nullptr when replaying, or for AIs. In neither case there is a client.
We need to send Admin port update here so that they first know about the new company
and then learn about a possibly joining client (see FS#6025) */
NetworkServerSendChat(NETWORK_ACTION_COMPANY_NEW, DESTTYPE_BROADCAST, 0, "", ci->client_id, c->index + 1);
}
}

View File

@ -450,6 +450,8 @@ static const SaveLoad _company_desc[] = {
SLE_VAR(CompanyProperties, president_name_2, SLE_UINT32),
SLE_CONDSSTR(CompanyProperties, president_name, SLE_STR | SLF_ALLOW_CONTROL, SLV_84, SL_MAX_VERSION),
SLE_CONDVECTOR(CompanyProperties, allow_list, SLE_STR, SLV_COMPANY_ALLOW_LIST, SL_MAX_VERSION),
SLE_VAR(CompanyProperties, face, SLE_UINT32),
/* money was changed to a 64 bit field in savegame version 1. */

View File

@ -379,6 +379,8 @@ enum SaveLoadVersion : uint16_t {
SLV_SCRIPT_RANDOMIZER, ///< 333 PR#12063 v14.0-RC1 Save script randomizers.
SLV_VEHICLE_ECONOMY_AGE, ///< 334 PR#12141 v14.0 Add vehicle age in economy year, for profit stats minimum age
SLV_COMPANY_ALLOW_LIST, ///< 335 PR#12337 Saving of list of client keys that are allowed to join this company.
SL_MAX_VERSION, ///< Highest possible saveload version
};

View File

@ -18,6 +18,7 @@
/* The length of the hexadecimal representation of a X25519 key must fit in the key length. */
static_assert(NETWORK_SECRET_KEY_LENGTH >= X25519_KEY_SIZE * 2 + 1);
static_assert(NETWORK_PUBLIC_KEY_LENGTH >= X25519_KEY_SIZE * 2 + 1);
class MockNetworkSocketHandler : public NetworkSocketHandler {
public: