Merge remote-tracking branch 'upstream/master'

This commit is contained in:
dP
2025-05-14 18:41:13 +05:00
994 changed files with 38753 additions and 34512 deletions
+3
View File
@@ -16,6 +16,9 @@ add_files(
network_content_gui.h
network_coordinator.cpp
network_coordinator.h
network_crypto.cpp
network_crypto.h
network_crypto_internal.h
network_func.h
network_gamelist.cpp
network_gamelist.h
+16 -7
View File
@@ -45,18 +45,16 @@ static const std::string NETWORK_SURVEY_DETAILS_LINK = "https://survey.openttd.o
static const size_t TCP_MTU = 32767; ///< Number of bytes we can pack in a single TCP packet
static const size_t COMPAT_MTU = 1460; ///< Number of bytes we can pack in a single packet for backward compatibility
static const byte NETWORK_GAME_ADMIN_VERSION = 3; ///< What version of the admin network do we use?
static const byte NETWORK_GAME_INFO_VERSION = 7; ///< What version of game-info do we use?
static const byte NETWORK_COORDINATOR_VERSION = 6; ///< What version of game-coordinator-protocol do we use?
static const byte NETWORK_SURVEY_VERSION = 2; ///< What version of the survey do we use?
static const uint8_t NETWORK_GAME_ADMIN_VERSION = 3; ///< What version of the admin network do we use?
static const uint8_t NETWORK_GAME_INFO_VERSION = 7; ///< What version of game-info do we use?
static const uint8_t NETWORK_COORDINATOR_VERSION = 6; ///< What version of game-coordinator-protocol do we use?
static const uint8_t NETWORK_SURVEY_VERSION = 2; ///< What version of the survey do we use?
static const uint NETWORK_NAME_LENGTH = 80; ///< The maximum length of the server name and map name, in bytes including '\0'
static const uint NETWORK_COMPANY_NAME_LENGTH = 128; ///< The maximum length of the company name, in bytes including '\0'
static const uint NETWORK_HOSTNAME_LENGTH = 80; ///< The maximum length of the host name, in bytes including '\0'
static const uint NETWORK_HOSTNAME_PORT_LENGTH = 80 + 6; ///< The maximum length of the host name + port, in bytes including '\0'. The extra six is ":" + port number (with a max of 65536)
static const uint NETWORK_SERVER_ID_LENGTH = 33; ///< The maximum length of the network id of the servers, in bytes including '\0'
static const uint NETWORK_REVISION_LENGTH = 33; ///< The maximum length of the revision, in bytes including '\0'
static const uint NETWORK_PASSWORD_LENGTH = 33; ///< The maximum length of the password, in bytes including '\0' (must be >= NETWORK_SERVER_ID_LENGTH)
static const uint NETWORK_PASSWORD_LENGTH = 33; ///< The maximum length of the password, in bytes including '\0'
static const uint NETWORK_CLIENT_NAME_LENGTH = 25; ///< The maximum length of a client's name, in bytes including '\0'
static const uint NETWORK_RCONCOMMAND_LENGTH = 500; ///< The maximum length of a rconsole command, in bytes including '\0'
static const uint NETWORK_GAMESCRIPT_JSON_LENGTH = 9000; ///< The maximum length of a receiving gamescript json string, in bytes including '\0'.
@@ -91,4 +89,15 @@ static const uint NETWORK_GRF_NAME_LENGTH = 80; ///< Maxim
*/
static const uint NETWORK_MAX_GRF_COUNT = 255;
/**
* The maximum length of the hexadecimal encoded secret keys, in bytes including '\0'.
* 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 */
+6
View File
@@ -13,6 +13,7 @@
#define NETWORK_CORE_CORE_H
#include "../../newgrf_config.h"
#include "../network_crypto.h"
#include "config.h"
bool NetworkCoreInitialize();
@@ -43,6 +44,11 @@ class NetworkSocketHandler {
private:
bool has_quit; ///< Whether the current client has quit/send a bad packet
protected:
friend struct Packet;
std::unique_ptr<class NetworkEncryptionHandler> receive_encryption_handler; ///< The handler for decrypting received packets.
std::unique_ptr<class NetworkEncryptionHandler> send_encryption_handler; ///< The handler for encrypting sent packets.
public:
/** Create a new unbound socket */
NetworkSocketHandler() { this->has_quit = false; }
+11 -11
View File
@@ -78,7 +78,7 @@ static std::string GetLastErrorAsString()
DWORD error_code = GetLastError();
if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS, GetModuleHandle(L"winhttp.dll"), error_code,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buffer, lengthof(buffer), nullptr) == 0) {
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buffer, static_cast<DWORD>(std::size(buffer)), nullptr) == 0) {
return fmt::format("unknown error {}", error_code);
}
@@ -121,8 +121,8 @@ void NetworkHTTPRequest::WinHttpCallback(DWORD code, void *info, DWORD length)
/* Make sure we are not in a redirect loop. */
if (this->depth++ > 5) {
Debug(net, 0, "HTTP request failed: too many redirects");
this->finished = true;
this->callback.OnFailure();
this->finished = true;
return;
}
break;
@@ -144,8 +144,8 @@ void NetworkHTTPRequest::WinHttpCallback(DWORD code, void *info, DWORD length)
if (status_code >= 400) {
/* No need to be verbose about rate limiting. */
Debug(net, status_code == HTTP_429_TOO_MANY_REQUESTS ? 1 : 0, "HTTP request failed: status-code {}", status_code);
this->finished = true;
this->callback.OnFailure();
this->finished = true;
return;
}
@@ -183,14 +183,14 @@ void NetworkHTTPRequest::WinHttpCallback(DWORD code, void *info, DWORD length)
case WINHTTP_CALLBACK_STATUS_SECURE_FAILURE:
case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR:
Debug(net, 0, "HTTP request failed: {}", GetLastErrorAsString());
this->finished = true;
this->callback.OnFailure();
this->finished = true;
break;
default:
Debug(net, 0, "HTTP request failed: unexepected callback code 0x{:x}", code);
this->finished = true;
this->callback.OnFailure();
this->finished = true;
return;
}
}
@@ -222,19 +222,19 @@ void NetworkHTTPRequest::Connect()
/* Convert the URL to its components. */
url_components.dwStructSize = sizeof(url_components);
url_components.lpszScheme = scheme;
url_components.dwSchemeLength = lengthof(scheme);
url_components.dwSchemeLength = static_cast<DWORD>(std::size(scheme));
url_components.lpszHostName = hostname;
url_components.dwHostNameLength = lengthof(hostname);
url_components.dwHostNameLength = static_cast<DWORD>(std::size(hostname));
url_components.lpszUrlPath = url_path;
url_components.dwUrlPathLength = lengthof(url_path);
url_components.dwUrlPathLength = static_cast<DWORD>(std::size(url_path));
WinHttpCrackUrl(this->uri.c_str(), 0, 0, &url_components);
/* Create the HTTP connection. */
this->connection = WinHttpConnect(_winhttp_session, url_components.lpszHostName, url_components.nPort, 0);
if (this->connection == nullptr) {
Debug(net, 0, "HTTP request failed: {}", GetLastErrorAsString());
this->finished = true;
this->callback.OnFailure();
this->finished = true;
return;
}
@@ -243,8 +243,8 @@ void NetworkHTTPRequest::Connect()
WinHttpCloseHandle(this->connection);
Debug(net, 0, "HTTP request failed: {}", GetLastErrorAsString());
this->finished = true;
this->callback.OnFailure();
this->finished = true;
return;
}
@@ -267,8 +267,8 @@ bool NetworkHTTPRequest::Receive()
{
if (this->callback.cancelled && !this->finished) {
Debug(net, 1, "HTTP request failed: cancelled by user");
this->finished = true;
this->callback.OnFailure();
this->finished = true;
/* Fall-through, as we are waiting for IsQueueEmpty() to happen. */
}
+24 -11
View File
@@ -24,6 +24,7 @@
#include "../../rev.h"
#include "../network_func.h"
#include "../network.h"
#include "../network_internal.h"
#include "packet.h"
#include "../../safeguards.h"
@@ -46,7 +47,18 @@ std::string_view GetNetworkRevisionString()
static std::string network_revision;
if (network_revision.empty()) {
#if not defined(NETWORK_INTERNAL_H)
# error("network_internal.h must be included, otherwise the debug related preprocessor tokens won't be picked up correctly.")
#elif not defined(ENABLE_NETWORK_SYNC_EVERY_FRAME)
/* Just a standard build. */
network_revision = _openttd_revision;
#elif defined(NETWORK_SEND_DOUBLE_SEED)
/* Build for debugging that sends both parts of the seeds and by doing that practically syncs every frame. */
network_revision = fmt::format("dbg_seed-{}", _openttd_revision);
#else
/* Build for debugging that sends the first part of the seed every frame, practically syncing every frame. */
network_revision = fmt::format("dbg_sync-{}", _openttd_revision);
#endif
if (_openttd_revision_tagged) {
/* Tagged; do not mangle further, though ensure it's not too long. */
if (network_revision.size() >= NETWORK_REVISION_LENGTH) network_revision.resize(NETWORK_REVISION_LENGTH - 1);
@@ -90,14 +102,19 @@ static std::string_view ExtractNetworkRevisionHash(std::string_view revision_str
*/
bool IsNetworkCompatibleVersion(std::string_view other)
{
if (GetNetworkRevisionString() == other) return true;
std::string_view our_revision = GetNetworkRevisionString();
if (our_revision == other) return true;
/* If this version is tagged, then the revision string must be a complete match,
* since there is no git hash suffix in it.
* This is needed to avoid situations like "1.9.0-beta1" comparing equal to "2.0.0-beta1". */
if (_openttd_revision_tagged) return false;
std::string_view hash1 = ExtractNetworkRevisionHash(GetNetworkRevisionString());
/* One of the versions is for some sort of debugging, but not both. */
if (other.starts_with("dbg_seed") != our_revision.starts_with("dbg_seed")) return false;
if (other.starts_with("dbg_sync") != our_revision.starts_with("dbg_sync")) return false;
std::string_view hash1 = ExtractNetworkRevisionHash(our_revision);
std::string_view hash2 = ExtractNetworkRevisionHash(other);
return hash1 == hash2;
}
@@ -148,7 +165,7 @@ const NetworkServerGameInfo &GetCurrentNetworkServerGameInfo()
* - invite_code
* These don't need to be updated manually here.
*/
_network_game_info.companies_on = (byte)Company::GetNumItems();
_network_game_info.companies_on = (uint8_t)Company::GetNumItems();
_network_game_info.spectators_on = NetworkSpectatorCount();
_network_game_info.calendar_date = TimerGameCalendar::date;
_network_game_info.ticks_playing = TimerGameTick::counter;
@@ -163,7 +180,7 @@ const NetworkServerGameInfo &GetCurrentNetworkServerGameInfo()
* @param config The GRF to handle.
* @param name The name of the NewGRF, empty when unknown.
*/
static void HandleIncomingNetworkGameInfoGRFConfig(GRFConfig *config, std::string name)
static void HandleIncomingNetworkGameInfoGRFConfig(GRFConfig *config, std::string_view name)
{
/* Find the matching GRF file */
const GRFConfig *f = FindGRFConfig(config->ident.grfid, FGCM_EXACT, &config->ident.md5sum);
@@ -260,7 +277,7 @@ void SerializeNetworkGameInfo(Packet &p, const NetworkServerGameInfo &info, bool
*/
void DeserializeNetworkGameInfo(Packet &p, NetworkGameInfo &info, const GameInfoNewGRFLookupTable *newgrf_lookup_table)
{
byte game_info_version = p.Recv_uint8();
uint8_t game_info_version = p.Recv_uint8();
NewGRFSerializationType newgrf_serialisation = NST_GRFID_MD5;
/*
@@ -375,9 +392,7 @@ void DeserializeNetworkGameInfo(Packet &p, NetworkGameInfo &info, const GameInfo
void SerializeGRFIdentifier(Packet &p, const GRFIdentifier &grf)
{
p.Send_uint32(grf.grfid);
for (size_t j = 0; j < grf.md5sum.size(); j++) {
p.Send_uint8(grf.md5sum[j]);
}
p.Send_bytes(grf.md5sum);
}
/**
@@ -388,9 +403,7 @@ void SerializeGRFIdentifier(Packet &p, const GRFIdentifier &grf)
void DeserializeGRFIdentifier(Packet &p, GRFIdentifier &grf)
{
grf.grfid = p.Recv_uint32();
for (size_t j = 0; j < grf.md5sum.size(); j++) {
grf.md5sum[j] = p.Recv_uint8();
}
p.Recv_bytes(grf.md5sum);
}
/**
+6 -6
View File
@@ -104,12 +104,12 @@ struct NetworkServerGameInfo {
std::string server_revision; ///< The version number the server is using (e.g.: 'r304' or 0.5.0)
bool dedicated; ///< Is this a dedicated server?
bool use_password; ///< Is this server passworded?
byte clients_on; ///< Current count of clients on server
byte clients_max; ///< Max clients allowed on server
byte companies_on; ///< How many started companies do we have
byte companies_max; ///< Max companies allowed on server
byte spectators_on; ///< How many spectators do we have?
byte landscape; ///< The used landscape
uint8_t clients_on; ///< Current count of clients on server
uint8_t clients_max; ///< Max clients allowed on server
uint8_t companies_on; ///< How many started companies do we have
uint8_t companies_max; ///< Max companies allowed on server
uint8_t spectators_on; ///< How many spectators do we have?
uint8_t landscape; ///< The used landscape
int gamescript_version; ///< Version of the gamescript.
std::string gamescript_name; ///< Name of the gamescript.
};
+1 -1
View File
@@ -83,7 +83,7 @@ const std::string &NetworkError::AsString() const
#if defined(_WIN32)
wchar_t buffer[512];
if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, this->error,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buffer, lengthof(buffer), nullptr) == 0) {
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buffer, static_cast<DWORD>(std::size(buffer)), nullptr) == 0) {
this->message.assign(fmt::format("Unknown error {}", this->error));
} else {
this->message.assign(FS2OTTD(buffer));
+66 -24
View File
@@ -38,16 +38,24 @@ Packet::Packet(NetworkSocketHandler *cs, size_t limit, size_t initial_read_size)
/**
* Creates a packet to send
* @param cs The socket handler associated with the socket we are writing to; could be \c nullptr.
* @param type The type of the packet to send
* @param limit The maximum number of bytes the packet may have. Default is COMPAT_MTU.
* Be careful of compatibility with older clients/servers when changing
* the limit as it might break things if the other side is not expecting
* much larger packets than what they support.
*/
Packet::Packet(PacketType type, size_t limit) : pos(0), limit(limit), cs(nullptr)
Packet::Packet(NetworkSocketHandler *cs, PacketType type, size_t limit) : pos(0), limit(limit), cs(cs)
{
/* Allocate space for the the size so we can write that in just before sending the packet. */
this->Send_uint16(0);
size_t size = EncodedLengthOfPacketSize();
if (cs != nullptr && cs->send_encryption_handler != nullptr) {
/* Allocate some space for the message authentication code of the encryption. */
size += cs->send_encryption_handler->MACSize();
}
assert(this->CanWriteToPacket(size));
this->buffer.resize(size, 0);
this->Send_uint8(type);
}
@@ -57,11 +65,19 @@ Packet::Packet(PacketType type, size_t limit) : pos(0), limit(limit), cs(nullptr
*/
void Packet::PrepareToSend()
{
assert(this->cs == nullptr);
/* Prevent this to be called twice and for packets that have been received. */
assert(this->buffer[0] == 0 && this->buffer[1] == 0);
this->buffer[0] = GB(this->Size(), 0, 8);
this->buffer[1] = GB(this->Size(), 8, 8);
if (cs != nullptr && cs->send_encryption_handler != nullptr) {
size_t offset = EncodedLengthOfPacketSize();
size_t mac_size = cs->send_encryption_handler->MACSize();
size_t message_offset = offset + mac_size;
cs->send_encryption_handler->Encrypt(std::span(&this->buffer[offset], mac_size), std::span(&this->buffer[message_offset], this->buffer.size() - message_offset));
}
this->pos = 0; // We start reading from here
this->buffer.shrink_to_fit();
}
@@ -164,7 +180,7 @@ void Packet::Send_string(const std::string_view data)
* Copy a sized byte buffer into the packet.
* @param data The data to send.
*/
void Packet::Send_buffer(const std::vector<byte> &data)
void Packet::Send_buffer(const std::vector<uint8_t> &data)
{
assert(this->CanWriteToPacket(sizeof(uint16_t) + data.size()));
this->Send_uint16((uint16_t)data.size());
@@ -174,16 +190,15 @@ void Packet::Send_buffer(const std::vector<byte> &data)
/**
* Send as many of the bytes as possible in the packet. This can mean
* that it is possible that not all bytes are sent. To cope with this
* the function returns the amount of bytes that were actually sent.
* @param begin The begin of the buffer to send.
* @param end The end of the buffer to send.
* @return The number of bytes that were added to this packet.
* the function returns the span of bytes that were not sent.
* @param span The span describing the range of bytes to send.
* @return The span of bytes that were not written.
*/
size_t Packet::Send_bytes(const byte *begin, const byte *end)
std::span<const uint8_t> Packet::Send_bytes(const std::span<const uint8_t> span)
{
size_t amount = std::min<size_t>(end - begin, this->limit - this->Size());
this->buffer.insert(this->buffer.end(), begin, begin + amount);
return amount;
size_t amount = std::min<size_t>(span.size(), this->limit - this->Size());
this->buffer.insert(this->buffer.end(), span.data(), span.data() + amount);
return span.subspan(amount);
}
/*
@@ -222,7 +237,7 @@ bool Packet::CanReadFromPacket(size_t bytes_to_read, bool close_connection)
*/
bool Packet::HasPacketSizeData() const
{
return this->pos >= sizeof(PacketSize);
return this->pos >= EncodedLengthOfPacketSize();
}
/**
@@ -243,27 +258,36 @@ size_t Packet::Size() const
*/
bool Packet::ParsePacketSize()
{
assert(this->cs != nullptr);
size_t size = (size_t)this->buffer[0];
size += (size_t)this->buffer[1] << 8;
size_t size = static_cast<size_t>(this->buffer[0]);
size += static_cast<size_t>(this->buffer[1]) << 8;
/* If the size of the packet is less than the bytes required for the size and type of
* the packet, or more than the allowed limit, then something is wrong with the packet.
* In those cases the packet can generally be regarded as containing garbage data. */
if (size < sizeof(PacketSize) + sizeof(PacketType) || size > this->limit) return false;
if (size < EncodedLengthOfPacketSize() + EncodedLengthOfPacketType() || size > this->limit) return false;
this->buffer.resize(size);
this->pos = sizeof(PacketSize);
this->pos = static_cast<PacketSize>(EncodedLengthOfPacketSize());
return true;
}
/**
* Prepares the packet so it can be read
* @return True when the packet was valid, otherwise false.
*/
void Packet::PrepareToRead()
bool Packet::PrepareToRead()
{
/* Put the position on the right place */
this->pos = sizeof(PacketSize);
this->pos = static_cast<PacketSize>(EncodedLengthOfPacketSize());
if (cs == nullptr || cs->receive_encryption_handler == nullptr) return true;
size_t mac_size = cs->receive_encryption_handler->MACSize();
if (this->buffer.size() <= pos + mac_size) return false;
bool valid = cs->receive_encryption_handler->Decrypt(std::span(&this->buffer[pos], mac_size), std::span(&this->buffer[pos + mac_size], this->buffer.size() - pos - mac_size));
this->pos += static_cast<PacketSize>(mac_size);
return valid;
}
/**
@@ -272,8 +296,10 @@ void Packet::PrepareToRead()
*/
PacketType Packet::GetPacketType() const
{
assert(this->Size() >= sizeof(PacketSize) + sizeof(PacketType));
return static_cast<PacketType>(buffer[sizeof(PacketSize)]);
assert(this->Size() >= EncodedLengthOfPacketSize() + EncodedLengthOfPacketType());
size_t offset = EncodedLengthOfPacketSize();
if (cs != nullptr && cs->send_encryption_handler != nullptr) offset += cs->send_encryption_handler->MACSize();
return static_cast<PacketType>(buffer[offset]);
}
/**
@@ -356,12 +382,12 @@ uint64_t Packet::Recv_uint64()
* Extract a sized byte buffer from the packet.
* @return The extracted buffer.
*/
std::vector<byte> Packet::Recv_buffer()
std::vector<uint8_t> Packet::Recv_buffer()
{
uint16_t size = this->Recv_uint16();
if (size == 0 || !this->CanReadFromPacket(size, true)) return {};
std::vector<byte> data;
std::vector<uint8_t> data;
while (size-- > 0) {
data.push_back(this->buffer[this->pos++]);
}
@@ -369,6 +395,22 @@ std::vector<byte> Packet::Recv_buffer()
return data;
}
/**
* Extract at most the length of the span bytes from the packet into the span.
* @param span The span to write the bytes to.
* @return The number of bytes that were actually read.
*/
size_t Packet::Recv_bytes(std::span<uint8_t> span)
{
auto tranfer_to_span = [](std::span<uint8_t> destination, const char *source, size_t amount) {
size_t to_copy = std::min(amount, destination.size());
std::copy(source, source + to_copy, destination.data());
return to_copy;
};
return this->TransferOut(tranfer_to_span, span);
}
/**
* Reads characters (bytes) from the packet until it finds a '\0', or reaches a
* maximum of \c length characters.
+10 -7
View File
@@ -40,11 +40,13 @@ typedef uint8_t PacketType; ///< Identifier for the packet
* (year % 4 == 0) and ((year % 100 != 0) or (year % 400 == 0))
*/
struct Packet {
static constexpr size_t EncodedLengthOfPacketSize() { return sizeof(PacketSize); }
static constexpr size_t EncodedLengthOfPacketType() { return sizeof(PacketType); }
private:
/** The current read/write position in the packet */
PacketSize pos;
/** The buffer of this packet. */
std::vector<byte> buffer;
std::vector<uint8_t> buffer;
/** The limit for the packet size. */
size_t limit;
@@ -52,8 +54,8 @@ private:
NetworkSocketHandler *cs;
public:
Packet(NetworkSocketHandler *cs, size_t limit, size_t initial_read_size = sizeof(PacketSize));
Packet(PacketType type, size_t limit = COMPAT_MTU);
Packet(NetworkSocketHandler *cs, size_t limit, size_t initial_read_size = EncodedLengthOfPacketSize());
Packet(NetworkSocketHandler *cs, PacketType type, size_t limit = COMPAT_MTU);
/* Sending/writing of packets */
void PrepareToSend();
@@ -65,14 +67,14 @@ public:
void Send_uint32(uint32_t data);
void Send_uint64(uint64_t data);
void Send_string(const std::string_view data);
void Send_buffer(const std::vector<byte> &data);
size_t Send_bytes (const byte *begin, const byte *end);
void Send_buffer(const std::vector<uint8_t> &data);
std::span<const uint8_t> Send_bytes(const std::span<const uint8_t> span);
/* Reading/receiving of packets */
bool HasPacketSizeData() const;
bool ParsePacketSize();
size_t Size() const;
void PrepareToRead();
[[nodiscard]] bool PrepareToRead();
PacketType GetPacketType() const;
bool CanReadFromPacket(size_t bytes_to_read, bool close_connection = false);
@@ -81,7 +83,8 @@ public:
uint16_t Recv_uint16();
uint32_t Recv_uint32();
uint64_t Recv_uint64();
std::vector<byte> Recv_buffer();
std::vector<uint8_t> Recv_buffer();
size_t Recv_bytes(std::span<uint8_t> span);
std::string Recv_string(size_t length, StringValidationSettings settings = SVS_REPLACE_WITH_QUESTION_MARK);
size_t RemainingBytesToTransfer() const;
+5 -1
View File
@@ -188,7 +188,11 @@ std::unique_ptr<Packet> NetworkTCPSocketHandler::ReceivePacket()
}
}
p.PrepareToRead();
if (!p.PrepareToRead()) {
Debug(net, 0, "Invalid packet received (too small / decryption error)");
this->CloseConnection();
return nullptr;
}
return std::move(this->packet_recv);
}
+8
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); }
+55 -5
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.
@@ -333,7 +364,7 @@ protected:
* string Name of the company.
* string Name of the companies manager.
* uint8_t Main company colour.
* bool Company is password protected.
* bool Company is protected.
* uint32_t Year the company was inaugurated.
* bool Company is an AI.
* @param p The packet that was just received.
@@ -347,7 +378,7 @@ protected:
* string Name of the company.
* string Name of the companies manager.
* uint8_t Main company colour.
* bool Company is password protected.
* bool Company is protected.
* uint8_t Quarters of bankruptcy.
* uint8_t Owner of share 1.
* uint8_t Owner of share 2.
@@ -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.
+2 -2
View File
@@ -25,7 +25,7 @@ protected:
/**
* Client requesting a list of content info:
* byte type
* uint8_t type
* uint32_t openttd version (or 0xFFFFFFFF if using a list)
* Only if the above value is 0xFFFFFFFF:
* uint8_t count
@@ -76,7 +76,7 @@ protected:
/**
* Server sending list of content info:
* byte type (invalid ID == does not exist)
* uint8_t type (invalid ID == does not exist)
* uint32_t id
* uint32_t file_size
* string name (max 32 characters)
+1 -1
View File
@@ -277,7 +277,7 @@ protected:
* uint16_t Number of NewGRFs in the packet, with for each of the NewGRFs:
* uint32_t Lookup table index for the NewGRF.
* uint32_t Unique NewGRF ID.
* byte[16] MD5 checksum of the NewGRF
* uint8_t[16] MD5 checksum of the NewGRF
* string Name of the NewGRF.
*
* The lookup table built using these packets are used by the deserialisation
+8 -12
View File
@@ -81,10 +81,10 @@ NetworkRecvStatus NetworkGameSocketHandler::HandlePacket(Packet &p)
case PACKET_CLIENT_GAME_INFO: return this->Receive_CLIENT_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_NEED_GAME_PASSWORD: return this->Receive_SERVER_NEED_GAME_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_COMPANY_PASSWORD: return this->Receive_CLIENT_COMPANY_PASSWORD(p);
case PACKET_CLIENT_IDENTIFY: return this->Receive_CLIENT_IDENTIFY(p);
case PACKET_SERVER_AUTH_REQUEST: return this->Receive_SERVER_AUTH_REQUEST(p);
case PACKET_CLIENT_AUTH_RESPONSE: return this->Receive_CLIENT_AUTH_RESPONSE(p);
case PACKET_SERVER_ENABLE_ENCRYPTION: return this->Receive_SERVER_ENABLE_ENCRYPTION(p);
case PACKET_SERVER_WELCOME: return this->Receive_SERVER_WELCOME(p);
case PACKET_CLIENT_GETMAP: return this->Receive_CLIENT_GETMAP(p);
case PACKET_SERVER_WAIT: return this->Receive_SERVER_WAIT(p);
@@ -102,7 +102,6 @@ NetworkRecvStatus NetworkGameSocketHandler::HandlePacket(Packet &p)
case PACKET_CLIENT_CHAT: return this->Receive_CLIENT_CHAT(p);
case PACKET_SERVER_CHAT: return this->Receive_SERVER_CHAT(p);
case PACKET_SERVER_EXTERNAL_CHAT: return this->Receive_SERVER_EXTERNAL_CHAT(p);
case PACKET_CLIENT_SET_PASSWORD: return this->Receive_CLIENT_SET_PASSWORD(p);
case PACKET_CLIENT_SET_NAME: return this->Receive_CLIENT_SET_NAME(p);
case PACKET_CLIENT_QUIT: return this->Receive_CLIENT_QUIT(p);
case PACKET_CLIENT_ERROR: return this->Receive_CLIENT_ERROR(p);
@@ -116,7 +115,6 @@ NetworkRecvStatus NetworkGameSocketHandler::HandlePacket(Packet &p)
case PACKET_CLIENT_NEWGRFS_CHECKED: return this->Receive_CLIENT_NEWGRFS_CHECKED(p);
case PACKET_SERVER_MOVE: return this->Receive_SERVER_MOVE(p);
case PACKET_CLIENT_MOVE: return this->Receive_CLIENT_MOVE(p);
case PACKET_SERVER_COMPANY_UPDATE: return this->Receive_SERVER_COMPANY_UPDATE(p);
case PACKET_SERVER_CONFIG_UPDATE: return this->Receive_SERVER_CONFIG_UPDATE(p);
default:
@@ -162,10 +160,10 @@ NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_ERROR(Packet &) { ret
NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_GAME_INFO(Packet &) { return this->ReceiveInvalidPacket(PACKET_CLIENT_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_NEED_GAME_PASSWORD(Packet &) { return this->ReceiveInvalidPacket(PACKET_SERVER_NEED_GAME_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_COMPANY_PASSWORD(Packet &) { return this->ReceiveInvalidPacket(PACKET_CLIENT_COMPANY_PASSWORD); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_IDENTIFY(Packet &) { return this->ReceiveInvalidPacket(PACKET_CLIENT_IDENTIFY); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_AUTH_REQUEST(Packet &) { return this->ReceiveInvalidPacket(PACKET_SERVER_AUTH_REQUEST); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_AUTH_RESPONSE(Packet &) { return this->ReceiveInvalidPacket(PACKET_CLIENT_AUTH_RESPONSE); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_ENABLE_ENCRYPTION(Packet &) { return this->ReceiveInvalidPacket(PACKET_SERVER_ENABLE_ENCRYPTION); }
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_SERVER_WAIT(Packet &) { return this->ReceiveInvalidPacket(PACKET_SERVER_WAIT); }
@@ -183,7 +181,6 @@ NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_COMMAND(Packet &) { r
NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_CHAT(Packet &) { return this->ReceiveInvalidPacket(PACKET_CLIENT_CHAT); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_CHAT(Packet &) { return this->ReceiveInvalidPacket(PACKET_SERVER_CHAT); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_EXTERNAL_CHAT(Packet &) { return this->ReceiveInvalidPacket(PACKET_SERVER_EXTERNAL_CHAT); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_SET_PASSWORD(Packet &) { return this->ReceiveInvalidPacket(PACKET_CLIENT_SET_PASSWORD); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_SET_NAME(Packet &) { return this->ReceiveInvalidPacket(PACKET_CLIENT_SET_NAME); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_QUIT(Packet &) { return this->ReceiveInvalidPacket(PACKET_CLIENT_QUIT); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_ERROR(Packet &) { return this->ReceiveInvalidPacket(PACKET_CLIENT_ERROR); }
@@ -197,7 +194,6 @@ NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_CHECK_NEWGRFS(Packet
NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_NEWGRFS_CHECKED(Packet &) { return this->ReceiveInvalidPacket(PACKET_CLIENT_NEWGRFS_CHECKED); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_MOVE(Packet &) { return this->ReceiveInvalidPacket(PACKET_SERVER_MOVE); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_MOVE(Packet &) { return this->ReceiveInvalidPacket(PACKET_CLIENT_MOVE); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_COMPANY_UPDATE(Packet &) { return this->ReceiveInvalidPacket(PACKET_SERVER_COMPANY_UPDATE); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_CONFIG_UPDATE(Packet &) { return this->ReceiveInvalidPacket(PACKET_SERVER_CONFIG_UPDATE); }
void NetworkGameSocketHandler::DeferDeletion()
+36 -46
View File
@@ -59,16 +59,18 @@ enum PacketGameType : uint8_t {
* the map and other important data.
*/
/* After the join step, the first is checking NewGRFs. */
/* 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_ENABLE_ENCRYPTION, ///< The server tells that authentication has completed and requests to enable encryption with the keys of the last \c PACKET_CLIENT_AUTH_RESPONSE.
/* 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.
PACKET_SERVER_NEED_COMPANY_PASSWORD, ///< Server requests the (hashed) company password.
PACKET_CLIENT_COMPANY_PASSWORD, ///< Client sends the (hashed) company password.
/* The server welcomes the authenticated client and sends information of other clients. */
PACKET_SERVER_WELCOME, ///< Server welcomes you and gives you your #ClientID.
PACKET_SERVER_CLIENT_INFO, ///< Server sends you information about a client.
@@ -113,9 +115,7 @@ enum PacketGameType : uint8_t {
PACKET_SERVER_MOVE, ///< Server tells everyone that someone is moved to another company.
/* Configuration updates. */
PACKET_CLIENT_SET_PASSWORD, ///< A client (re)sets its company's password.
PACKET_CLIENT_SET_NAME, ///< A client changes its name.
PACKET_SERVER_COMPANY_UPDATE, ///< Information (password) of a company changed.
PACKET_SERVER_CONFIG_UPDATE, ///< Some network configuration important to the client changed.
/* A client quitting. */
@@ -161,10 +161,13 @@ protected:
/**
* Try to join the server:
* string OpenTTD revision (norev000 if no revision).
* string Name of the client (max NETWORK_NAME_LENGTH).
* uint8_t ID of the company to play as (1..MAX_COMPANIES).
* uint8_t ID of the clients Language.
* string OpenTTD revision (norev0000 if no revision).
* uint32_t NewGRF version (added in 1.2).
* string Name of the client (max NETWORK_NAME_LENGTH) (removed in 15).
* uint8_t ID of the company to play as (1..MAX_COMPANIES) (removed in 15).
* uint8_t ID of the clients Language (removed in 15).
* string Client's unique identifier (removed in 1.0).
*
* @param p The packet that was just received.
*/
virtual NetworkRecvStatus Receive_CLIENT_JOIN(Packet &p);
@@ -193,53 +196,55 @@ 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);
/**
* Indication to the client that the server needs a game password.
* The client tells the server about the identity of the client:
* string Name of the client (max NETWORK_NAME_LENGTH).
* uint8_t ID of the company to play as (1..MAX_COMPANIES, or COMPANY_SPECTATOR).
* @param p The packet that was just received.
*/
virtual NetworkRecvStatus Receive_SERVER_NEED_GAME_PASSWORD(Packet &p);
virtual NetworkRecvStatus Receive_CLIENT_IDENTIFY(Packet &p);
/**
* Indication to the client that the server needs a company password:
* uint32_t Generation seed.
* string Network ID of the server.
* Indication to the client that it needs to authenticate:
* uint8_t The \c NetworkAuthenticationMethod to use.
* 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_COMPANY_PASSWORD(Packet &p);
virtual NetworkRecvStatus Receive_SERVER_AUTH_REQUEST(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.
* 16 * uint8_t Message authentication code.
* 8 * uint8_t Random message that got encoded and signed.
* @param p The packet that was just received.
*/
virtual NetworkRecvStatus Receive_CLIENT_GAME_PASSWORD(Packet &p);
virtual NetworkRecvStatus Receive_CLIENT_AUTH_RESPONSE(Packet &p);
/**
* Send a password to the server to authorize
* uint8_t Password type (see NetworkPasswordType).
* string The password.
* 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_CLIENT_COMPANY_PASSWORD(Packet &p);
virtual NetworkRecvStatus Receive_SERVER_ENABLE_ENCRYPTION(Packet &p);
/**
* The client is joined and ready to receive their map:
* uint32_t Own client ID.
* uint32_t Generation seed.
* string Network ID of the server.
* @param p The packet that was just received.
*/
virtual NetworkRecvStatus Receive_SERVER_WELCOME(Packet &p);
/**
* Request the map from the server.
* uint32_t NewGRF version (release versions of OpenTTD only).
* @param p The packet that was just received.
*/
virtual NetworkRecvStatus Receive_CLIENT_GETMAP(Packet &p);
@@ -373,13 +378,6 @@ protected:
*/
virtual NetworkRecvStatus Receive_SERVER_EXTERNAL_CHAT(Packet &p);
/**
* Set the password for the clients current company:
* string The password.
* @param p The packet that was just received.
*/
virtual NetworkRecvStatus Receive_CLIENT_SET_PASSWORD(Packet &p);
/**
* Gives the client a new name:
* string New name of the client.
@@ -469,18 +467,10 @@ protected:
/**
* Request the server to move this client into another company:
* uint8_t ID of the company the client wants to join.
* string Password, if the company is password protected.
* @param p The packet that was just received.
*/
virtual NetworkRecvStatus Receive_CLIENT_MOVE(Packet &p);
/**
* Update the clients knowledge of which company is password protected:
* uint16_t Bitwise representation of each company
* @param p The packet that was just received.
*/
virtual NetworkRecvStatus Receive_SERVER_COMPANY_UPDATE(Packet &p);
/**
* Update the clients knowledge of the max settings:
* uint8_t Maximum number of companies allowed.
+2 -2
View File
@@ -35,7 +35,7 @@ public:
/* Check if the client is banned. */
for (const auto &entry : _network_ban_list) {
if (address.IsInNetmask(entry)) {
Packet p(Tban_packet);
Packet p(nullptr, Tban_packet);
p.PrepareToSend();
Debug(net, 2, "[{}] Banned ip tried to join ({}), refused", Tsocket::GetName(), entry);
@@ -52,7 +52,7 @@ public:
if (!Tsocket::AllowConnection()) {
/* No more clients allowed?
* Send to the client that we are full! */
Packet p(Tfull_packet);
Packet p(nullptr, Tfull_packet);
p.PrepareToSend();
if (p.TransferOut<int>(send, s, 0) < 0) {
+5 -2
View File
@@ -133,11 +133,14 @@ void NetworkUDPSocketHandler::ReceivePackets()
/* If the size does not match the packet must be corrupted.
* Otherwise it will be marked as corrupted later on. */
if (!p.ParsePacketSize() || (size_t)nbytes != p.Size()) {
if (!p.ParsePacketSize() || static_cast<size_t>(nbytes) != p.Size()) {
Debug(net, 1, "Received a packet with mismatching size from {}", address.GetAddressAsString());
continue;
}
p.PrepareToRead();
if (!p.PrepareToRead()) {
Debug(net, 1, "Invalid packet received (too small / decryption error)");
continue;
}
/* Handle the packet */
this->HandleUDPPacket(p, address);
+129 -103
View File
@@ -70,7 +70,6 @@ bool _network_server; ///< network-server is active
bool _network_available; ///< is network mode available?
bool _network_dedicated; ///< are we a dedicated server?
bool _is_network_server; ///< Does this client wants to be a network-server?
NetworkCompanyState *_network_company_states = nullptr; ///< Statistics about some companies.
ClientID _network_own_client_id; ///< Our client identifier.
ClientID _redirect_console_to_client; ///< If not invalid, redirect the console output to a client.
uint8_t _network_reconnect; ///< Reconnect timeout
@@ -88,13 +87,9 @@ uint32_t _sync_seed_2; ///< Second part of the seed.
#endif
uint32_t _sync_frame; ///< The frame to perform the sync check.
bool _network_first_time; ///< Whether we have finished joining or not.
CompanyMask _network_company_passworded; ///< Bitmask of the password status of all companies.
bool _novarole = false;
static_assert((int)NETWORK_COMPANY_NAME_LENGTH == MAX_LENGTH_COMPANY_NAME_CHARS * MAX_CHAR_LENGTH);
/** The amount of clients connected */
byte _network_clients_connected = 0;
uint8_t _network_clients_connected = 0;
extern std::string GenerateUid(std::string_view subject);
@@ -131,6 +126,28 @@ NetworkClientInfo::~NetworkClientInfo()
return nullptr;
}
/**
* Returns whether the given company can be joined by this client.
* @param company_id The id of the company.
* @return \c true when this company is allowed to join, otherwise \c false.
*/
bool NetworkClientInfo::CanJoinCompany(CompanyID company_id) const
{
Company *c = Company::GetIfValid(company_id);
return c != nullptr && c->allow_list.Contains(this->public_key);
}
/**
* Returns whether the given company can be joined by this client.
* @param company_id The id of the company.
* @return \c true when this company is allowed to join, otherwise \c false.
*/
bool NetworkCanJoinCompany(CompanyID company_id)
{
NetworkClientInfo *info = NetworkClientInfo::GetByClientID(_network_own_client_id);
return info != nullptr && info->CanJoinCompany(company_id);
}
/**
* Return the client state given it's client-identifier
* @param client_id the ClientID to search for
@@ -145,9 +162,62 @@ NetworkClientInfo::~NetworkClientInfo()
return nullptr;
}
byte NetworkSpectatorCount()
/**
* Simple helper to find the location of the given authorized key in the authorized keys.
* @param authorized_keys The keys to look through.
* @param authorized_key The key to look for.
* @return The iterator to the location of the authorized key, or \c authorized_keys.end().
*/
static auto FindKey(auto *authorized_keys, std::string_view authorized_key)
{
byte count = 0;
return std::ranges::find_if(*authorized_keys, [authorized_key](auto &value) { return StrEqualsIgnoreCase(value, authorized_key); });
}
/**
* Check whether the given key is contains in these authorized keys.
* @param key The key to look for.
* @return \c true when the key has been found, otherwise \c false.
*/
bool NetworkAuthorizedKeys::Contains(std::string_view key) const
{
return FindKey(this, key) != this->end();
}
/**
* 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 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;
this->emplace_back(key);
return true;
}
/**
* Remove the given key from the authorized keys, when it is exists.
* @param key The key to remove.
* @return \c true when the key was removed, \c false when the key did not exist.
*/
bool NetworkAuthorizedKeys::Remove(std::string_view key)
{
auto iter = FindKey(this, key);
if (iter == this->end()) return false;
this->erase(iter);
return true;
}
uint8_t NetworkSpectatorCount()
{
uint8_t count = 0;
for (const NetworkClientInfo *ci : NetworkClientInfo::Iterate()) {
if (ci->client_playas == COMPANY_SPECTATOR) count++;
@@ -159,68 +229,6 @@ byte NetworkSpectatorCount()
return count;
}
/**
* Change the company password of a given company.
* @param company_id ID of the company the password should be changed for.
* @param password The unhashed password we like to set ('*' or '' resets the password)
* @return The password.
*/
std::string NetworkChangeCompanyPassword(CompanyID company_id, std::string password)
{
if (password.compare("*") == 0) password = "";
if (_network_server) {
NetworkServerSetCompanyPassword(company_id, password, false);
} else {
NetworkClientSetCompanyPassword(password);
}
return password;
}
/**
* Hash the given password using server ID and game seed.
* @param password Password to hash.
* @param password_server_id Server ID.
* @param password_game_seed Game seed.
* @return The hashed password.
*/
std::string GenerateCompanyPasswordHash(const std::string &password, const std::string &password_server_id, uint32_t password_game_seed)
{
if (password.empty()) return password;
size_t password_length = password.size();
size_t password_server_id_length = password_server_id.size();
std::ostringstream salted_password;
/* Add the password with the server's ID and game seed as the salt. */
for (uint i = 0; i < NETWORK_SERVER_ID_LENGTH - 1; i++) {
char password_char = (i < password_length ? password[i] : 0);
char server_id_char = (i < password_server_id_length ? password_server_id[i] : 0);
char seed_char = password_game_seed >> (i % 32);
salted_password << (char)(password_char ^ server_id_char ^ seed_char); // Cast needed, otherwise interpreted as integer to format
}
Md5 checksum;
MD5Hash digest;
/* Generate the MD5 hash */
std::string salted_password_string = salted_password.str();
checksum.Append(salted_password_string.data(), salted_password_string.size());
checksum.Finish(digest);
return FormatArrayAsHex(digest);
}
/**
* Check if the company we want to join requires a password.
* @param company_id id of the company we want to check the 'passworded' flag for.
* @return true if the company requires a password.
*/
bool NetworkCompanyIsPassworded(CompanyID company_id)
{
return HasBit(_network_company_passworded, company_id);
}
/* This puts a text-message to the console, or in the future, the chat-box,
* (to keep it all a bit more general)
@@ -332,6 +340,8 @@ 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,
STR_NETWORK_ERROR_CLIENT_NO_AUTHENTICATION_METHOD_AVAILABLE,
};
static_assert(lengthof(network_error_strings) == NETWORK_ERROR_END);
@@ -617,10 +627,6 @@ void NetworkClose(bool close_admins)
NetworkFreeLocalCommandQueue();
delete[] _network_company_states;
_network_company_states = nullptr;
_network_company_passworded = 0;
InitializeNetworkPools(close_admins);
}
@@ -767,7 +773,7 @@ public:
/**
* Join a client to the server at with the given connection string.
* The default for the passwords is \c nullptr. When the server or company needs a
* The default for the passwords is \c nullptr. When the server needs a
* password and none is given, the user is asked to enter the password in the GUI.
* This function will return false whenever some information required to join is not
* correct such as the company number or the client's name, or when there is not
@@ -779,10 +785,9 @@ public:
* @param connection_string The IP address, port and company number to join as.
* @param default_company The company number to join as when none is given.
* @param join_server_password The password for the server.
* @param join_company_password The password for the company.
* @return Whether the join has started.
*/
bool NetworkClientConnectGame(const std::string &connection_string, CompanyID default_company, const std::string &join_server_password, const std::string &join_company_password)
bool NetworkClientConnectGame(const std::string &connection_string, CompanyID default_company, const std::string &join_server_password)
{
Debug(net, 9, "NetworkClientConnectGame(): connection_string={}", connection_string);
@@ -795,7 +800,6 @@ bool NetworkClientConnectGame(const std::string &connection_string, CompanyID de
_network_join.connection_string = resolved_connection_string;
_network_join.company = join_as;
_network_join.server_password = join_server_password;
_network_join.company_password = join_company_password;
if (_game_mode == GM_MENU) {
/* From the menu we can immediately continue with the actual join. */
@@ -838,9 +842,12 @@ static void NetworkInitGameInfo()
/* There should be always space for the server. */
assert(NetworkClientInfo::CanAllocateItem());
NetworkClientInfo *ci = new NetworkClientInfo(CLIENT_ID_SERVER);
ci->client_playas = _network_dedicated ? COMPANY_SPECTATOR : COMPANY_FIRST;
ci->client_playas = COMPANY_SPECTATOR;
ci->client_name = _settings_client.network.client_name;
NetworkAuthenticationClientHandler::EnsureValidSecretKeyAndUpdatePublicKey(_settings_client.network.client_secret_key, _settings_client.network.client_public_key);
ci->public_key = _settings_client.network.client_public_key;
}
/**
@@ -902,8 +909,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;
}
@@ -912,7 +919,6 @@ bool NetworkServerStart()
Debug(net, 5, "Starting listeners for incoming server queries");
NetworkUDPServerListen();
_network_company_states = new NetworkCompanyState[MAX_COMPANIES];
_network_server = true;
_networking = true;
_frame_counter = 0;
@@ -922,7 +928,6 @@ bool NetworkServerStart()
_network_own_client_id = CLIENT_ID_SERVER;
_network_clients_connected = 0;
_network_company_passworded = 0;
NetworkInitGameInfo();
@@ -935,12 +940,42 @@ bool NetworkServerStart()
/* if the server is dedicated ... add some other script */
if (_network_dedicated) IConsoleCmdExec("exec scripts/on_dedicated.scr 0");
/* welcome possibly still connected admins - this can only happen on a dedicated server. */
if (_network_dedicated) ServerNetworkAdminSocketHandler::WelcomeAll();
return true;
}
/**
* Perform tasks when the server is started. This consists of things
* like putting the server's client in a valid company and resetting the restart time.
*/
void NetworkOnGameStart()
{
if (!_network_server) return;
/* Update the static game info to set the values from the new game. */
NetworkServerUpdateGameInfo();
ChangeNetworkRestartTime(true);
if (!_network_dedicated) {
Company *c = Company::GetIfValid(GetFirstPlayableCompanyID());
NetworkClientInfo *ci = NetworkClientInfo::GetByClientID(CLIENT_ID_SERVER);
if (c != nullptr && ci != nullptr) {
ci->client_playas = c->index;
/*
* If the company has not been named yet, the company was just started.
* Otherwise it would have gotten a name already, so announce it as a new company.
*/
if (c->name_1 == STR_SV_UNNAMED && c->name.empty()) NetworkServerNewCompany(c, ci);
}
ShowClientList();
} else {
/* welcome possibly still connected admins - this can only happen on a dedicated server. */
ServerNetworkAdminSocketHandler::WelcomeAll();
}
}
/* The server is rebooting...
* The only difference with NetworkDisconnect, is the packets that is sent */
void NetworkReboot()
@@ -1080,18 +1115,18 @@ void NetworkGameLoop()
#ifdef DEBUG_DUMP_COMMANDS
/* Loading of the debug commands from -ddesync>=1 */
static FILE *f = FioFOpenFile("commands.log", "rb", SAVE_DIR);
static auto f = FioFOpenFile("commands.log", "rb", SAVE_DIR);
static TimerGameEconomy::Date next_date(0);
static uint32_t next_date_fract;
static CommandPacket *cp = nullptr;
static bool check_sync_state = false;
static uint32_t sync_state[2];
if (f == nullptr && next_date == 0) {
if (!f.has_value() && next_date == 0) {
Debug(desync, 0, "Cannot open commands.log");
next_date = TimerGameEconomy::Date(1);
}
while (f != nullptr && !feof(f)) {
while (f.has_value() && !feof(*f)) {
if (TimerGameEconomy::date == next_date && TimerGameEconomy::date_fract == next_date_fract) {
if (cp != nullptr) {
NetworkSendCommand(cp->cmd, cp->err_msg, nullptr, cp->company, cp->data);
@@ -1124,7 +1159,7 @@ void NetworkGameLoop()
if (cp != nullptr || check_sync_state) break;
char buff[4096];
if (fgets(buff, lengthof(buff), f) == nullptr) break;
if (fgets(buff, lengthof(buff), *f) == nullptr) break;
char *p = buff;
/* Ignore the "[date time] " part of the message */
@@ -1153,10 +1188,10 @@ void NetworkGameLoop()
cp->cmd = (Commands)cmd;
/* Parse command data. */
std::vector<byte> args;
std::vector<uint8_t> args;
size_t arg_len = strlen(buffer);
for (size_t i = 0; i + 1 < arg_len; i += 2) {
byte e = 0;
uint8_t e = 0;
std::from_chars(buffer + i, buffer + i + 2, e, 16);
args.emplace_back(e);
}
@@ -1193,10 +1228,9 @@ void NetworkGameLoop()
NOT_REACHED();
}
}
if (f != nullptr && feof(f)) {
if (f.has_value() && feof(*f)) {
Debug(desync, 0, "End of commands.log");
fclose(f);
f = nullptr;
f.reset();
}
#endif /* DEBUG_DUMP_COMMANDS */
if (_frame_counter >= _frame_counter_max) {
@@ -1253,11 +1287,6 @@ void NetworkGameLoop()
NetworkSend();
}
static void NetworkGenerateServerId()
{
_settings_client.network.network_id = GenerateUid("OpenTTD Server ID");
}
/** This tries to launch the network for a given OS */
void NetworkStartUp()
{
@@ -1267,9 +1296,6 @@ void NetworkStartUp()
_network_available = NetworkCoreInitialize();
_network_dedicated = false;
/* Generate an server id when there is none yet */
if (_settings_client.network.network_id.empty()) NetworkGenerateServerId();
_network_game_info = {};
NetworkInitialize();
+131 -40
View File
@@ -32,12 +32,15 @@
AdminIndex _redirect_console_to_admin = INVALID_ADMIN_ID;
/** The amount of admins connected. */
byte _network_admins_connected = 0;
uint8_t _network_admins_connected = 0;
/** The pool with sockets/clients. */
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,7 +137,10 @@ ServerNetworkAdminSocketHandler::~ServerNetworkAdminSocketHandler()
*/
NetworkRecvStatus ServerNetworkAdminSocketHandler::SendError(NetworkErrorCode error)
{
auto p = std::make_unique<Packet>(ADMIN_PACKET_SERVER_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);
this->SendPacket(std::move(p));
@@ -149,7 +155,9 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendError(NetworkErrorCode er
/** Send the protocol version to the admin. */
NetworkRecvStatus ServerNetworkAdminSocketHandler::SendProtocol()
{
auto p = std::make_unique<Packet>(ADMIN_PACKET_SERVER_PROTOCOL);
this->status = ADMIN_STATUS_ACTIVE;
auto p = std::make_unique<Packet>(this, ADMIN_PACKET_SERVER_PROTOCOL);
/* announce the protocol version */
p->Send_uint8(NETWORK_GAME_ADMIN_VERSION);
@@ -169,7 +177,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendProtocol()
/** Send a welcome message to the admin. */
NetworkRecvStatus ServerNetworkAdminSocketHandler::SendWelcome()
{
auto p = std::make_unique<Packet>(ADMIN_PACKET_SERVER_WELCOME);
auto p = std::make_unique<Packet>(this, ADMIN_PACKET_SERVER_WELCOME);
p->Send_string(_settings_client.network.server_name);
p->Send_string(GetNetworkRevisionString());
@@ -190,7 +198,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendWelcome()
/** Tell the admin we started a new game. */
NetworkRecvStatus ServerNetworkAdminSocketHandler::SendNewGame()
{
auto p = std::make_unique<Packet>(ADMIN_PACKET_SERVER_NEWGAME);
auto p = std::make_unique<Packet>(this, ADMIN_PACKET_SERVER_NEWGAME);
this->SendPacket(std::move(p));
return NETWORK_RECV_STATUS_OKAY;
}
@@ -198,7 +206,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendNewGame()
/** Tell the admin we're shutting down. */
NetworkRecvStatus ServerNetworkAdminSocketHandler::SendShutdown()
{
auto p = std::make_unique<Packet>(ADMIN_PACKET_SERVER_SHUTDOWN);
auto p = std::make_unique<Packet>(this, ADMIN_PACKET_SERVER_SHUTDOWN);
this->SendPacket(std::move(p));
return NETWORK_RECV_STATUS_OKAY;
}
@@ -206,7 +214,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendShutdown()
/** Tell the admin the date. */
NetworkRecvStatus ServerNetworkAdminSocketHandler::SendDate()
{
auto p = std::make_unique<Packet>(ADMIN_PACKET_SERVER_DATE);
auto p = std::make_unique<Packet>(this, ADMIN_PACKET_SERVER_DATE);
p->Send_uint32(TimerGameCalendar::date.base());
this->SendPacket(std::move(p));
@@ -220,7 +228,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendDate()
*/
NetworkRecvStatus ServerNetworkAdminSocketHandler::SendClientJoin(ClientID client_id)
{
auto p = std::make_unique<Packet>(ADMIN_PACKET_SERVER_CLIENT_JOIN);
auto p = std::make_unique<Packet>(this, ADMIN_PACKET_SERVER_CLIENT_JOIN);
p->Send_uint32(client_id);
this->SendPacket(std::move(p));
@@ -238,7 +246,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendClientInfo(const NetworkC
/* Only send data when we're a proper client, not just someone trying to query the server. */
if (ci == nullptr) return NETWORK_RECV_STATUS_OKAY;
auto p = std::make_unique<Packet>(ADMIN_PACKET_SERVER_CLIENT_INFO);
auto p = std::make_unique<Packet>(this, ADMIN_PACKET_SERVER_CLIENT_INFO);
p->Send_uint32(ci->client_id);
p->Send_string(cs == nullptr ? "" : const_cast<NetworkAddress &>(cs->client_address).GetHostname());
@@ -259,7 +267,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendClientInfo(const NetworkC
*/
NetworkRecvStatus ServerNetworkAdminSocketHandler::SendClientUpdate(const NetworkClientInfo *ci)
{
auto p = std::make_unique<Packet>(ADMIN_PACKET_SERVER_CLIENT_UPDATE);
auto p = std::make_unique<Packet>(this, ADMIN_PACKET_SERVER_CLIENT_UPDATE);
p->Send_uint32(ci->client_id);
p->Send_string(ci->client_name);
@@ -276,7 +284,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendClientUpdate(const Networ
*/
NetworkRecvStatus ServerNetworkAdminSocketHandler::SendClientQuit(ClientID client_id)
{
auto p = std::make_unique<Packet>(ADMIN_PACKET_SERVER_CLIENT_QUIT);
auto p = std::make_unique<Packet>(this, ADMIN_PACKET_SERVER_CLIENT_QUIT);
p->Send_uint32(client_id);
this->SendPacket(std::move(p));
@@ -291,7 +299,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendClientQuit(ClientID clien
*/
NetworkRecvStatus ServerNetworkAdminSocketHandler::SendClientError(ClientID client_id, NetworkErrorCode error)
{
auto p = std::make_unique<Packet>(ADMIN_PACKET_SERVER_CLIENT_ERROR);
auto p = std::make_unique<Packet>(this, ADMIN_PACKET_SERVER_CLIENT_ERROR);
p->Send_uint32(client_id);
p->Send_uint8 (error);
@@ -306,7 +314,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendClientError(ClientID clie
*/
NetworkRecvStatus ServerNetworkAdminSocketHandler::SendCompanyNew(CompanyID company_id)
{
auto p = std::make_unique<Packet>(ADMIN_PACKET_SERVER_COMPANY_NEW);
auto p = std::make_unique<Packet>(this, ADMIN_PACKET_SERVER_COMPANY_NEW);
p->Send_uint8(company_id);
this->SendPacket(std::move(p));
@@ -320,7 +328,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendCompanyNew(CompanyID comp
*/
NetworkRecvStatus ServerNetworkAdminSocketHandler::SendCompanyInfo(const Company *c)
{
auto p = std::make_unique<Packet>(ADMIN_PACKET_SERVER_COMPANY_INFO);
auto p = std::make_unique<Packet>(this, ADMIN_PACKET_SERVER_COMPANY_INFO);
p->Send_uint8 (c->index);
SetDParam(0, c->index);
@@ -328,7 +336,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendCompanyInfo(const Company
SetDParam(0, c->index);
p->Send_string(GetString(STR_PRESIDENT_NAME));
p->Send_uint8 (c->colour);
p->Send_bool (NetworkCompanyIsPassworded(c->index));
p->Send_bool (true);
p->Send_uint32(c->inaugurated_year.base());
p->Send_bool (c->is_ai);
p->Send_uint8 (CeilDiv(c->months_of_bankruptcy, 3)); // send as quarters_of_bankruptcy
@@ -345,7 +353,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendCompanyInfo(const Company
*/
NetworkRecvStatus ServerNetworkAdminSocketHandler::SendCompanyUpdate(const Company *c)
{
auto p = std::make_unique<Packet>(ADMIN_PACKET_SERVER_COMPANY_UPDATE);
auto p = std::make_unique<Packet>(this, ADMIN_PACKET_SERVER_COMPANY_UPDATE);
p->Send_uint8 (c->index);
SetDParam(0, c->index);
@@ -353,7 +361,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendCompanyUpdate(const Compa
SetDParam(0, c->index);
p->Send_string(GetString(STR_PRESIDENT_NAME));
p->Send_uint8 (c->colour);
p->Send_bool (NetworkCompanyIsPassworded(c->index));
p->Send_bool (true);
p->Send_uint8 (CeilDiv(c->months_of_bankruptcy, 3)); // send as quarters_of_bankruptcy
this->SendPacket(std::move(p));
@@ -368,7 +376,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendCompanyUpdate(const Compa
*/
NetworkRecvStatus ServerNetworkAdminSocketHandler::SendCompanyRemove(CompanyID company_id, AdminCompanyRemoveReason acrr)
{
auto p = std::make_unique<Packet>(ADMIN_PACKET_SERVER_COMPANY_REMOVE);
auto p = std::make_unique<Packet>(this, ADMIN_PACKET_SERVER_COMPANY_REMOVE);
p->Send_uint8(company_id);
p->Send_uint8(acrr);
@@ -385,7 +393,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendCompanyEconomy()
/* Get the income. */
Money income = -std::reduce(std::begin(company->yearly_expenses[0]), std::end(company->yearly_expenses[0]));
auto p = std::make_unique<Packet>(ADMIN_PACKET_SERVER_COMPANY_ECONOMY);
auto p = std::make_unique<Packet>(this, ADMIN_PACKET_SERVER_COMPANY_ECONOMY);
p->Send_uint8(company->index);
@@ -418,7 +426,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendCompanyStats()
/* Go through all the companies. */
for (const Company *company : Company::Iterate()) {
auto p = std::make_unique<Packet>(ADMIN_PACKET_SERVER_COMPANY_STATS);
auto p = std::make_unique<Packet>(this, ADMIN_PACKET_SERVER_COMPANY_STATS);
/* Send the information. */
p->Send_uint8(company->index);
@@ -447,7 +455,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendCompanyStats()
*/
NetworkRecvStatus ServerNetworkAdminSocketHandler::SendChat(NetworkAction action, DestType desttype, ClientID client_id, const std::string &msg, int64_t data)
{
auto p = std::make_unique<Packet>(ADMIN_PACKET_SERVER_CHAT);
auto p = std::make_unique<Packet>(this, ADMIN_PACKET_SERVER_CHAT);
p->Send_uint8 (action);
p->Send_uint8 (desttype);
@@ -465,7 +473,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendChat(NetworkAction action
*/
NetworkRecvStatus ServerNetworkAdminSocketHandler::SendRconEnd(const std::string_view command)
{
auto p = std::make_unique<Packet>(ADMIN_PACKET_SERVER_RCON_END);
auto p = std::make_unique<Packet>(this, ADMIN_PACKET_SERVER_RCON_END);
p->Send_string(command);
this->SendPacket(std::move(p));
@@ -480,7 +488,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendRconEnd(const std::string
*/
NetworkRecvStatus ServerNetworkAdminSocketHandler::SendRcon(uint16_t colour, const std::string_view result)
{
auto p = std::make_unique<Packet>(ADMIN_PACKET_SERVER_RCON);
auto p = std::make_unique<Packet>(this, ADMIN_PACKET_SERVER_RCON);
p->Send_uint16(colour);
p->Send_string(result);
@@ -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();
@@ -539,7 +547,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendConsole(const std::string
* smaller than COMPAT_MTU. */
if (origin.size() + string.size() + 2 + 3 >= COMPAT_MTU) return NETWORK_RECV_STATUS_OKAY;
auto p = std::make_unique<Packet>(ADMIN_PACKET_SERVER_CONSOLE);
auto p = std::make_unique<Packet>(this, ADMIN_PACKET_SERVER_CONSOLE);
p->Send_string(origin);
p->Send_string(string);
@@ -554,7 +562,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendConsole(const std::string
*/
NetworkRecvStatus ServerNetworkAdminSocketHandler::SendGameScript(const std::string_view json)
{
auto p = std::make_unique<Packet>(ADMIN_PACKET_SERVER_GAMESCRIPT);
auto p = std::make_unique<Packet>(this, ADMIN_PACKET_SERVER_GAMESCRIPT);
p->Send_string(json);
this->SendPacket(std::move(p));
@@ -565,7 +573,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendGameScript(const std::str
/** Send ping-reply (pong) to admin **/
NetworkRecvStatus ServerNetworkAdminSocketHandler::SendPong(uint32_t d1)
{
auto p = std::make_unique<Packet>(ADMIN_PACKET_SERVER_PONG);
auto p = std::make_unique<Packet>(this, ADMIN_PACKET_SERVER_PONG);
p->Send_uint32(d1);
this->SendPacket(std::move(p));
@@ -576,7 +584,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendPong(uint32_t d1)
/** Send the names of the commands. */
NetworkRecvStatus ServerNetworkAdminSocketHandler::SendCmdNames()
{
auto p = std::make_unique<Packet>(ADMIN_PACKET_SERVER_CMD_NAMES);
auto p = std::make_unique<Packet>(this, ADMIN_PACKET_SERVER_CMD_NAMES);
for (uint16_t i = 0; i < CMD_END; i++) {
const char *cmdname = GetCommandName(static_cast<Commands>(i));
@@ -588,7 +596,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendCmdNames()
p->Send_bool(false);
this->SendPacket(std::move(p));
p = std::make_unique<Packet>(ADMIN_PACKET_SERVER_CMD_NAMES);
p = std::make_unique<Packet>(this, ADMIN_PACKET_SERVER_CMD_NAMES);
}
p->Send_bool(true);
@@ -610,7 +618,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendCmdNames()
*/
NetworkRecvStatus ServerNetworkAdminSocketHandler::SendCmdLogging(ClientID client_id, const CommandPacket &cp)
{
auto p = std::make_unique<Packet>(ADMIN_PACKET_SERVER_CMD_LOGGING);
auto p = std::make_unique<Packet>(this, ADMIN_PACKET_SERVER_CMD_LOGGING);
p->Send_uint32(client_id);
p->Send_uint8 (cp.company);
@@ -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
*/
+6
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.
+3
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
@@ -35,6 +36,8 @@ struct NetworkClientInfo : NetworkClientInfoPool::PoolItem<&_networkclientinfo_p
~NetworkClientInfo();
static NetworkClientInfo *GetByClientID(ClientID client_id);
bool CanJoinCompany(CompanyID company_id) const;
};
#endif /* NETWORK_BASE_H */
+45 -122
View File
@@ -9,6 +9,7 @@
#include "../stdafx.h"
#include "../strings_func.h"
#include "../autocompletion.h"
#include "../blitter/factory.hpp"
#include "../console_func.h"
#include "../video/video_driver.hpp"
@@ -44,7 +45,6 @@ struct ChatMessage {
static std::deque<ChatMessage> _chatmsg_list; ///< The actual chat message list.
static bool _chatmessage_dirty = false; ///< Does the chat message need repainting?
static bool _chatmessage_visible = false; ///< Is a chat message visible.
static bool _chat_tab_completion_active; ///< Whether tab completion is active.
static uint MAX_CHAT_MESSAGES = 0; ///< The limit of chat messages to show.
/**
@@ -262,11 +262,47 @@ static void SendChat(const std::string &buf, DestType type, int dest)
}
}
class NetworkChatAutoCompletion final : public AutoCompletion {
public:
using AutoCompletion::AutoCompletion;
private:
std::vector<std::string> GetSuggestions([[maybe_unused]] std::string_view prefix, std::string_view query) override
{
std::vector<std::string> suggestions;
for (NetworkClientInfo *ci : NetworkClientInfo::Iterate()) {
if (ci->client_name.starts_with(query)) {
suggestions.push_back(ci->client_name);
}
}
for (const Town *t : Town::Iterate()) {
/* Get the town-name via the string-system */
SetDParam(0, t->index);
std::string town_name = GetString(STR_TOWN_NAME);
if (town_name.starts_with(query)) {
suggestions.push_back(std::move(town_name));
}
}
return suggestions;
}
void ApplySuggestion(std::string_view prefix, std::string_view suggestion) override
{
/* Add ': ' if we are at the start of the line (pretty) */
if (prefix.empty()) {
this->textbuf->Assign(fmt::format("{}: ", suggestion));
} else {
this->textbuf->Assign(fmt::format("{}{} ", prefix, suggestion));
}
}
};
/** Window to enter the chat message in. */
struct NetworkChatWindow : public Window {
DestType dtype; ///< The type of destination.
int dest; ///< The identifier of the destination.
QueryString message_editbox; ///< Message editbox.
NetworkChatAutoCompletion chat_tab_completion; ///< Holds the state and logic of auto-completion of player names and towns on Tab press.
/**
* Create a chat input window.
@@ -274,7 +310,8 @@ struct NetworkChatWindow : public Window {
* @param type The type of destination.
* @param dest The actual destination index.
*/
NetworkChatWindow(WindowDesc *desc, DestType type, int dest) : Window(desc), message_editbox(NETWORK_CHAT_LENGTH)
NetworkChatWindow(WindowDesc &desc, DestType type, int dest)
: Window(desc), message_editbox(NETWORK_CHAT_LENGTH), chat_tab_completion(&message_editbox.text)
{
this->dtype = type;
this->dest = dest;
@@ -295,7 +332,6 @@ struct NetworkChatWindow : public Window {
this->SetFocusedWidget(WID_NC_TEXTBOX);
InvalidateWindowData(WC_NEWS_WINDOW, 0, this->height);
_chat_tab_completion_active = false;
PositionNetworkChatWindow(this);
}
@@ -311,125 +347,12 @@ struct NetworkChatWindow : public Window {
Window::FindWindowPlacementAndResize(_toolbar_width, def_height);
}
/**
* Find the next item of the list of things that can be auto-completed.
* @param item The current indexed item to return. This function can, and most
* likely will, alter item, to skip empty items in the arrays.
* @return Returns the view that matched to the index.
*/
std::optional<std::string> ChatTabCompletionNextItem(uint *item)
{
/* First, try clients */
if (*item < MAX_CLIENT_SLOTS) {
/* Skip inactive clients */
for (NetworkClientInfo *ci : NetworkClientInfo::Iterate(*item)) {
*item = ci->index;
return ci->client_name;
}
*item = MAX_CLIENT_SLOTS;
}
/* Then, try townnames
* Not that the following assumes all town indices are adjacent, ie no
* towns have been deleted. */
if (*item < (uint)MAX_CLIENT_SLOTS + Town::GetPoolSize()) {
for (const Town *t : Town::Iterate(*item - MAX_CLIENT_SLOTS)) {
/* Get the town-name via the string-system */
SetDParam(0, t->index);
return GetString(STR_TOWN_NAME);
}
}
return std::nullopt;
}
/**
* Find what text to complete. It scans for a space from the left and marks
* the word right from that as to complete. It also writes a \0 at the
* position of the space (if any). If nothing found, buf is returned.
*/
static std::string_view ChatTabCompletionFindText(std::string_view &buf)
{
auto it = buf.find_last_of(' ');
if (it == std::string_view::npos) return buf;
std::string_view res = buf.substr(it + 1);
buf.remove_suffix(res.size() + 1);
return res;
}
/**
* See if we can auto-complete the current text of the user.
*/
void ChatTabCompletion()
{
static std::string _chat_tab_completion_buf;
Textbuf *tb = &this->message_editbox.text;
uint item = 0;
bool second_scan = false;
/* Create views, so we do not need to copy the data for now. */
std::string_view pre_buf = _chat_tab_completion_active ? std::string_view(_chat_tab_completion_buf) : std::string_view(tb->buf);
std::string_view tb_buf = ChatTabCompletionFindText(pre_buf);
/*
* Comparing pointers of the data, as both "Hi:<tab>" and "Hi: Hi:<tab>" will result in
* tb_buf and pre_buf being "Hi:", which would be equal in content but not in context.
*/
bool begin_of_line = tb_buf.data() == pre_buf.data();
std::optional<std::string> cur_item;
while ((cur_item = ChatTabCompletionNextItem(&item)).has_value()) {
std::string_view cur_name = cur_item.value();
item++;
if (_chat_tab_completion_active) {
/* We are pressing TAB again on the same name, is there another name
* that starts with this? */
if (!second_scan) {
std::string_view view;
/* If we are completing at the begin of the line, skip the ': ' we added */
if (begin_of_line) {
view = std::string_view(tb->buf, (tb->bytes - 1) - 2);
} else {
/* Else, find the place we are completing at */
size_t offset = pre_buf.size() + 1;
view = std::string_view(tb->buf + offset, (tb->bytes - 1) - offset);
}
/* Compare if we have a match */
if (cur_name == view) second_scan = true;
continue;
}
/* Now any match we make on _chat_tab_completion_buf after this, is perfect */
}
if (tb_buf.size() < cur_name.size() && cur_name.starts_with(tb_buf)) {
/* Save the data it was before completion */
if (!second_scan) _chat_tab_completion_buf = tb->buf;
_chat_tab_completion_active = true;
/* Change to the found name. Add ': ' if we are at the start of the line (pretty) */
if (begin_of_line) {
this->message_editbox.text.Assign(fmt::format("{}: ", cur_name));
} else {
this->message_editbox.text.Assign(fmt::format("{} {}", pre_buf, cur_name));
}
this->SetDirty();
return;
}
}
if (second_scan) {
/* We walked all possibilities, and the user presses tab again.. revert to original text */
this->message_editbox.text.Assign(_chat_tab_completion_buf);
_chat_tab_completion_active = false;
if (this->chat_tab_completion.AutoComplete()) {
this->SetDirty();
}
}
@@ -475,7 +398,7 @@ struct NetworkChatWindow : public Window {
void OnEditboxChanged(WidgetID widget) override
{
if (widget == WID_NC_TEXTBOX) {
_chat_tab_completion_active = false;
this->chat_tab_completion.Reset();
}
}
@@ -497,7 +420,7 @@ static constexpr NWidgetPart _nested_chat_window_widgets[] = {
NWidget(WWT_PANEL, COLOUR_GREY, WID_NC_BACKGROUND),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_TEXT, COLOUR_GREY, WID_NC_DESTINATION), SetMinimalSize(62, 12), SetPadding(1, 0, 1, 0), SetAlignment(SA_VERT_CENTER | SA_RIGHT), SetDataTip(STR_NULL, STR_NULL),
NWidget(WWT_EDITBOX, COLOUR_GREY, WID_NC_TEXTBOX), SetMinimalSize(100, 12), SetPadding(1, 0, 1, 0), SetResize(1, 0),
NWidget(WWT_EDITBOX, COLOUR_GREY, WID_NC_TEXTBOX), SetMinimalSize(100, 0), SetPadding(1, 0, 1, 0), SetResize(1, 0),
SetDataTip(STR_NETWORK_CHAT_OSKTITLE, STR_NULL),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_NC_SENDBUTTON), SetMinimalSize(62, 12), SetPadding(1, 0, 1, 0), SetDataTip(STR_NETWORK_CHAT_SEND, STR_NULL),
EndContainer(),
@@ -506,11 +429,11 @@ static constexpr NWidgetPart _nested_chat_window_widgets[] = {
};
/** The description of the chat window. */
static WindowDesc _chat_window_desc(__FILE__, __LINE__,
static WindowDesc _chat_window_desc(
WDP_MANUAL, nullptr, 0, 0,
WC_SEND_NETWORK_MSG, WC_NONE,
0,
std::begin(_nested_chat_window_widgets), std::end(_nested_chat_window_widgets)
_nested_chat_window_widgets
);
@@ -522,5 +445,5 @@ static WindowDesc _chat_window_desc(__FILE__, __LINE__,
void ShowNetworkChatQueryWindow(DestType type, int dest)
{
CloseWindowByClass(WC_SEND_NETWORK_MSG);
new NetworkChatWindow(&_chat_window_desc, type, dest);
new NetworkChatWindow(_chat_window_desc, type, dest);
}
+90 -120
View File
@@ -51,10 +51,10 @@ void SyncCMUser(const std::string &msg);
struct PacketReader : LoadFilter {
static const size_t CHUNK = 32 * 1024; ///< 32 KiB chunks of memory.
std::vector<byte *> blocks; ///< Buffer with blocks of allocated memory.
byte *buf; ///< Buffer we're going to write to/read from.
byte *bufe; ///< End of the buffer we write to/read from.
byte **block; ///< The block we're reading from/writing to.
std::vector<uint8_t *> blocks; ///< Buffer with blocks of allocated memory.
uint8_t *buf; ///< Buffer we're going to write to/read from.
uint8_t *bufe; ///< End of the buffer we write to/read from.
uint8_t **block; ///< The block we're reading from/writing to.
size_t written_bytes; ///< The total number of bytes we've written.
size_t read_bytes; ///< The total number of read bytes.
@@ -98,18 +98,18 @@ struct PacketReader : LoadFilter {
if (p.RemainingBytesToTransfer() == 0) return;
/* Allocate a new chunk and add the remaining data. */
this->blocks.push_back(this->buf = CallocT<byte>(CHUNK));
this->blocks.push_back(this->buf = CallocT<uint8_t>(CHUNK));
this->bufe = this->buf + CHUNK;
p.TransferOutWithLimit(TransferOutMemCopy, this->bufe - this->buf, this);
}
size_t Read(byte *rbuf, size_t size) override
size_t Read(uint8_t *rbuf, size_t size) override
{
/* Limit the amount to read to whatever we still have. */
size_t ret_size = size = std::min(this->written_bytes - this->read_bytes, size);
this->read_bytes += ret_size;
const byte *rbufe = rbuf + ret_size;
const uint8_t *rbufe = rbuf + ret_size;
while (rbuf != rbufe) {
if (this->buf == this->bufe) {
@@ -323,11 +323,6 @@ ClientNetworkGameSocketHandler * ClientNetworkGameSocketHandler::my_client = nul
/** Last frame we performed an ack. */
static uint32_t last_ack_frame;
/** One bit of 'entropy' used to generate a salt for the company passwords. */
static uint32_t _password_game_seed;
/** The other bit of 'entropy' used to generate a salt for the company passwords. */
static std::string _password_server_id;
/** Maximum number of companies of the currently joined server. */
static uint8_t _network_server_max_companies;
/** The current name of the server you are on. */
@@ -336,9 +331,6 @@ std::string _network_server_name;
/** Information about the game to join to. */
NetworkJoinInfo _network_join;
/** Make sure the server ID length is the same as a md5 hash. */
static_assert(NETWORK_SERVER_ID_LENGTH == MD5_HASH_BYTES * 2 + 1);
/***********
* Sending functions
************/
@@ -354,12 +346,21 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendJoin()
_network_join_status = NETWORK_JOIN_STATUS_AUTHORIZING;
SetWindowDirty(WC_NETWORK_STATUS_WINDOW, WN_NETWORK_STATUS_WINDOW_JOIN);
auto p = std::make_unique<Packet>(PACKET_CLIENT_JOIN);
auto p = std::make_unique<Packet>(my_client, PACKET_CLIENT_JOIN);
p->Send_string(GetNetworkRevisionString());
p->Send_uint32(_openttd_newgrf_version);
my_client->SendPacket(std::move(p));
return NETWORK_RECV_STATUS_OKAY;
}
NetworkRecvStatus ClientNetworkGameSocketHandler::SendIdentify()
{
Debug(net, 9, "Client::SendIdentify()");
auto p = std::make_unique<Packet>(my_client, PACKET_CLIENT_IDENTIFY);
p->Send_string(_settings_client.network.client_name); // Client name
p->Send_uint8 (_network_join.company); // PlayAs
p->Send_uint8 (0); // Used to be language
p->Send_uint8 (citymania::GetAvailableLoadFormats()); // Compressnion formats that we can decompress
my_client->SendPacket(std::move(p));
return NETWORK_RECV_STATUS_OKAY;
@@ -370,7 +371,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendNewGRFsOk()
{
Debug(net, 9, "Client::SendNewGRFsOk()");
auto p = std::make_unique<Packet>(PACKET_CLIENT_NEWGRFS_CHECKED);
auto p = std::make_unique<Packet>(my_client, PACKET_CLIENT_NEWGRFS_CHECKED);
my_client->SendPacket(std::move(p));
return NETWORK_RECV_STATUS_OKAY;
}
@@ -379,27 +380,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<Packet>(PACKET_CLIENT_GAME_PASSWORD);
p->Send_string(password);
auto p = std::make_unique<Packet>(my_client, PACKET_CLIENT_AUTH_RESPONSE);
my_client->authentication_handler->SendResponse(*p);
my_client->SendPacket(std::move(p));
return NETWORK_RECV_STATUS_OKAY;
}
/**
* Set the company password as requested.
* @param password The company password.
*/
NetworkRecvStatus ClientNetworkGameSocketHandler::SendCompanyPassword(const std::string &password)
{
Debug(net, 9, "Client::SendCompanyPassword()");
auto p = std::make_unique<Packet>(PACKET_CLIENT_COMPANY_PASSWORD);
p->Send_string(GenerateCompanyPasswordHash(password, _password_server_id, _password_game_seed));
my_client->SendPacket(std::move(p));
return NETWORK_RECV_STATUS_OKAY;
}
@@ -411,7 +399,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendGetMap()
Debug(net, 9, "Client::status = MAP_WAIT");
my_client->status = STATUS_MAP_WAIT;
auto p = std::make_unique<Packet>(PACKET_CLIENT_GETMAP);
auto p = std::make_unique<Packet>(my_client, PACKET_CLIENT_GETMAP);
my_client->SendPacket(std::move(p));
return NETWORK_RECV_STATUS_OKAY;
}
@@ -424,7 +412,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendMapOk()
Debug(net, 9, "Client::status = ACTIVE");
my_client->status = STATUS_ACTIVE;
auto p = std::make_unique<Packet>(PACKET_CLIENT_MAP_OK);
auto p = std::make_unique<Packet>(my_client, PACKET_CLIENT_MAP_OK);
my_client->SendPacket(std::move(p));
return NETWORK_RECV_STATUS_OKAY;
}
@@ -434,7 +422,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendAck()
{
Debug(net, 9, "Client::SendAck()");
auto p = std::make_unique<Packet>(PACKET_CLIENT_ACK);
auto p = std::make_unique<Packet>(my_client, PACKET_CLIENT_ACK);
p->Send_uint32(_frame_counter);
p->Send_uint8 (my_client->token);
@@ -450,7 +438,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendCommand(const CommandPacke
{
Debug(net, 9, "Client::SendCommand(): cmd={}", cp.cmd);
auto p = std::make_unique<Packet>(PACKET_CLIENT_COMMAND);
auto p = std::make_unique<Packet>(my_client, PACKET_CLIENT_COMMAND);
my_client->NetworkGameSocketHandler::SendCommand(*p, cp);
my_client->SendPacket(std::move(p));
@@ -462,7 +450,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendChat(NetworkAction action,
{
Debug(net, 9, "Client::SendChat(): action={}, type={}, dest={}", action, type, dest);
auto p = std::make_unique<Packet>(PACKET_CLIENT_CHAT);
auto p = std::make_unique<Packet>(my_client, PACKET_CLIENT_CHAT);
p->Send_uint8 (action);
p->Send_uint8 (type);
@@ -479,28 +467,13 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendError(NetworkErrorCode err
{
Debug(net, 9, "Client::SendError(): errorno={}", errorno);
auto p = std::make_unique<Packet>(PACKET_CLIENT_ERROR);
auto p = std::make_unique<Packet>(my_client, PACKET_CLIENT_ERROR);
p->Send_uint8(errorno);
my_client->SendPacket(std::move(p));
return NETWORK_RECV_STATUS_OKAY;
}
/**
* Tell the server that we like to change the password of the company.
* @param password The new password.
*/
NetworkRecvStatus ClientNetworkGameSocketHandler::SendSetPassword(const std::string &password)
{
Debug(net, 9, "Client::SendSetPassword()");
auto p = std::make_unique<Packet>(PACKET_CLIENT_SET_PASSWORD);
p->Send_string(GenerateCompanyPasswordHash(password, _password_server_id, _password_game_seed));
my_client->SendPacket(std::move(p));
return NETWORK_RECV_STATUS_OKAY;
}
/**
* Tell the server that we like to change the name of the client.
* @param name The new name.
@@ -509,7 +482,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendSetName(const std::string
{
Debug(net, 9, "Client::SendSetName()");
auto p = std::make_unique<Packet>(PACKET_CLIENT_SET_NAME);
auto p = std::make_unique<Packet>(my_client, PACKET_CLIENT_SET_NAME);
p->Send_string(name);
my_client->SendPacket(std::move(p));
@@ -521,9 +494,9 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendSetName(const std::string
*/
NetworkRecvStatus ClientNetworkGameSocketHandler::SendQuit()
{
Debug(net, 9, "Client::SendSetName()");
Debug(net, 9, "Client::SendQuit()");
auto p = std::make_unique<Packet>(PACKET_CLIENT_QUIT);
auto p = std::make_unique<Packet>(my_client, PACKET_CLIENT_QUIT);
my_client->SendPacket(std::move(p));
return NETWORK_RECV_STATUS_OKAY;
@@ -538,7 +511,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendRCon(const std::string &pa
{
Debug(net, 9, "Client::SendRCon()");
auto p = std::make_unique<Packet>(PACKET_CLIENT_RCON);
auto p = std::make_unique<Packet>(my_client, PACKET_CLIENT_RCON);
p->Send_string(pass);
p->Send_string(command);
my_client->SendPacket(std::move(p));
@@ -548,15 +521,13 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendRCon(const std::string &pa
/**
* Ask the server to move us.
* @param company The company to move to.
* @param password The password of the company to move to.
*/
NetworkRecvStatus ClientNetworkGameSocketHandler::SendMove(CompanyID company, const std::string &password)
NetworkRecvStatus ClientNetworkGameSocketHandler::SendMove(CompanyID company)
{
Debug(net, 9, "Client::SendMove(): company={}", company);
auto p = std::make_unique<Packet>(PACKET_CLIENT_MOVE);
auto p = std::make_unique<Packet>(my_client, PACKET_CLIENT_MOVE);
p->Send_uint8(company);
p->Send_string(GenerateCompanyPasswordHash(password, _password_server_id, _password_game_seed));
my_client->SendPacket(std::move(p));
return NETWORK_RECV_STATUS_OKAY;
}
@@ -609,6 +580,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;
@@ -633,6 +605,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_CLIENT_INFO(Pac
ci->client_playas = playas;
ci->client_name = name;
ci->public_key = public_key;
citymania::SetClientListDirty();
InvalidateWindowData(WC_CLIENT_LIST, 0);
@@ -657,6 +630,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;
citymania::SetClientListDirty();
InvalidateWindowData(WC_CLIENT_LIST, 0);
@@ -692,6 +666,8 @@ 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
STR_NETWORK_ERROR_SERVER_ERROR, // NETWORK_ERROR_NO_AUTHENTICATION_METHOD_AVAILABLE
};
static_assert(lengthof(network_error_strings) == NETWORK_ERROR_END);
@@ -717,7 +693,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_ENCRYPTED) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
uint grf_count = p.Recv_uint8();
NetworkRecvStatus ret = NETWORK_RECV_STATUS_OKAY;
@@ -748,47 +724,64 @@ 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<NetworkAuthenticationPasswordRequest> request) override
{
if (!_network_join.server_password.empty()) {
request->Reply(_network_join.server_password);
} else {
ShowNetworkNeedPassword(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<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_NEED_COMPANY_PASSWORD(Packet &p)
NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_ENABLE_ENCRYPTION(Packet &p)
{
if (this->status < STATUS_JOIN || this->status >= STATUS_AUTH_COMPANY) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
Debug(net, 9, "Client::status = AUTH_COMPANY");
this->status = STATUS_AUTH_COMPANY;
if (this->status != STATUS_AUTH_GAME || this->authentication_handler == nullptr) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
Debug(net, 9, "Client::Receive_SERVER_NEED_COMPANY_PASSWORD()");
Debug(net, 9, "Client::Receive_SERVER_ENABLE_ENCRYPTION()");
_password_game_seed = p.Recv_uint32();
_password_server_id = p.Recv_string(NETWORK_SERVER_ID_LENGTH);
if (this->HasClientQuit()) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
if (!this->authentication_handler->ReceiveEnableEncryption(p)) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
if (!_network_join.company_password.empty()) {
return SendCompanyPassword(_network_join.company_password);
}
this->receive_encryption_handler = this->authentication_handler->CreateServerToClientEncryptionHandler();
this->send_encryption_handler = this->authentication_handler->CreateClientToServerEncryptionHandler();
this->authentication_handler = nullptr;
ShowNetworkNeedPassword(NETWORK_COMPANY_PASSWORD);
Debug(net, 9, "Client::status = ENCRYPTED");
this->status = STATUS_ENCRYPTED;
return NETWORK_RECV_STATUS_OKAY;
return this->SendIdentify();
}
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_ENCRYPTED || this->status >= STATUS_AUTHORIZED) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
Debug(net, 9, "Client::status = AUTHORIZED");
this->status = STATUS_AUTHORIZED;
@@ -796,10 +789,6 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_WELCOME(Packet
Debug(net, 9, "Client::Receive_SERVER_WELCOME(): client_id={}", _network_own_client_id);
/* Initialize the password hash salting variables, even if they were previously. */
_password_game_seed = p.Recv_uint32();
_password_server_id = p.Recv_string(NETWORK_SERVER_ID_LENGTH);
/* Start receiving the map */
return SendGetMap();
}
@@ -887,6 +876,10 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_MAP_DONE(Packet
/* The map is done downloading, load it */
ClearErrorMessages();
/* Set the abstract filetype. This is read during savegame load. */
_file_to_saveload.SetMode(SLO_LOAD, FT_SAVEGAME, DFT_GAME_FILE);
bool load_success = SafeLoad({}, SLO_LOAD, DFT_GAME_FILE, GM_NORMAL, NO_DIRECTORY, this->savegame);
this->savegame = nullptr;
@@ -921,7 +914,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_MAP_DONE(Packet
Debug(net, 9, "Client::join_status = REGISTERING");
_network_join_status = NETWORK_JOIN_STATUS_REGISTERING;
ShowJoinStatusWindow();
Command<CMD_COMPANY_CTRL>::SendNet(STR_NULL, _local_company, CCA_NEW, INVALID_COMPANY, CRR_NONE, INVALID_CLIENT_ID);
Command<CMD_COMPANY_CTRL>::Post(CCA_NEW, INVALID_COMPANY, CRR_NONE, _network_own_client_id);
}
} else {
/* take control over an existing company */
@@ -1226,26 +1219,14 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_CONFIG_UPDATE(P
_network_server_max_companies = p.Recv_uint8();
_network_server_name = p.Recv_string(NETWORK_NAME_LENGTH);
SetWindowClassesDirty(WC_CLIENT_LIST);
InvalidateWindowData(WC_CLIENT_LIST, 0);
Debug(net, 9, "Client::Receive_SERVER_CONFIG_UPDATE(): max_companies={}", _network_server_max_companies);
return NETWORK_RECV_STATUS_OKAY;
}
NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_COMPANY_UPDATE(Packet &p)
{
if (this->status < STATUS_ACTIVE) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
static_assert(sizeof(_network_company_passworded) <= sizeof(uint16_t));
_network_company_passworded = p.Recv_uint16();
SetWindowClassesDirty(WC_COMPANY);
Debug(net, 9, "Client::Receive_SERVER_COMPANY_UPDATE()");
return NETWORK_RECV_STATUS_OKAY;
}
/**
* Check the connection's state, i.e. is the connection still up?
*/
@@ -1302,12 +1283,10 @@ void NetworkClientSendRcon(const std::string &password, const std::string &comma
/**
* Notify the server of this client wanting to be moved to another company.
* @param company_id id of the company the client wishes to be moved to.
* @param pass the password, is only checked on the server end if a password is needed.
* @return void
*/
void NetworkClientRequestMove(CompanyID company_id, const std::string &pass)
void NetworkClientRequestMove(CompanyID company_id)
{
MyClient::SendMove(company_id, pass);
MyClient::SendMove(company_id);
}
/**
@@ -1316,7 +1295,7 @@ void NetworkClientRequestMove(CompanyID company_id, const std::string &pass)
*/
void NetworkClientsToSpectators(CompanyID cid)
{
Backup<CompanyID> cur_company(_current_company, FILE_LINE);
Backup<CompanyID> cur_company(_current_company);
/* If our company is changing owner, go to spectators */
if (cid == _local_company) SetLocalCompany(COMPANY_SPECTATOR);
@@ -1417,15 +1396,6 @@ void NetworkClientSendChat(NetworkAction action, DestType type, int dest, const
MyClient::SendChat(action, type, dest, msg, data);
}
/**
* Set/Reset company password on the client side.
* @param password Password to be set.
*/
void NetworkClientSetCompanyPassword(const std::string &password)
{
MyClient::SendSetPassword(password);
}
/**
* Tell whether the client has team members who they can chat to.
* @param cio client to check members of.
@@ -1464,7 +1434,7 @@ bool NetworkMaxCompaniesReached()
void SyncCMUser(const std::string &msg) {
uint user_id, role;
sscanf(msg.c_str() + 10, "%u %u", &user_id, &role);
_novarole = (role >= 50);
// _novarole = (role >= 50);
Debug(net, 1, "CityMania user synchronized: %u %u", user_id, role);
}
+9 -12
View File
@@ -15,17 +15,18 @@
/** Class for handling the client side of the game connection. */
class ClientNetworkGameSocketHandler : public ZeroedMemoryAllocator, public NetworkGameSocketHandler {
private:
std::unique_ptr<class NetworkAuthenticationClientHandler> authentication_handler; ///< The handler for the authentication.
std::string connection_string; ///< Address we are connected to.
std::shared_ptr<struct PacketReader> savegame; ///< Packet reader for reading the savegame.
byte 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.
/** Status of the connection with the server. */
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_AUTH_COMPANY, ///< Last action was requesting company password.
STATUS_ENCRYPTED, ///< The game authentication has completed and from here on the connection to the server is encrypted.
STATUS_NEWGRFS_CHECK, ///< Last action was checking NewGRFs.
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, ///< The client is downloading the map.
@@ -44,8 +45,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_NEED_COMPANY_PASSWORD(Packet &p) override;
NetworkRecvStatus Receive_SERVER_AUTH_REQUEST(Packet &p) override;
NetworkRecvStatus Receive_SERVER_ENABLE_ENCRYPTION(Packet &p) override;
NetworkRecvStatus Receive_SERVER_WELCOME(Packet &p) override;
NetworkRecvStatus Receive_SERVER_WAIT(Packet &p) override;
NetworkRecvStatus Receive_SERVER_MAP_BEGIN(Packet &p) override;
@@ -65,12 +66,12 @@ protected:
NetworkRecvStatus Receive_SERVER_RCON(Packet &p) override;
NetworkRecvStatus Receive_SERVER_CHECK_NEWGRFS(Packet &p) override;
NetworkRecvStatus Receive_SERVER_MOVE(Packet &p) override;
NetworkRecvStatus Receive_SERVER_COMPANY_UPDATE(Packet &p) override;
NetworkRecvStatus Receive_SERVER_CONFIG_UPDATE(Packet &p) override;
static NetworkRecvStatus SendNewGRFsOk();
static NetworkRecvStatus SendGetMap();
static NetworkRecvStatus SendMapOk();
static NetworkRecvStatus SendIdentify();
void CheckConnection();
public:
ClientNetworkGameSocketHandler(SOCKET s, const std::string &connection_string);
@@ -85,14 +86,12 @@ public:
static NetworkRecvStatus SendQuit();
static NetworkRecvStatus SendAck();
static NetworkRecvStatus SendGamePassword(const std::string &password);
static NetworkRecvStatus SendCompanyPassword(const std::string &password);
static NetworkRecvStatus SendAuthResponse();
static NetworkRecvStatus SendChat(NetworkAction action, DestType type, int dest, const std::string &msg, int64_t data);
static NetworkRecvStatus SendSetPassword(const std::string &password);
static NetworkRecvStatus SendSetName(const std::string &name);
static NetworkRecvStatus SendRCon(const std::string &password, const std::string &command);
static NetworkRecvStatus SendMove(CompanyID company, const std::string &password);
static NetworkRecvStatus SendMove(CompanyID company);
static bool IsConnected();
@@ -105,7 +104,6 @@ public:
typedef ClientNetworkGameSocketHandler MyClient;
void NetworkClient_Connected();
void NetworkClientSetCompanyPassword(const std::string &password);
/** Information required to join a server. */
struct NetworkJoinInfo {
@@ -113,7 +111,6 @@ struct NetworkJoinInfo {
std::string connection_string; ///< The address of the server to join.
CompanyID company; ///< The company to join.
std::string server_password; ///< The password of the server to join.
std::string company_password; ///< The password of the company to join.
};
extern NetworkJoinInfo _network_join;
+4 -4
View File
@@ -180,8 +180,8 @@ static CommandQueue _local_execution_queue;
*/
static size_t FindCallbackIndex(CommandCallback *callback)
{
if (auto it = std::find(std::cbegin(_callback_table), std::cend(_callback_table), callback); it != std::cend(_callback_table)) {
return static_cast<size_t>(std::distance(std::cbegin(_callback_table), it));
if (auto it = std::ranges::find(_callback_table, callback); it != std::end(_callback_table)) {
return static_cast<size_t>(std::distance(std::begin(_callback_table), it));
}
return std::numeric_limits<size_t>::max();
@@ -386,7 +386,7 @@ const char *NetworkGameSocketHandler::ReceiveCommand(Packet &p, CommandPacket &c
cp.err_msg = p.Recv_uint16();
cp.data = _cmd_dispatch[cp.cmd].Sanitize(p.Recv_buffer());
byte callback = p.Recv_uint8();
uint8_t callback = p.Recv_uint8();
if (callback >= _callback_table.size() || _cmd_dispatch[cp.cmd].Unpack[callback] == nullptr) return "invalid callback";
cp.callback = _callback_table[callback];
@@ -458,7 +458,7 @@ template <class T>
static inline void SanitizeSingleStringHelper([[maybe_unused]] CommandFlags cmd_flags, T &data)
{
if constexpr (std::is_same_v<std::string, T>) {
data = StrMakeValid(data.substr(0, NETWORK_COMPANY_NAME_LENGTH), (!_network_server && cmd_flags & CMD_STR_CTRL) != 0 ? SVS_ALLOW_CONTROL_CODE | SVS_REPLACE_WITH_QUESTION_MARK : SVS_REPLACE_WITH_QUESTION_MARK);
data = StrMakeValid(data, (!_network_server && HasFlag(cmd_flags, CMD_STR_CTRL)) ? SVS_ALLOW_CONTROL_CODE | SVS_REPLACE_WITH_QUESTION_MARK : SVS_REPLACE_WITH_QUESTION_MARK);
}
}
+36 -45
View File
@@ -13,6 +13,7 @@
#include "../game/game.hpp"
#include "../window_func.h"
#include "../error.h"
#include "../fileio_func.h"
#include "../base_media_base.h"
#include "../settings_type.h"
#include "network_content.h"
@@ -62,16 +63,14 @@ bool ClientNetworkContentSocketHandler::Receive_SERVER_INFO(Packet &p)
ci->description = p.Recv_string(NETWORK_CONTENT_DESC_LENGTH, SVS_REPLACE_WITH_QUESTION_MARK | SVS_ALLOW_NEWLINE);
ci->unique_id = p.Recv_uint32();
for (size_t j = 0; j < ci->md5sum.size(); j++) {
ci->md5sum[j] = p.Recv_uint8();
}
p.Recv_bytes(ci->md5sum);
uint dependency_count = p.Recv_uint8();
ci->dependencies.reserve(dependency_count);
for (uint i = 0; i < dependency_count; i++) {
ContentID dependency_cid = (ContentID)p.Recv_uint32();
ci->dependencies.push_back(dependency_cid);
this->reverse_dependency_map.insert({ dependency_cid, ci->id });
this->reverse_dependency_map.emplace(dependency_cid, ci->id);
}
uint tag_count = p.Recv_uint8();
@@ -204,8 +203,8 @@ void ClientNetworkContentSocketHandler::RequestContentList(ContentType type)
this->Connect();
auto p = std::make_unique<Packet>(PACKET_CONTENT_CLIENT_INFO_LIST);
p->Send_uint8 ((byte)type);
auto p = std::make_unique<Packet>(this, PACKET_CONTENT_CLIENT_INFO_LIST);
p->Send_uint8 ((uint8_t)type);
p->Send_uint32(0xffffffff);
p->Send_uint8 (1);
p->Send_string("vanilla");
@@ -238,9 +237,9 @@ void ClientNetworkContentSocketHandler::RequestContentList(uint count, const Con
* A packet begins with the packet size and a byte for the type.
* Then this packet adds a uint16_t for the count in this packet.
* The rest of the packet can be used for the IDs. */
uint p_count = std::min<uint>(count, (TCP_MTU - sizeof(PacketSize) - sizeof(byte) - sizeof(uint16_t)) / sizeof(uint32_t));
uint p_count = std::min<uint>(count, (TCP_MTU - sizeof(PacketSize) - sizeof(uint8_t) - sizeof(uint16_t)) / sizeof(uint32_t));
auto p = std::make_unique<Packet>(PACKET_CONTENT_CLIENT_INFO_ID, TCP_MTU);
auto p = std::make_unique<Packet>(this, PACKET_CONTENT_CLIENT_INFO_ID, TCP_MTU);
p->Send_uint16(p_count);
for (uint i = 0; i < p_count; i++) {
@@ -265,20 +264,17 @@ void ClientNetworkContentSocketHandler::RequestContentList(ContentVector *cv, bo
this->Connect();
assert(cv->size() < 255);
assert(cv->size() < (TCP_MTU - sizeof(PacketSize) - sizeof(byte) - sizeof(uint8_t)) /
assert(cv->size() < (TCP_MTU - sizeof(PacketSize) - sizeof(uint8_t) - sizeof(uint8_t)) /
(sizeof(uint8_t) + sizeof(uint32_t) + (send_md5sum ? MD5_HASH_BYTES : 0)));
auto p = std::make_unique<Packet>(send_md5sum ? PACKET_CONTENT_CLIENT_INFO_EXTID_MD5 : PACKET_CONTENT_CLIENT_INFO_EXTID, TCP_MTU);
auto p = std::make_unique<Packet>(this, send_md5sum ? PACKET_CONTENT_CLIENT_INFO_EXTID_MD5 : PACKET_CONTENT_CLIENT_INFO_EXTID, TCP_MTU);
p->Send_uint8((uint8_t)cv->size());
for (const ContentInfo *ci : *cv) {
p->Send_uint8((byte)ci->type);
p->Send_uint8((uint8_t)ci->type);
p->Send_uint32(ci->unique_id);
if (!send_md5sum) continue;
for (size_t j = 0; j < ci->md5sum.size(); j++) {
p->Send_uint8(ci->md5sum[j]);
}
p->Send_bytes(ci->md5sum);
}
this->SendPacket(std::move(p));
@@ -363,9 +359,9 @@ void ClientNetworkContentSocketHandler::DownloadSelectedContentFallback(const Co
* A packet begins with the packet size and a byte for the type.
* Then this packet adds a uint16_t for the count in this packet.
* The rest of the packet can be used for the IDs. */
uint p_count = std::min<uint>(count, (TCP_MTU - sizeof(PacketSize) - sizeof(byte) - sizeof(uint16_t)) / sizeof(uint32_t));
uint p_count = std::min<uint>(count, (TCP_MTU - sizeof(PacketSize) - sizeof(uint8_t) - sizeof(uint16_t)) / sizeof(uint32_t));
auto p = std::make_unique<Packet>(PACKET_CONTENT_CLIENT_CONTENT, TCP_MTU);
auto p = std::make_unique<Packet>(this, PACKET_CONTENT_CLIENT_CONTENT, TCP_MTU);
p->Send_uint16(p_count);
for (uint i = 0; i < p_count; i++) {
@@ -408,19 +404,19 @@ static bool GunzipFile(const ContentInfo *ci)
bool ret = true;
/* Need to open the file with fopen() to support non-ASCII on Windows. */
FILE *ftmp = fopen(GetFullFilename(ci, true).c_str(), "rb");
if (ftmp == nullptr) return false;
auto ftmp = FileHandle::Open(GetFullFilename(ci, true), "rb");
if (!ftmp.has_value()) return false;
/* Duplicate the handle, and close the FILE*, to avoid double-closing the handle later. */
int fdup = dup(fileno(ftmp));
int fdup = dup(fileno(*ftmp));
gzFile fin = gzdopen(fdup, "rb");
fclose(ftmp);
ftmp.reset();
FILE *fout = fopen(GetFullFilename(ci, false).c_str(), "wb");
auto fout = FileHandle::Open(GetFullFilename(ci, false), "wb");
if (fin == nullptr || fout == nullptr) {
if (fin == nullptr || !fout.has_value()) {
ret = false;
} else {
byte buff[8192];
uint8_t buff[8192];
for (;;) {
int read = gzread(fin, buff, sizeof(buff));
if (read == 0) {
@@ -441,7 +437,7 @@ static bool GunzipFile(const ContentInfo *ci)
if (errnum != 0 && errnum != Z_STREAM_END) ret = false;
break;
}
if (read < 0 || (size_t)read != fwrite(buff, 1, read, fout)) {
if (read < 0 || static_cast<size_t>(read) != fwrite(buff, 1, read, *fout)) {
/* If gzread() returns -1, there was an error in archive */
ret = false;
break;
@@ -457,7 +453,6 @@ static bool GunzipFile(const ContentInfo *ci)
/* Failing gzdopen does not close the passed file descriptor. */
close(fdup);
}
if (fout != nullptr) fclose(fout);
return ret;
#else
@@ -472,14 +467,14 @@ static bool GunzipFile(const ContentInfo *ci)
* @param amount The number of bytes to write.
* @return The number of bytes that were written.
*/
static inline ssize_t TransferOutFWrite(FILE *file, const char *buffer, size_t amount)
static inline ssize_t TransferOutFWrite(std::optional<FileHandle> &file, const char *buffer, size_t amount)
{
return fwrite(buffer, 1, amount, file);
return fwrite(buffer, 1, amount, *file);
}
bool ClientNetworkContentSocketHandler::Receive_SERVER_CONTENT(Packet &p)
{
if (this->curFile == nullptr) {
if (!this->curFile.has_value()) {
delete this->curInfo;
/* When we haven't opened a file this must be our first packet with metadata. */
this->curInfo = new ContentInfo;
@@ -495,12 +490,11 @@ bool ClientNetworkContentSocketHandler::Receive_SERVER_CONTENT(Packet &p)
} else {
/* We have a file opened, thus are downloading internal content */
size_t toRead = p.RemainingBytesToTransfer();
if (toRead != 0 && (size_t)p.TransferOut(TransferOutFWrite, this->curFile) != toRead) {
if (toRead != 0 && static_cast<size_t>(p.TransferOut(TransferOutFWrite, std::ref(this->curFile))) != toRead) {
CloseWindowById(WC_NETWORK_STATUS_WINDOW, WN_NETWORK_STATUS_WINDOW_CONTENT_DOWNLOAD);
ShowErrorMessage(STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD, STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD_FILE_NOT_WRITABLE, WL_ERROR);
this->CloseConnection();
fclose(this->curFile);
this->curFile = nullptr;
this->curFile.reset();
return false;
}
@@ -528,7 +522,7 @@ bool ClientNetworkContentSocketHandler::BeforeDownload()
if (this->curInfo->filesize != 0) {
/* The filesize is > 0, so we are going to download it */
std::string filename = GetFullFilename(this->curInfo, true);
if (filename.empty() || (this->curFile = fopen(filename.c_str(), "wb")) == nullptr) {
if (filename.empty() || !(this->curFile = FileHandle::Open(filename, "wb")).has_value()) {
/* Unless that fails of course... */
CloseWindowById(WC_NETWORK_STATUS_WINDOW, WN_NETWORK_STATUS_WINDOW_CONTENT_DOWNLOAD);
ShowErrorMessage(STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD, STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD_FILE_NOT_WRITABLE, WL_ERROR);
@@ -546,11 +540,10 @@ void ClientNetworkContentSocketHandler::AfterDownload()
{
/* We read nothing; that's our marker for end-of-stream.
* Now gunzip the tar and make it known. */
fclose(this->curFile);
this->curFile = nullptr;
this->curFile.reset();
if (GunzipFile(this->curInfo)) {
unlink(GetFullFilename(this->curInfo, true).c_str());
FioRemove(GetFullFilename(this->curInfo, true));
Subdirectory sd = GetContentInfoSubDir(this->curInfo->type);
if (sd == NO_DIRECTORY) NOT_REACHED();
@@ -562,7 +555,7 @@ void ClientNetworkContentSocketHandler::AfterDownload()
if (this->curInfo->type == CONTENT_TYPE_BASE_MUSIC) {
/* Music can't be in a tar. So extract the tar! */
ExtractTar(fname, BASESET_DIR);
unlink(fname.c_str());
FioRemove(fname);
}
#ifdef __EMSCRIPTEN__
@@ -587,11 +580,10 @@ void ClientNetworkContentSocketHandler::OnFailure()
this->http_response.shrink_to_fit();
this->http_response_index = -2;
if (this->curFile != nullptr) {
if (this->curFile.has_value()) {
this->OnDownloadProgress(this->curInfo, -1);
fclose(this->curFile);
this->curFile = nullptr;
this->curFile.reset();
}
/* If we fail, download the rest via the 'old' system. */
@@ -627,7 +619,7 @@ void ClientNetworkContentSocketHandler::OnReceiveData(std::unique_ptr<char[]> da
if (data != nullptr) {
/* We have data, so write it to the file. */
if (fwrite(data.get(), 1, length, this->curFile) != length) {
if (fwrite(data.get(), 1, length, *this->curFile) != length) {
/* Writing failed somehow, let try via the old method. */
this->OnFailure();
} else {
@@ -639,7 +631,7 @@ void ClientNetworkContentSocketHandler::OnReceiveData(std::unique_ptr<char[]> da
return;
}
if (this->curFile != nullptr) {
if (this->curFile.has_value()) {
/* We've finished downloading a file. */
this->AfterDownload();
}
@@ -736,7 +728,7 @@ void ClientNetworkContentSocketHandler::OnReceiveData(std::unique_ptr<char[]> da
ClientNetworkContentSocketHandler::ClientNetworkContentSocketHandler() :
NetworkContentSocketHandler(),
http_response_index(-2),
curFile(nullptr),
curFile(std::nullopt),
curInfo(nullptr),
isConnecting(false),
isCancelled(false)
@@ -748,7 +740,6 @@ ClientNetworkContentSocketHandler::ClientNetworkContentSocketHandler() :
ClientNetworkContentSocketHandler::~ClientNetworkContentSocketHandler()
{
delete this->curInfo;
if (this->curFile != nullptr) fclose(this->curFile);
for (ContentInfo *ci : this->infos) delete ci;
}
@@ -847,7 +838,7 @@ void ClientNetworkContentSocketHandler::SendReceive()
void ClientNetworkContentSocketHandler::DownloadContentInfo(ContentID cid)
{
/* When we tried to download it already, don't try again */
if (std::find(this->requested.begin(), this->requested.end(), cid) != this->requested.end()) return;
if (std::ranges::find(this->requested, cid) != this->requested.end()) return;
this->requested.push_back(cid);
this->RequestContentList(1, &cid);
+2 -2
View File
@@ -74,7 +74,7 @@ protected:
std::vector<char> http_response; ///< The HTTP response to the requests we've been doing
int http_response_index; ///< Where we are, in the response, with handling it
FILE *curFile; ///< Currently downloaded file
std::optional<FileHandle> curFile; ///< Currently downloaded file
ContentInfo *curInfo; ///< Information about the currently downloaded file
bool isConnecting; ///< Whether we're connecting
bool isCancelled; ///< Whether the download has been cancelled
@@ -146,7 +146,7 @@ public:
/** Add a callback to this class */
void AddCallback(ContentCallback *cb) { include(this->callbacks, cb); }
/** Remove a callback */
void RemoveCallback(ContentCallback *cb) { this->callbacks.erase(std::find(this->callbacks.begin(), this->callbacks.end(), cb)); }
void RemoveCallback(ContentCallback *cb) { this->callbacks.erase(std::ranges::find(this->callbacks, cb)); }
};
extern ClientNetworkContentSocketHandler _network_content_client;
+43 -46
View File
@@ -94,14 +94,14 @@ static constexpr NWidgetPart _nested_network_content_download_status_window_widg
};
/** Window description for the download window */
static WindowDesc _network_content_download_status_window_desc(__FILE__, __LINE__,
static WindowDesc _network_content_download_status_window_desc(
WDP_CENTER, nullptr, 0, 0,
WC_NETWORK_STATUS_WINDOW, WC_NONE,
WDF_MODAL,
std::begin(_nested_network_content_download_status_window_widgets), std::end(_nested_network_content_download_status_window_widgets)
_nested_network_content_download_status_window_widgets
);
BaseNetworkContentDownloadStatusWindow::BaseNetworkContentDownloadStatusWindow(WindowDesc *desc) :
BaseNetworkContentDownloadStatusWindow::BaseNetworkContentDownloadStatusWindow(WindowDesc &desc) :
Window(desc), downloaded_bytes(0), downloaded_files(0), cur_id(UINT32_MAX)
{
_network_content_client.AddCallback(this);
@@ -116,21 +116,21 @@ void BaseNetworkContentDownloadStatusWindow::Close([[maybe_unused]] int data)
this->Window::Close();
}
void BaseNetworkContentDownloadStatusWindow::UpdateWidgetSize(WidgetID widget, Dimension *size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension *fill, [[maybe_unused]] Dimension *resize)
void BaseNetworkContentDownloadStatusWindow::UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize)
{
switch (widget) {
case WID_NCDS_PROGRESS_BAR:
SetDParamMaxDigits(0, 8);
SetDParamMaxDigits(1, 8);
SetDParamMaxDigits(2, 8);
*size = GetStringBoundingBox(STR_CONTENT_DOWNLOAD_PROGRESS_SIZE);
size = GetStringBoundingBox(STR_CONTENT_DOWNLOAD_PROGRESS_SIZE);
/* We need some spacing for the 'border' */
size->height += WidgetDimensions::scaled.frametext.Horizontal();
size->width += WidgetDimensions::scaled.frametext.Vertical();
size.height += WidgetDimensions::scaled.frametext.Horizontal();
size.width += WidgetDimensions::scaled.frametext.Vertical();
break;
case WID_NCDS_PROGRESS_TEXT:
size->height = GetCharacterHeight(FS_NORMAL) * 2 + WidgetDimensions::scaled.vsep_normal;
size.height = GetCharacterHeight(FS_NORMAL) * 2 + WidgetDimensions::scaled.vsep_normal;
break;
}
}
@@ -142,7 +142,7 @@ void BaseNetworkContentDownloadStatusWindow::DrawWidget(const Rect &r, WidgetID
/* Draw the % complete with a bar and a text */
DrawFrameRect(r, COLOUR_GREY, FR_BORDERONLY | FR_LOWERED);
Rect ir = r.Shrink(WidgetDimensions::scaled.bevel);
DrawFrameRect(ir.WithWidth((uint64_t)ir.Width() * this->downloaded_bytes / this->total_bytes, false), COLOUR_MAUVE, FR_NONE);
DrawFrameRect(ir.WithWidth((uint64_t)ir.Width() * this->downloaded_bytes / this->total_bytes, _current_text_dir == TD_RTL), COLOUR_MAUVE, FR_NONE);
SetDParam(0, this->downloaded_bytes);
SetDParam(1, this->total_bytes);
SetDParam(2, this->downloaded_bytes * 100LL / this->total_bytes);
@@ -197,7 +197,7 @@ public:
* Create a new download window based on a list of content information
* with flags whether to download them or not.
*/
NetworkContentDownloadStatusWindow() : BaseNetworkContentDownloadStatusWindow(&_network_content_download_status_window_desc)
NetworkContentDownloadStatusWindow() : BaseNetworkContentDownloadStatusWindow(_network_content_download_status_window_desc)
{
this->parent = FindWindowById(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_CONTENT_LIST);
}
@@ -334,8 +334,8 @@ class NetworkContentListWindow : public Window, ContentCallback {
static Listing last_sorting; ///< The last sorting setting.
static Filtering last_filtering; ///< The last filtering setting.
static GUIContentList::SortFunction * const sorter_funcs[]; ///< Sorter functions
static GUIContentList::FilterFunction * const filter_funcs[]; ///< Filter functions.
static const std::initializer_list<GUIContentList::SortFunction * const> sorter_funcs; ///< Sorter functions
static const std::initializer_list<GUIContentList::FilterFunction * const> filter_funcs; ///< Filter functions.
GUIContentList content; ///< List with content
bool auto_select; ///< Automatically select all content when the meta-data becomes available
ContentListFilterData filter_data; ///< Filter for content list
@@ -421,7 +421,6 @@ class NetworkContentListWindow : public Window, ContentCallback {
this->SetWidgetDisabledState(WID_NCL_SEARCH_EXTERNAL, this->auto_select && all_available);
this->FilterContentList();
this->content.shrink_to_fit();
this->content.RebuildDone();
this->SortContentList();
@@ -464,7 +463,7 @@ class NetworkContentListWindow : public Window, ContentCallback {
}
/** Filter content by tags/name */
static bool CDECL TagNameFilter(const ContentInfo * const *a, ContentListFilterData &filter)
static bool TagNameFilter(const ContentInfo * const *a, ContentListFilterData &filter)
{
if ((*a)->state == ContentInfo::SELECTED || (*a)->state == ContentInfo::AUTOSELECTED) return true;
@@ -476,7 +475,7 @@ class NetworkContentListWindow : public Window, ContentCallback {
}
/** Filter content by type, but still show content selected for download. */
static bool CDECL TypeOrSelectedFilter(const ContentInfo * const *a, ContentListFilterData &filter)
static bool TypeOrSelectedFilter(const ContentInfo * const *a, ContentListFilterData &filter)
{
if (filter.types.none()) return true;
if (filter.types[(*a)->type]) return true;
@@ -543,7 +542,7 @@ public:
* other types are only shown when content that depend on them are
* selected.
*/
NetworkContentListWindow(WindowDesc *desc, bool select_all, const std::bitset<CONTENT_TYPE_END> &types) :
NetworkContentListWindow(WindowDesc &desc, bool select_all, const std::bitset<CONTENT_TYPE_END> &types) :
Window(desc),
auto_select(select_all),
filter_editbox(EDITBOX_MAX_SIZE),
@@ -582,28 +581,28 @@ public:
void OnInit() override
{
this->checkbox_size = maxdim(maxdim(GetSpriteSize(SPR_BOX_EMPTY), GetSpriteSize(SPR_BOX_CHECKED)), GetSpriteSize(SPR_BLOT));
this->checkbox_size = maxdim(maxdim(GetScaledSpriteSize(SPR_BOX_EMPTY), GetScaledSpriteSize(SPR_BOX_CHECKED)), GetScaledSpriteSize(SPR_BLOT));
}
void UpdateWidgetSize(WidgetID widget, Dimension *size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension *fill, [[maybe_unused]] Dimension *resize) override
void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
{
switch (widget) {
case WID_NCL_CHECKBOX:
size->width = this->checkbox_size.width + padding.width;
size.width = this->checkbox_size.width + padding.width;
break;
case WID_NCL_TYPE: {
Dimension d = *size;
Dimension d = size;
for (int i = CONTENT_TYPE_BEGIN; i < CONTENT_TYPE_END; i++) {
d = maxdim(d, GetStringBoundingBox(STR_CONTENT_TYPE_BASE_GRAPHICS + i - CONTENT_TYPE_BASE_GRAPHICS));
}
size->width = d.width + padding.width;
size.width = d.width + padding.width;
break;
}
case WID_NCL_MATRIX:
resize->height = std::max(this->checkbox_size.height, (uint)GetCharacterHeight(FS_NORMAL)) + padding.height;
size->height = 10 * resize->height;
resize.height = std::max(this->checkbox_size.height, (uint)GetCharacterHeight(FS_NORMAL)) + padding.height;
size.height = 10 * resize.height;
break;
}
}
@@ -645,20 +644,17 @@ public:
*/
void DrawMatrix(const Rect &r) const
{
Rect checkbox = this->GetWidget<NWidgetBase>(WID_NCL_CHECKBOX)->GetCurrentRect();
Rect name = this->GetWidget<NWidgetBase>(WID_NCL_NAME)->GetCurrentRect().Shrink(WidgetDimensions::scaled.framerect);
Rect type = this->GetWidget<NWidgetBase>(WID_NCL_TYPE)->GetCurrentRect();
const Rect checkbox = this->GetWidget<NWidgetBase>(WID_NCL_CHECKBOX)->GetCurrentRect();
const Rect name = this->GetWidget<NWidgetBase>(WID_NCL_NAME)->GetCurrentRect().Shrink(WidgetDimensions::scaled.framerect);
const Rect type = this->GetWidget<NWidgetBase>(WID_NCL_TYPE)->GetCurrentRect().Shrink(WidgetDimensions::scaled.framerect);
/* Fill the matrix with the information */
int sprite_y_offset = (this->resize.step_height - this->checkbox_size.height) / 2;
int text_y_offset = (this->resize.step_height - GetCharacterHeight(FS_NORMAL)) / 2;
const uint step_height = this->GetWidget<NWidgetBase>(WID_NCL_MATRIX)->resize_y;
const int text_y_offset = WidgetDimensions::scaled.matrix.top + (step_height - WidgetDimensions::scaled.matrix.Vertical() - GetCharacterHeight(FS_NORMAL)) / 2;
Rect mr = r.WithHeight(this->resize.step_height);
auto iter = this->content.begin() + this->vscroll->GetPosition();
size_t last = this->vscroll->GetPosition() + this->vscroll->GetCapacity();
auto end = (last < this->content.size()) ? this->content.begin() + last : this->content.end();
for (/**/; iter != end; iter++) {
Rect mr = r.WithHeight(step_height);
auto [first, last] = this->vscroll->GetVisibleRangeIterators(this->content);
for (auto iter = first; iter != last; iter++) {
const ContentInfo *ci = *iter;
if (ci == this->selected) GfxFillRect(mr.Shrink(WidgetDimensions::scaled.bevel), PC_GREY);
@@ -673,13 +669,13 @@ public:
case ContentInfo::DOES_NOT_EXIST: sprite = SPR_BLOT; pal = PALETTE_TO_RED; break;
default: NOT_REACHED();
}
DrawSprite(sprite, pal, checkbox.left + (sprite == SPR_BLOT ? 3 : 2), mr.top + sprite_y_offset + (sprite == SPR_BLOT ? 0 : 1));
DrawSpriteIgnorePadding(sprite, pal, {checkbox.left, mr.top, checkbox.right, mr.bottom}, SA_CENTER);
StringID str = STR_CONTENT_TYPE_BASE_GRAPHICS + ci->type - CONTENT_TYPE_BASE_GRAPHICS;
DrawString(type.left, type.right, mr.top + text_y_offset, str, TC_BLACK, SA_HOR_CENTER);
DrawString(name.left, name.right, mr.top + text_y_offset, ci->name, TC_BLACK);
mr = mr.Translate(0, this->resize.step_height);
mr = mr.Translate(0, step_height);
}
}
@@ -743,6 +739,7 @@ public:
SetDParam(0, this->selected->filesize);
tr.top = DrawStringMultiLine(tr, STR_CONTENT_DETAIL_FILESIZE);
std::string_view list_separator = GetListSeparator();
if (!this->selected->dependencies.empty()) {
/* List dependencies */
std::string buf;
@@ -753,7 +750,7 @@ public:
const ContentInfo *ci = *iter;
if (ci->id != cid) continue;
if (!buf.empty()) buf += ", ";
if (!buf.empty()) buf += list_separator;
buf += (*iter)->name;
break;
}
@@ -766,7 +763,7 @@ public:
/* List all tags */
std::string buf;
for (auto &tag : this->selected->tags) {
if (!buf.empty()) buf += ", ";
if (!buf.empty()) buf += list_separator;
buf += tag;
}
SetDParamStr(0, buf);
@@ -782,7 +779,7 @@ public:
for (const ContentInfo *ci : tree) {
if (ci == this->selected || ci->state != ContentInfo::SELECTED) continue;
if (!buf.empty()) buf += ", ";
if (!buf.empty()) buf += list_separator;
buf += ci->name;
}
if (!buf.empty()) {
@@ -1013,13 +1010,13 @@ public:
Listing NetworkContentListWindow::last_sorting = {false, 1};
Filtering NetworkContentListWindow::last_filtering = {false, 0};
NetworkContentListWindow::GUIContentList::SortFunction * const NetworkContentListWindow::sorter_funcs[] = {
const std::initializer_list<NetworkContentListWindow::GUIContentList::SortFunction * const> NetworkContentListWindow::sorter_funcs = {
&StateSorter,
&TypeSorter,
&NameSorter,
};
NetworkContentListWindow::GUIContentList::FilterFunction * const NetworkContentListWindow::filter_funcs[] = {
const std::initializer_list<NetworkContentListWindow::GUIContentList::FilterFunction * const> NetworkContentListWindow::filter_funcs = {
&TagNameFilter,
&TypeOrSelectedFilter,
};
@@ -1058,13 +1055,13 @@ static constexpr NWidgetPart _nested_network_content_list_widgets[] = {
NWidget(NWID_HORIZONTAL),
NWidget(NWID_VERTICAL),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NCL_CHECKBOX), SetMinimalSize(13, 1), SetDataTip(STR_EMPTY, STR_NULL),
NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NCL_CHECKBOX), SetDataTip(STR_EMPTY, STR_NULL),
NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NCL_TYPE),
SetDataTip(STR_CONTENT_TYPE_CAPTION, STR_CONTENT_TYPE_CAPTION_TOOLTIP),
NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NCL_NAME), SetResize(1, 0), SetFill(1, 0),
SetDataTip(STR_CONTENT_NAME_CAPTION, STR_CONTENT_NAME_CAPTION_TOOLTIP),
EndContainer(),
NWidget(WWT_MATRIX, COLOUR_LIGHT_BLUE, WID_NCL_MATRIX), SetResize(1, 14), SetFill(1, 1), SetScrollbar(WID_NCL_SCROLLBAR), SetMatrixDataTip(1, 0, STR_CONTENT_MATRIX_TOOLTIP),
NWidget(WWT_MATRIX, COLOUR_LIGHT_BLUE, WID_NCL_MATRIX), SetResize(1, 1), SetFill(1, 1), SetScrollbar(WID_NCL_SCROLLBAR), SetMatrixDataTip(1, 0, STR_CONTENT_MATRIX_TOOLTIP),
EndContainer(),
NWidget(NWID_VSCROLLBAR, COLOUR_LIGHT_BLUE, WID_NCL_SCROLLBAR),
EndContainer(),
@@ -1116,11 +1113,11 @@ static constexpr NWidgetPart _nested_network_content_list_widgets[] = {
};
/** Window description of the content list */
static WindowDesc _network_content_list_desc(__FILE__, __LINE__,
static WindowDesc _network_content_list_desc(
WDP_CENTER, "list_content", 630, 460,
WC_NETWORK_WINDOW, WC_NONE,
0,
std::begin(_nested_network_content_list_widgets), std::end(_nested_network_content_list_widgets)
_nested_network_content_list_widgets
);
/**
@@ -1148,7 +1145,7 @@ void ShowNetworkContentListWindow(ContentVector *cv, ContentType type1, ContentT
}
CloseWindowById(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_CONTENT_LIST);
new NetworkContentListWindow(&_network_content_list_desc, cv != nullptr, types);
new NetworkContentListWindow(_network_content_list_desc, cv != nullptr, types);
#else
ShowErrorMessage(STR_CONTENT_NO_ZLIB, STR_CONTENT_NO_ZLIB_SUB, WL_ERROR);
/* Connection failed... clean up the mess */
+2 -2
View File
@@ -30,10 +30,10 @@ public:
* Create the window with the given description.
* @param desc The description of the window.
*/
BaseNetworkContentDownloadStatusWindow(WindowDesc *desc);
BaseNetworkContentDownloadStatusWindow(WindowDesc &desc);
void Close([[maybe_unused]] int data = 0) override;
void UpdateWidgetSize(WidgetID widget, Dimension *size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension *fill, [[maybe_unused]] Dimension *resize) override;
void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override;
void DrawWidget(const Rect &r, WidgetID widget) const override;
void OnDownloadProgress(const ContentInfo *ci, int bytes) override;
};
+7 -7
View File
@@ -458,7 +458,7 @@ void ClientNetworkCoordinatorSocketHandler::Register()
this->Connect();
auto p = std::make_unique<Packet>(PACKET_COORDINATOR_SERVER_REGISTER);
auto p = std::make_unique<Packet>(this, PACKET_COORDINATOR_SERVER_REGISTER);
p->Send_uint8(NETWORK_COORDINATOR_VERSION);
p->Send_uint8(_settings_client.network.server_game_type);
p->Send_uint16(_settings_client.network.server_port);
@@ -480,7 +480,7 @@ void ClientNetworkCoordinatorSocketHandler::SendServerUpdate()
{
Debug(net, 6, "Sending server update to Game Coordinator");
auto p = std::make_unique<Packet>(PACKET_COORDINATOR_SERVER_UPDATE, TCP_MTU);
auto p = std::make_unique<Packet>(this, PACKET_COORDINATOR_SERVER_UPDATE, TCP_MTU);
p->Send_uint8(NETWORK_COORDINATOR_VERSION);
SerializeNetworkGameInfo(*p, GetCurrentNetworkServerGameInfo(), this->next_update.time_since_epoch() != std::chrono::nanoseconds::zero());
@@ -498,7 +498,7 @@ void ClientNetworkCoordinatorSocketHandler::GetListing()
_network_game_list_version++;
auto p = std::make_unique<Packet>(PACKET_COORDINATOR_CLIENT_LISTING);
auto p = std::make_unique<Packet>(this, PACKET_COORDINATOR_CLIENT_LISTING);
p->Send_uint8(NETWORK_COORDINATOR_VERSION);
p->Send_uint8(NETWORK_GAME_INFO_VERSION);
p->Send_string(_openttd_revision);
@@ -530,7 +530,7 @@ void ClientNetworkCoordinatorSocketHandler::ConnectToServer(const std::string &i
this->Connect();
auto p = std::make_unique<Packet>(PACKET_COORDINATOR_CLIENT_CONNECT);
auto p = std::make_unique<Packet>(this, PACKET_COORDINATOR_CLIENT_CONNECT);
p->Send_uint8(NETWORK_COORDINATOR_VERSION);
p->Send_string(invite_code);
@@ -547,7 +547,7 @@ void ClientNetworkCoordinatorSocketHandler::ConnectFailure(const std::string &to
/* Connecter will destroy itself. */
this->game_connecter = nullptr;
auto p = std::make_unique<Packet>(PACKET_COORDINATOR_SERCLI_CONNECT_FAILED);
auto p = std::make_unique<Packet>(this, PACKET_COORDINATOR_SERCLI_CONNECT_FAILED);
p->Send_uint8(NETWORK_COORDINATOR_VERSION);
p->Send_string(token);
p->Send_uint8(tracking_number);
@@ -578,7 +578,7 @@ void ClientNetworkCoordinatorSocketHandler::ConnectSuccess(const std::string &to
} else {
/* The client informs the Game Coordinator about the success. The server
* doesn't have to, as it is implied by the client telling. */
auto p = std::make_unique<Packet>(PACKET_COORDINATOR_CLIENT_CONNECTED);
auto p = std::make_unique<Packet>(this, PACKET_COORDINATOR_CLIENT_CONNECTED);
p->Send_uint8(NETWORK_COORDINATOR_VERSION);
p->Send_string(token);
this->SendPacket(std::move(p));
@@ -606,7 +606,7 @@ void ClientNetworkCoordinatorSocketHandler::ConnectSuccess(const std::string &to
*/
void ClientNetworkCoordinatorSocketHandler::StunResult(const std::string &token, uint8_t family, bool result)
{
auto p = std::make_unique<Packet>(PACKET_COORDINATOR_SERCLI_STUN_RESULT);
auto p = std::make_unique<Packet>(this, PACKET_COORDINATOR_SERCLI_STUN_RESULT);
p->Send_uint8(NETWORK_COORDINATOR_VERSION);
p->Send_string(token);
p->Send_uint8(family);
+495
View File
@@ -0,0 +1,495 @@
/*
* This file is part of OpenTTD.
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file network_crypto.cpp Implementation of the network specific cryptography helpers. */
#include "../stdafx.h"
#include "network_crypto_internal.h"
#include "core/packet.h"
#include "../3rdparty/monocypher/monocypher.h"
#include "../core/random_func.hpp"
#include "../debug.h"
#include "../string_func.h"
#include "../safeguards.h"
/**
* Call \c crypto_wipe for all the data in the given span.
* @param span The span to cryptographically wipe.
*/
static void crypto_wipe(std::span<uint8_t> span)
{
crypto_wipe(span.data(), span.size());
}
/** Ensure the derived keys do not get leaked when we're done with it. */
X25519DerivedKeys::~X25519DerivedKeys()
{
crypto_wipe(keys);
}
/**
* Get the key to encrypt or decrypt a message sent from the client to the server.
* @return The raw bytes of the key.
*/
std::span<const uint8_t> X25519DerivedKeys::ClientToServer() const
{
return std::span(this->keys.data(), X25519_KEY_SIZE);
}
/**
* Get the key to encrypt or decrypt a message sent from the server to the client.
* @return The raw bytes of the key.
*/
std::span<const uint8_t> X25519DerivedKeys::ServerToClient() const
{
return std::span(this->keys.data() + X25519_KEY_SIZE, X25519_KEY_SIZE);
}
/**
* Perform the actual key exchange.
* @param peer_public_key The public key chosen by the other participant of the key exchange.
* @param side Whether we are the client or server; used to hash the public key of us and the peer in the right order.
* @param our_secret_key The secret key of us.
* @param our_public_key The public key of us.
* @param extra_payload Extra payload to put into the hash function to create the derived keys.
* @return True when the key exchange has succeeded, false when an illegal public key was given.
*/
bool X25519DerivedKeys::Exchange(const X25519PublicKey &peer_public_key, X25519KeyExchangeSide side,
const X25519SecretKey &our_secret_key, const X25519PublicKey &our_public_key, std::string_view extra_payload)
{
X25519Key shared_secret;
crypto_x25519(shared_secret.data(), our_secret_key.data(), peer_public_key.data());
if (std::all_of(shared_secret.begin(), shared_secret.end(), [](auto v) { return v == 0; })) {
/* A shared secret of all zeros means that the peer tried to force the shared secret to a known constant. */
return false;
}
crypto_blake2b_ctx ctx;
crypto_blake2b_init(&ctx, this->keys.size());
crypto_blake2b_update(&ctx, shared_secret.data(), shared_secret.size());
switch (side) {
case X25519KeyExchangeSide::SERVER:
crypto_blake2b_update(&ctx, our_public_key.data(), our_public_key.size());
crypto_blake2b_update(&ctx, peer_public_key.data(), peer_public_key.size());
break;
case X25519KeyExchangeSide::CLIENT:
crypto_blake2b_update(&ctx, peer_public_key.data(), peer_public_key.size());
crypto_blake2b_update(&ctx, our_public_key.data(), our_public_key.size());
break;
default:
NOT_REACHED();
}
crypto_blake2b_update(&ctx, reinterpret_cast<const uint8_t *>(extra_payload.data()), extra_payload.size());
crypto_blake2b_final(&ctx, this->keys.data());
return true;
}
/**
* Encryption handler implementation for monocypther encryption after a X25519 key exchange.
*/
class X25519EncryptionHandler : public NetworkEncryptionHandler {
private:
crypto_aead_ctx context; ///< The actual encryption context.
public:
/**
* Create the encryption handler.
* @param key The key used for the encryption.
* @param nonce The nonce used for the encryption.
*/
X25519EncryptionHandler(const std::span<const uint8_t> key, const X25519Nonce &nonce)
{
assert(key.size() == X25519_KEY_SIZE);
crypto_aead_init_x(&this->context, key.data(), nonce.data());
}
/** Ensure the encryption context is wiped! */
~X25519EncryptionHandler()
{
crypto_wipe(&this->context, sizeof(this->context));
}
size_t MACSize() const override
{
return X25519_MAC_SIZE;
}
bool Decrypt(std::span<std::uint8_t> mac, std::span<std::uint8_t> message) override
{
return crypto_aead_read(&this->context, message.data(), mac.data(), nullptr, 0, message.data(), message.size()) == 0;
}
void Encrypt(std::span<std::uint8_t> mac, std::span<std::uint8_t> message) override
{
crypto_aead_write(&this->context, message.data(), mac.data(), nullptr, 0, message.data(), message.size());
}
};
/** Ensure the key does not get leaked when we're done with it. */
X25519Key::~X25519Key()
{
crypto_wipe(*this);
}
/**
* Create a new secret key that's filled with random bytes.
* @return The randomly filled key.
*/
/* static */ X25519SecretKey X25519SecretKey::CreateRandom()
{
X25519SecretKey secret_key;
RandomBytesWithFallback(secret_key);
return secret_key;
}
/**
* Create the public key associated with this secret key.
* @return The public key.
*/
X25519PublicKey X25519SecretKey::CreatePublicKey() const
{
X25519PublicKey public_key;
crypto_x25519_public_key(public_key.data(), this->data());
return public_key;
}
/**
* Create a new nonce that's filled with random bytes.
* @return The randomly filled nonce.
*/
/* static */ X25519Nonce X25519Nonce::CreateRandom()
{
X25519Nonce nonce;
RandomBytesWithFallback(nonce);
return nonce;
}
/** Ensure the nonce does not get leaked when we're done with it. */
X25519Nonce::~X25519Nonce()
{
crypto_wipe(*this);
}
/**
* Create the handler, and generate the public keys accordingly.
* @param secret_key The secret key to use for this handler. Defaults to secure random data.
*/
X25519AuthenticationHandler::X25519AuthenticationHandler(const X25519SecretKey &secret_key) :
our_secret_key(secret_key), our_public_key(secret_key.CreatePublicKey()),
key_exchange_nonce(X25519Nonce::CreateRandom()), encryption_nonce(X25519Nonce::CreateRandom())
{
}
/* virtual */ void X25519AuthenticationHandler::SendRequest(Packet &p)
{
p.Send_bytes(this->our_public_key);
p.Send_bytes(this->key_exchange_nonce);
}
/**
* Read the key exchange data from a \c Packet that came from the server,
* @param p The packet that has been received.
* @return True when the data seems correct.
*/
bool X25519AuthenticationHandler::ReceiveRequest(Packet &p)
{
if (p.RemainingBytesToTransfer() != X25519_KEY_SIZE + X25519_NONCE_SIZE) {
Debug(net, 1, "[crypto] Received auth response of illegal size; authentication aborted.");
return false;
}
p.Recv_bytes(this->peer_public_key);
p.Recv_bytes(this->key_exchange_nonce);
return true;
}
/**
* Perform the key exchange, and when that is correct fill the \c Packet with the appropriate data.
* @param p The packet that has to be sent.
* @param derived_key_extra_payload The extra payload to pass to the key exchange.
* @return Whether the key exchange was successful or not.
*/
bool X25519AuthenticationHandler::SendResponse(Packet &p, std::string_view derived_key_extra_payload)
{
if (!this->derived_keys.Exchange(this->peer_public_key, X25519KeyExchangeSide::CLIENT,
this->our_secret_key, this->our_public_key, derived_key_extra_payload)) {
Debug(net, 0, "[crypto] Peer sent an illegal public key; authentication aborted.");
return false;
}
X25519KeyExchangeMessage message;
RandomBytesWithFallback(message);
X25519Mac mac;
crypto_aead_lock(message.data(), mac.data(), this->derived_keys.ClientToServer().data(), this->key_exchange_nonce.data(),
this->our_public_key.data(), this->our_public_key.size(), message.data(), message.size());
p.Send_bytes(this->our_public_key);
p.Send_bytes(mac);
p.Send_bytes(message);
return true;
}
/**
* Get the public key the peer provided for the key exchange.
* @return The hexadecimal string representation of the peer's public key.
*/
std::string X25519AuthenticationHandler::GetPeerPublicKey() const
{
return FormatArrayAsHex(this->peer_public_key);
}
/**
* Send the initial nonce for the encrypted connection.
* @param p The packet to send the data in.
*/
void X25519AuthenticationHandler::SendEnableEncryption(struct Packet &p) const
{
p.Send_bytes(this->encryption_nonce);
}
/**
* Receive the initial nonce for the encrypted connection.
* @param p The packet to read the data from.
* @return \c true when enough bytes could be read for the nonce, otherwise \c false.
*/
bool X25519AuthenticationHandler::ReceiveEnableEncryption(struct Packet &p)
{
return p.Recv_bytes(this->encryption_nonce) == this->encryption_nonce.size();
}
std::unique_ptr<NetworkEncryptionHandler> X25519AuthenticationHandler::CreateClientToServerEncryptionHandler() const
{
return std::make_unique<X25519EncryptionHandler>(this->derived_keys.ClientToServer(), this->encryption_nonce);
}
std::unique_ptr<NetworkEncryptionHandler> X25519AuthenticationHandler::CreateServerToClientEncryptionHandler() const
{
return std::make_unique<X25519EncryptionHandler>(this->derived_keys.ServerToClient(), this->encryption_nonce);
}
/**
* Read the key exchange data from a \c Packet that came from the client, and check whether the client passes the key
* exchange successfully.
* @param p The packet that has been received.
* @param derived_key_extra_payload The extra payload to pass to the key exchange.
* @return Whether the authentication was successful or not.
*/
NetworkAuthenticationServerHandler::ResponseResult X25519AuthenticationHandler::ReceiveResponse(Packet &p, std::string_view derived_key_extra_payload)
{
if (p.RemainingBytesToTransfer() != X25519_KEY_SIZE + X25519_MAC_SIZE + X25519_KEY_EXCHANGE_MESSAGE_SIZE) {
Debug(net, 1, "[crypto] Received auth response of illegal size; authentication aborted.");
return NetworkAuthenticationServerHandler::NOT_AUTHENTICATED;
}
X25519KeyExchangeMessage message{};
X25519Mac mac;
p.Recv_bytes(this->peer_public_key);
p.Recv_bytes(mac);
p.Recv_bytes(message);
if (!this->derived_keys.Exchange(this->peer_public_key, X25519KeyExchangeSide::SERVER,
this->our_secret_key, this->our_public_key, derived_key_extra_payload)) {
Debug(net, 0, "[crypto] Peer sent an illegal public key; authentication aborted.");
return NetworkAuthenticationServerHandler::NOT_AUTHENTICATED;
}
if (crypto_aead_unlock(message.data(), mac.data(), this->derived_keys.ClientToServer().data(), this->key_exchange_nonce.data(),
this->peer_public_key.data(), this->peer_public_key.size(), message.data(), message.size()) != 0) {
/*
* The ciphertext and the message authentication code do not match with the encryption key.
* This is most likely an invalid password, or possibly a bug in the client.
*/
return NetworkAuthenticationServerHandler::NOT_AUTHENTICATED;
}
return NetworkAuthenticationServerHandler::AUTHENTICATED;
}
/* virtual */ NetworkAuthenticationClientHandler::RequestResult X25519PAKEClientHandler::ReceiveRequest(struct Packet &p)
{
bool success = this->X25519AuthenticationHandler::ReceiveRequest(p);
if (!success) return NetworkAuthenticationClientHandler::INVALID;
this->handler->AskUserForPassword(this->handler);
return NetworkAuthenticationClientHandler::AWAIT_USER_INPUT;
}
/**
* Get the secret key from the given string. If that is not a valid secret key, reset it with a random one.
* Furthermore update the public key so it is always in sync with the private key.
* @param secret_key The secret key to read/validate/fix.
* @param public_key The public key to update.
* @return The valid secret key.
*/
/* static */ X25519SecretKey X25519AuthorizedKeyClientHandler::GetValidSecretKeyAndUpdatePublicKey(std::string &secret_key, std::string &public_key)
{
X25519SecretKey key{};
if (!ConvertHexToBytes(secret_key, key)) {
if (secret_key.empty()) {
Debug(net, 3, "[crypto] Creating a new random key");
} else {
Debug(net, 0, "[crypto] Found invalid secret key, creating a new random key");
}
key = X25519SecretKey::CreateRandom();
secret_key = FormatArrayAsHex(key);
}
public_key = FormatArrayAsHex(key.CreatePublicKey());
return key;
}
/* virtual */ NetworkAuthenticationServerHandler::ResponseResult X25519AuthorizedKeyServerHandler::ReceiveResponse(Packet &p)
{
ResponseResult result = this->X25519AuthenticationHandler::ReceiveResponse(p, {});
if (result != AUTHENTICATED) return result;
std::string peer_public_key = this->GetPeerPublicKey();
return this->authorized_key_handler->IsAllowed(peer_public_key) ? AUTHENTICATED : NOT_AUTHENTICATED;
}
/* virtual */ NetworkAuthenticationClientHandler::RequestResult CombinedAuthenticationClientHandler::ReceiveRequest(struct Packet &p)
{
NetworkAuthenticationMethod method = static_cast<NetworkAuthenticationMethod>(p.Recv_uint8());
auto is_of_method = [method](Handler &handler) { return handler->GetAuthenticationMethod() == method; };
auto it = std::ranges::find_if(handlers, is_of_method);
if (it == handlers.end()) return INVALID;
this->current_handler = it->get();
Debug(net, 9, "Received {} authentication request", this->GetName());
return this->current_handler->ReceiveRequest(p);
}
/* virtual */ bool CombinedAuthenticationClientHandler::SendResponse(struct Packet &p)
{
Debug(net, 9, "Sending {} authentication response", this->GetName());
return this->current_handler->SendResponse(p);
}
/* virtual */ std::string_view CombinedAuthenticationClientHandler::GetName() const
{
return this->current_handler != nullptr ? this->current_handler->GetName() : "Unknown";
}
/* virtual */ NetworkAuthenticationMethod CombinedAuthenticationClientHandler::GetAuthenticationMethod() const
{
return this->current_handler != nullptr ? this->current_handler->GetAuthenticationMethod() : NETWORK_AUTH_METHOD_END;
}
/**
* Add the given sub-handler to this handler, if the handler can be used (e.g. there are authorized keys or there is a password).
* @param handler The handler to add.
*/
void CombinedAuthenticationServerHandler::Add(CombinedAuthenticationServerHandler::Handler &&handler)
{
/* Is the handler configured correctly, e.g. does it have a password? */
if (!handler->CanBeUsed()) return;
this->handlers.push_back(std::move(handler));
}
/* virtual */ void CombinedAuthenticationServerHandler::SendRequest(struct Packet &p)
{
Debug(net, 9, "Sending {} authentication request", this->GetName());
p.Send_uint8(this->handlers.back()->GetAuthenticationMethod());
this->handlers.back()->SendRequest(p);
}
/* virtual */ NetworkAuthenticationServerHandler::ResponseResult CombinedAuthenticationServerHandler::ReceiveResponse(struct Packet &p)
{
Debug(net, 9, "Receiving {} authentication response", this->GetName());
ResponseResult result = this->handlers.back()->ReceiveResponse(p);
if (result != NOT_AUTHENTICATED) return result;
this->handlers.pop_back();
return this->CanBeUsed() ? RETRY_NEXT_METHOD : NOT_AUTHENTICATED;
}
/* virtual */ std::string_view CombinedAuthenticationServerHandler::GetName() const
{
return this->CanBeUsed() ? this->handlers.back()->GetName() : "Unknown";
}
/* virtual */ NetworkAuthenticationMethod CombinedAuthenticationServerHandler::GetAuthenticationMethod() const
{
return this->CanBeUsed() ? this->handlers.back()->GetAuthenticationMethod() : NETWORK_AUTH_METHOD_END;
}
/* virtual */ bool CombinedAuthenticationServerHandler::CanBeUsed() const
{
return !this->handlers.empty();
}
/* virtual */ void NetworkAuthenticationPasswordRequestHandler::Reply(const std::string &password)
{
this->password = password;
this->SendResponse();
}
/**
* Ensures that the given secret key is valid, and when not overwrite it with a valid secret key. Then update the public key to be associated with the secret key.
* @param secret_key The location where the secret key is stored; can be overwritten when invalid.
* @param public_key The location where the public key is stored; can be overwritten when invalid.
*/
/* static */ void NetworkAuthenticationClientHandler::EnsureValidSecretKeyAndUpdatePublicKey(std::string &secret_key, std::string &public_key)
{
X25519AuthorizedKeyClientHandler::GetValidSecretKeyAndUpdatePublicKey(secret_key, public_key);
}
/**
* Create a NetworkAuthenticationClientHandler.
* @param password_handler The handler for when a request for password needs to be passed on to the user.
* @param secret_key The location where the secret key is stored; can be overwritten when invalid.
* @param public_key The location where the public key is stored; can be overwritten when invalid.
*/
/* static */ std::unique_ptr<NetworkAuthenticationClientHandler> NetworkAuthenticationClientHandler::Create(std::shared_ptr<NetworkAuthenticationPasswordRequestHandler> password_handler, std::string &secret_key, std::string &public_key)
{
auto secret = X25519AuthorizedKeyClientHandler::GetValidSecretKeyAndUpdatePublicKey(secret_key, public_key);
auto handler = std::make_unique<CombinedAuthenticationClientHandler>();
handler->Add(std::make_unique<X25519KeyExchangeOnlyClientHandler>(secret));
handler->Add(std::make_unique<X25519PAKEClientHandler>(secret, std::move(password_handler)));
handler->Add(std::make_unique<X25519AuthorizedKeyClientHandler>(secret));
return handler;
}
/**
* Create a NetworkAuthenticationServerHandler.
* @param password_provider Callback to provide the password handling. Must remain valid until the authentication has succeeded or failed. Can be \c nullptr to skip password checks.
* @param authorized_key_handler Callback to provide the authorized key handling. Must remain valid until the authentication has succeeded or failed. Can be \c nullptr to skip authorized key checks.
* @param client_supported_method_mask Bitmask of the methods that are supported by the client. Defaults to support of all methods.
*/
std::unique_ptr<NetworkAuthenticationServerHandler> NetworkAuthenticationServerHandler::Create(const NetworkAuthenticationPasswordProvider *password_provider, const NetworkAuthenticationAuthorizedKeyHandler *authorized_key_handler, NetworkAuthenticationMethodMask client_supported_method_mask)
{
auto secret = X25519SecretKey::CreateRandom();
auto handler = std::make_unique<CombinedAuthenticationServerHandler>();
if (password_provider != nullptr && HasBit(client_supported_method_mask, NETWORK_AUTH_METHOD_X25519_PAKE)) {
handler->Add(std::make_unique<X25519PAKEServerHandler>(secret, password_provider));
}
if (authorized_key_handler != nullptr && HasBit(client_supported_method_mask, NETWORK_AUTH_METHOD_X25519_AUTHORIZED_KEY)) {
handler->Add(std::make_unique<X25519AuthorizedKeyServerHandler>(secret, authorized_key_handler));
}
if (!handler->CanBeUsed() && HasBit(client_supported_method_mask, NETWORK_AUTH_METHOD_X25519_KEY_EXCHANGE_ONLY)) {
/* Fall back to the plain handler when neither password, nor authorized keys are configured. */
handler->Add(std::make_unique<X25519KeyExchangeOnlyServerHandler>(secret));
}
return handler;
}
+302
View File
@@ -0,0 +1,302 @@
/*
* This file is part of OpenTTD.
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @file network_crypto.h Crypto specific bits of the network handling.
*
* This provides a set of functionality to perform authentication combined with a key exchange,
* to create a shared secret as well as encryption using those shared secrets.
*
* For the authentication/key exchange, the server determines the available methods and creates
* the appropriate \c NetworkAuthenticationServerHandler. This will be used to create a request
* for the client, which instantiates a \c NetworkAuthenticationClientHandler to handle that
* request.
* At the moment there are three types of request: key exchange only, password-authenticated key
* exchange (PAKE) and authorized keys. When the request is for a password, the user is asked
* for the password via an essentially asynchronous callback from the client handler. For the
* other requests no input from the user is needed, and these are immediately ready to generate
* the response for the server.
*
* The server will validate the response resulting in either the user being authenticated or not.
* When the user failed authentication, there might be a possibility to retry. For example when
* the server has configured authorized keys and passwords; when the client fails with the
* authorized keys, it will retry with the password.
*
* Once the key exchange/authentication has been done, the server can signal the client to
* upgrade the network connection to use encryption using the shared secret of the key exchange.
*/
#ifndef NETWORK_CRYPTO_H
#define NETWORK_CRYPTO_H
#include "network_type.h"
/**
* Base class for handling the encryption (or decryption) of a network connection.
*/
class NetworkEncryptionHandler {
public:
virtual ~NetworkEncryptionHandler() = default;
/**
* Get the size of the MAC (Message Authentication Code) used by the underlying encryption protocol.
* @return The size, in bytes, of the MACs.
*/
virtual size_t MACSize() const = 0;
/**
* Decrypt the given message in-place, validating against the given MAC.
* @param mac The message authentication code (MAC).
* @param message The location of the message to decrypt.
* @return Whether decryption and authentication/validation of the message succeeded.
*/
virtual bool Decrypt(std::span<std::uint8_t> mac, std::span<std::uint8_t> message) = 0;
/**
* Encrypt the given message in-place, and write the associated MAC.
* @param mac The location to write the message authentication code (MAC) to.
* @param message The location of the message to encrypt.
*/
virtual void Encrypt(std::span<std::uint8_t> mac, std::span<std::uint8_t> message) = 0;
};
/**
* Callback interface for requests for passwords in the context of network authentication.
*/
class NetworkAuthenticationPasswordRequest {
public:
virtual ~NetworkAuthenticationPasswordRequest() = default;
/**
* Reply to the request with the given password.
*/
virtual void Reply(const std::string &password) = 0;
};
/**
* Callback interface for client implementations to provide the handling of the password requests.
*/
class NetworkAuthenticationPasswordRequestHandler : public NetworkAuthenticationPasswordRequest {
protected:
friend class X25519PAKEClientHandler;
std::string password; ///< The entered password.
public:
virtual void Reply(const std::string &password) override;
/**
* Callback to trigger sending the response for the password request.
*/
virtual void SendResponse() = 0;
/**
* Callback to trigger asking the user for the password.
* @param request The request to the user, to which it can reply with the password.
*/
virtual void AskUserForPassword(std::shared_ptr<NetworkAuthenticationPasswordRequest> request) = 0;
};
/**
* Callback interface for server implementations to provide the current password.
*/
class NetworkAuthenticationPasswordProvider {
public:
virtual ~NetworkAuthenticationPasswordProvider() = default;
/**
* Callback to return the password where to validate against.
* @return \c std::string_view of the current password; an empty view means no password check will be performed.
*/
virtual std::string_view GetPassword() const = 0;
};
/**
* Default implementation of the password provider.
*/
class NetworkAuthenticationDefaultPasswordProvider : public NetworkAuthenticationPasswordProvider {
private:
const std::string *password; ///< The password to check against.
public:
/**
* Create the provider with the pointer to the password that is to be used. A pointer, so this can handle
* situations where the password gets changed over time.
* @param password The reference to the configured password.
*/
NetworkAuthenticationDefaultPasswordProvider(const std::string &password) : password(&password) {}
std::string_view GetPassword() const override { return *this->password; };
};
/**
* Callback interface for server implementations to provide the authorized key validation.
*/
class NetworkAuthenticationAuthorizedKeyHandler {
public:
virtual ~NetworkAuthenticationAuthorizedKeyHandler() = default;
/**
* Check whether the key handler can be used, i.e. whether there are authorized keys to check against.
* @return \c true when it can be used, otherwise \c false.
*/
virtual bool CanBeUsed() const = 0;
/**
* Check whether the given public key of the peer is allowed in.
* @param peer_public_key The public key of the peer to check against.
* @return \c true when the key is allowed, otherwise \c false.
*/
virtual bool IsAllowed(std::string_view peer_public_key) const = 0;
};
/**
* Default implementation for the authorized key handler.
*/
class NetworkAuthenticationDefaultAuthorizedKeyHandler : public NetworkAuthenticationAuthorizedKeyHandler {
private:
const NetworkAuthorizedKeys *authorized_keys; ///< The authorized keys to check against.
public:
/**
* Create the handler that uses the given authorized keys to check against.
* @param authorized_keys The reference to the authorized keys to check against.
*/
NetworkAuthenticationDefaultAuthorizedKeyHandler(const NetworkAuthorizedKeys &authorized_keys) : authorized_keys(&authorized_keys) {}
bool CanBeUsed() const override { return !this->authorized_keys->empty(); }
bool IsAllowed(std::string_view peer_public_key) const override { return authorized_keys->Contains(peer_public_key); }
};
/** 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. 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)
};
/** The mask of authentication methods that can be used. */
using NetworkAuthenticationMethodMask = uint16_t;
/**
* Base class for cryptographic authentication handlers.
*/
class NetworkAuthenticationHandler {
public:
virtual ~NetworkAuthenticationHandler() = default;
/**
* Get the name of the handler for debug messages.
* @return The name of the handler.
*/
virtual std::string_view GetName() const = 0;
/**
* Get the method this handler is providing functionality for.
* @return The \c NetworkAuthenticationMethod.
*/
virtual NetworkAuthenticationMethod GetAuthenticationMethod() const = 0;
/**
* Create a \a NetworkEncryptionHandler to encrypt or decrypt messages from the client to the server.
* @return The handler for the client to server encryption.
*/
virtual std::unique_ptr<NetworkEncryptionHandler> CreateClientToServerEncryptionHandler() const = 0;
/**
* Create a \a NetworkEncryptionHandler to encrypt or decrypt messages from the server to the client.
* @return The handler for the server to client encryption.
*/
virtual std::unique_ptr<NetworkEncryptionHandler> CreateServerToClientEncryptionHandler() const = 0;
};
/**
* Base class for client side cryptographic authentication handlers.
*/
class NetworkAuthenticationClientHandler : public NetworkAuthenticationHandler {
public:
/** The processing result of receiving a request. */
enum RequestResult {
AWAIT_USER_INPUT, ///< We have requested some user input, but must wait on that.
READY_FOR_RESPONSE, ///< We do not have to wait for user input, and can immediately respond to the server.
INVALID, ///< We have received an invalid request.
};
/**
* Read a request from the server.
* @param p The packet to read the request from.
* @return True when valid, otherwise false.
*/
virtual RequestResult ReceiveRequest(struct Packet &p) = 0;
/**
* Create the response to send to the server.
* @param p The packet to write the response from.
* @return True when a valid packet was made, otherwise false.
*/
virtual bool SendResponse(struct Packet &p) = 0;
/**
* Read the request to enable encryption from the server.
* @param p The request from the server.
*/
virtual bool ReceiveEnableEncryption(struct Packet &p) = 0;
static void EnsureValidSecretKeyAndUpdatePublicKey(std::string &secret_key, std::string &public_key);
static std::unique_ptr<NetworkAuthenticationClientHandler> Create(std::shared_ptr<NetworkAuthenticationPasswordRequestHandler> password_handler, std::string &secret_key, std::string &public_key);
};
/**
* Base class for server side cryptographic authentication handlers.
*/
class NetworkAuthenticationServerHandler : public NetworkAuthenticationHandler {
public:
/** The processing result of receiving a response. */
enum ResponseResult {
AUTHENTICATED, ///< The client was authenticated successfully.
NOT_AUTHENTICATED, ///< All authentications for this handler have been exhausted.
RETRY_NEXT_METHOD, ///< The client failed to authenticate, but there is another method to try.
};
/**
* Create the request to send to the client.
* @param p The packet to write the request to.
*/
virtual void SendRequest(struct Packet &p) = 0;
/**
* Read the response from the client.
* @param p The packet to read the response from.
* @return The \c ResponseResult describing the result.
*/
virtual ResponseResult ReceiveResponse(struct Packet &p) = 0;
/**
* Create the request to enable encryption to the client.
* @param p The packet to write the enable encryption request to.
*/
virtual void SendEnableEncryption(struct Packet &p) = 0;
/**
* Checks whether this handler can be used with the current configuration.
* For example when there is no password, the handler cannot be used.
* @return True when this handler can be used.
*/
virtual bool CanBeUsed() const = 0;
/**
* Get the public key the peer provided during the authentication.
* @return The hexadecimal string representation of the peer's public key.
*/
virtual std::string GetPeerPublicKey() const = 0;
static std::unique_ptr<NetworkAuthenticationServerHandler> Create(const NetworkAuthenticationPasswordProvider *password_provider, const NetworkAuthenticationAuthorizedKeyHandler *authorized_key_handler, NetworkAuthenticationMethodMask client_supported_method_mask = ~static_cast<NetworkAuthenticationMethodMask>(0));
};
#endif /* NETWORK_CRYPTO_H */
+353
View File
@@ -0,0 +1,353 @@
/*
* This file is part of OpenTTD.
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file network_crypto_internal.h Internal bits to the crypto of the network handling. */
#ifndef NETWORK_CRYPTO_INTERNAL_H
#define NETWORK_CRYPTO_INTERNAL_H
#include "network_crypto.h"
/** The number of bytes the public and secret keys are in X25519. */
constexpr size_t X25519_KEY_SIZE = 32;
/** The number of bytes the nonces are in X25519. */
constexpr size_t X25519_NONCE_SIZE = 24;
/** The number of bytes the message authentication codes are in X25519. */
constexpr size_t X25519_MAC_SIZE = 16;
/** The number of bytes the (random) payload of the authentication message has. */
constexpr size_t X25519_KEY_EXCHANGE_MESSAGE_SIZE = 8;
/** Container for a X25519 key that is automatically crypto-wiped when destructed. */
struct X25519Key : std::array<uint8_t, X25519_KEY_SIZE> {
~X25519Key();
};
/** Container for a X25519 public key. */
struct X25519PublicKey : X25519Key {
};
/** Container for a X25519 secret key. */
struct X25519SecretKey : X25519Key {
static X25519SecretKey CreateRandom();
X25519PublicKey CreatePublicKey() const;
};
/** Container for a X25519 nonce that is automatically crypto-wiped when destructed. */
struct X25519Nonce : std::array<uint8_t, X25519_NONCE_SIZE> {
static X25519Nonce CreateRandom();
~X25519Nonce();
};
/** Container for a X25519 message authentication code. */
using X25519Mac = std::array<uint8_t, X25519_MAC_SIZE>;
/** Container for a X25519 key exchange message. */
using X25519KeyExchangeMessage = std::array<uint8_t, X25519_KEY_EXCHANGE_MESSAGE_SIZE>;
/** The side of the key exchange. */
enum class X25519KeyExchangeSide {
CLIENT, ///< We are the client.
SERVER, ///< We are the server.
};
/**
* Container for the keys that derived from the X25519 key exchange mechanism. This mechanism derives
* a key to encrypt both the client-to-server and a key to encrypt server-to-client communication.
*/
class X25519DerivedKeys {
private:
/** Single contiguous buffer to store the derived keys in, as they are generated as a single hash. */
std::array<uint8_t, X25519_KEY_SIZE + X25519_KEY_SIZE> keys;
public:
~X25519DerivedKeys();
std::span<const uint8_t> ClientToServer() const;
std::span<const uint8_t> ServerToClient() const;
bool Exchange(const X25519PublicKey &peer_public_key, X25519KeyExchangeSide side,
const X25519SecretKey &our_secret_key, const X25519PublicKey &our_public_key, std::string_view extra_payload);
};
/**
* Base for handlers using a X25519 key exchange to perform authentication.
*
* In general this works as follows:
* 1) the client and server have or generate a secret and public X25519 key.
* 2) the X25519 key exchange is performed at both the client and server, with their own secret key and their peer's public key.
* 3) a pair of derived keys is created by BLAKE2b-hashing the following into 64 bytes, in this particular order:
* - the shared secret from the key exchange;
* - the public key of the server;
* - the public key of the client;
* - optional extra payload, e.g. a password in the case of PAKE.
* The first of the pair of derived keys is usually used to encrypt client-to-server communication, and the second of the pair
* is usually used to encrypt server-to-client communication.
* 4) a XChaCha20-Poly1305 (authenticated) encryption is performed using:
* - the first of the pair of derived keys as encryption key;
* - a 24 byte nonce;
* - the public key of the client as additional authenticated data.
* - a 8 byte random number as content/message.
*
* The server initiates the request by sending its public key and a 24 byte nonce that is randomly generated. Normally the side
* that sends the encrypted data sends the nonce in their packet, which would be the client on our case. However, there are
* many implementations of clients due to the admin-protocol where this is used, and we cannot guarantee that they generate a
* good enough nonce. As such the server sends one instead. The server will create a new set of keys for each session.
*
* The client receives the request, performs the key exchange, generates the derived keys and then encrypts the message. This
* message must contain some content, so it has to be filled with 8 random bytes. Once the message has been encrypted, the
* client sends their public key, the encrypted message and the message authentication code (MAC) to the server in a response.
*
* The server receives the response, performs the key exchange, generates the derived keys, decrypts the message and validates the
* message authentication code, and finally the message. It is up to the sub class to perform the final authentication checks.
*/
class X25519AuthenticationHandler {
private:
X25519SecretKey our_secret_key; ///< The secret key used by us.
X25519PublicKey our_public_key; ///< The public key used by us.
X25519Nonce key_exchange_nonce; ///< The nonce to prevent replay attacks of the key exchange.
X25519DerivedKeys derived_keys; ///< Keys derived from the authentication process.
X25519PublicKey peer_public_key; ///< The public key used by our peer.
X25519Nonce encryption_nonce; ///< The nonce to prevent replay attacks the encrypted connection.
protected:
X25519AuthenticationHandler(const X25519SecretKey &secret_key);
void SendRequest(struct Packet &p);
bool ReceiveRequest(struct Packet &p);
bool SendResponse(struct Packet &p, std::string_view derived_key_extra_payload);
NetworkAuthenticationServerHandler::ResponseResult ReceiveResponse(struct Packet &p, std::string_view derived_key_extra_payload);
std::string GetPeerPublicKey() const;
void SendEnableEncryption(struct Packet &p) const;
bool ReceiveEnableEncryption(struct Packet &p);
std::unique_ptr<NetworkEncryptionHandler> CreateClientToServerEncryptionHandler() const;
std::unique_ptr<NetworkEncryptionHandler> CreateServerToClientEncryptionHandler() const;
};
/**
* Client side handler for using X25519 without actual authentication.
*
* This follows the method described in \c X25519AuthenticationHandler, without an extra payload.
*/
class X25519KeyExchangeOnlyClientHandler : protected X25519AuthenticationHandler, public NetworkAuthenticationClientHandler {
public:
/**
* Create the handler that that one does the key exchange.
* @param secret_key The secret key to initialize this handler with.
*/
X25519KeyExchangeOnlyClientHandler(const X25519SecretKey &secret_key) : X25519AuthenticationHandler(secret_key) {}
virtual RequestResult ReceiveRequest(struct Packet &p) override { return this->X25519AuthenticationHandler::ReceiveRequest(p) ? READY_FOR_RESPONSE : INVALID; }
virtual bool SendResponse(struct Packet &p) override { return this->X25519AuthenticationHandler::SendResponse(p, {}); }
virtual std::string_view GetName() const override { return "X25519-KeyExchangeOnly-client"; }
virtual NetworkAuthenticationMethod GetAuthenticationMethod() const override { return NETWORK_AUTH_METHOD_X25519_KEY_EXCHANGE_ONLY; }
virtual bool ReceiveEnableEncryption(struct Packet &p) override { return this->X25519AuthenticationHandler::ReceiveEnableEncryption(p); }
virtual std::unique_ptr<NetworkEncryptionHandler> CreateClientToServerEncryptionHandler() const override { return this->X25519AuthenticationHandler::CreateClientToServerEncryptionHandler(); }
virtual std::unique_ptr<NetworkEncryptionHandler> CreateServerToClientEncryptionHandler() const override { return this->X25519AuthenticationHandler::CreateServerToClientEncryptionHandler(); }
};
/**
* Server side handler for using X25519 without actual authentication.
*
* This follows the method described in \c X25519AuthenticationHandler, without an extra payload.
*/
class X25519KeyExchangeOnlyServerHandler : protected X25519AuthenticationHandler, public NetworkAuthenticationServerHandler {
public:
/**
* Create the handler that that one does the key exchange.
* @param secret_key The secret key to initialize this handler with.
*/
X25519KeyExchangeOnlyServerHandler(const X25519SecretKey &secret_key) : X25519AuthenticationHandler(secret_key) {}
virtual void SendRequest(struct Packet &p) override { this->X25519AuthenticationHandler::SendRequest(p); }
virtual ResponseResult ReceiveResponse(struct Packet &p) override { return this->X25519AuthenticationHandler::ReceiveResponse(p, {}); }
virtual std::string_view GetName() const override { return "X25519-KeyExchangeOnly-server"; }
virtual NetworkAuthenticationMethod GetAuthenticationMethod() const override { return NETWORK_AUTH_METHOD_X25519_KEY_EXCHANGE_ONLY; }
virtual bool CanBeUsed() const override { return true; }
virtual std::string GetPeerPublicKey() const override { return this->X25519AuthenticationHandler::GetPeerPublicKey(); }
virtual void SendEnableEncryption(struct Packet &p) override { this->X25519AuthenticationHandler::SendEnableEncryption(p); }
virtual std::unique_ptr<NetworkEncryptionHandler> CreateClientToServerEncryptionHandler() const override { return this->X25519AuthenticationHandler::CreateClientToServerEncryptionHandler(); }
virtual std::unique_ptr<NetworkEncryptionHandler> CreateServerToClientEncryptionHandler() const override { return this->X25519AuthenticationHandler::CreateServerToClientEncryptionHandler(); }
};
/**
* Client side handler for using X25519 with a password-authenticated key exchange.
*
* This follows the method described in \c X25519AuthenticationHandler, were the password is the extra payload.
*/
class X25519PAKEClientHandler : protected X25519AuthenticationHandler, public NetworkAuthenticationClientHandler {
private:
std::shared_ptr<NetworkAuthenticationPasswordRequestHandler> handler;
public:
/**
* Create the handler with the given password handler.
* @param secret_key The secret key to initialize this handler with.
* @param handler The handler requesting the password from the user, if required.
*/
X25519PAKEClientHandler(const X25519SecretKey &secret_key, std::shared_ptr<NetworkAuthenticationPasswordRequestHandler> handler) : X25519AuthenticationHandler(secret_key), handler(handler) {}
virtual RequestResult ReceiveRequest(struct Packet &p) override;
virtual bool SendResponse(struct Packet &p) override { return this->X25519AuthenticationHandler::SendResponse(p, this->handler->password); }
virtual std::string_view GetName() const override { return "X25519-PAKE-client"; }
virtual NetworkAuthenticationMethod GetAuthenticationMethod() const override { return NETWORK_AUTH_METHOD_X25519_PAKE; }
virtual bool ReceiveEnableEncryption(struct Packet &p) override { return this->X25519AuthenticationHandler::ReceiveEnableEncryption(p); }
virtual std::unique_ptr<NetworkEncryptionHandler> CreateClientToServerEncryptionHandler() const override { return this->X25519AuthenticationHandler::CreateClientToServerEncryptionHandler(); }
virtual std::unique_ptr<NetworkEncryptionHandler> CreateServerToClientEncryptionHandler() const override { return this->X25519AuthenticationHandler::CreateServerToClientEncryptionHandler(); }
};
/**
* Server side handler for using X25519 with a password-authenticated key exchange.
*
* This follows the method described in \c X25519AuthenticationHandler, were the password is the extra payload.
*/
class X25519PAKEServerHandler : protected X25519AuthenticationHandler, public NetworkAuthenticationServerHandler {
private:
const NetworkAuthenticationPasswordProvider *password_provider; ///< The password to check against.
public:
/**
* Create the handler with the given password provider.
* @param secret_key The secret key to initialize this handler with.
* @param password_provider The provider for the passwords.
*/
X25519PAKEServerHandler(const X25519SecretKey &secret_key, const NetworkAuthenticationPasswordProvider *password_provider) : X25519AuthenticationHandler(secret_key), password_provider(password_provider) {}
virtual void SendRequest(struct Packet &p) override { this->X25519AuthenticationHandler::SendRequest(p); }
virtual ResponseResult ReceiveResponse(struct Packet &p) override { return this->X25519AuthenticationHandler::ReceiveResponse(p, this->password_provider->GetPassword()); }
virtual std::string_view GetName() const override { return "X25519-PAKE-server"; }
virtual NetworkAuthenticationMethod GetAuthenticationMethod() const override { return NETWORK_AUTH_METHOD_X25519_PAKE; }
virtual bool CanBeUsed() const override { return !this->password_provider->GetPassword().empty(); }
virtual std::string GetPeerPublicKey() const override { return this->X25519AuthenticationHandler::GetPeerPublicKey(); }
virtual void SendEnableEncryption(struct Packet &p) override { this->X25519AuthenticationHandler::SendEnableEncryption(p); }
virtual std::unique_ptr<NetworkEncryptionHandler> CreateClientToServerEncryptionHandler() const override { return this->X25519AuthenticationHandler::CreateClientToServerEncryptionHandler(); }
virtual std::unique_ptr<NetworkEncryptionHandler> CreateServerToClientEncryptionHandler() const override { return this->X25519AuthenticationHandler::CreateServerToClientEncryptionHandler(); }
};
/**
* Handler for clients using a X25519 key exchange to perform authentication via a set of authorized (public) keys of clients.
*
* This follows the method described in \c X25519AuthenticationHandler. Once all these checks have succeeded, it will
* check whether the public key of the client is in the list of authorized keys to login.
*/
class X25519AuthorizedKeyClientHandler : protected X25519AuthenticationHandler, public NetworkAuthenticationClientHandler {
public:
/**
* Create the handler that uses the given password to check against.
* @param secret_key The secret key to initialize this handler with.
*/
X25519AuthorizedKeyClientHandler(const X25519SecretKey &secret_key) : X25519AuthenticationHandler(secret_key) {}
virtual RequestResult ReceiveRequest(struct Packet &p) override { return this->X25519AuthenticationHandler::ReceiveRequest(p) ? READY_FOR_RESPONSE : INVALID; }
virtual bool SendResponse(struct Packet &p) override { return this->X25519AuthenticationHandler::SendResponse(p, {}); }
virtual std::string_view GetName() const override { return "X25519-AuthorizedKey-client"; }
virtual NetworkAuthenticationMethod GetAuthenticationMethod() const override { return NETWORK_AUTH_METHOD_X25519_AUTHORIZED_KEY; }
virtual bool ReceiveEnableEncryption(struct Packet &p) override { return this->X25519AuthenticationHandler::ReceiveEnableEncryption(p); }
virtual std::unique_ptr<NetworkEncryptionHandler> CreateClientToServerEncryptionHandler() const override { return this->X25519AuthenticationHandler::CreateClientToServerEncryptionHandler(); }
virtual std::unique_ptr<NetworkEncryptionHandler> CreateServerToClientEncryptionHandler() const override { return this->X25519AuthenticationHandler::CreateServerToClientEncryptionHandler(); }
static X25519SecretKey GetValidSecretKeyAndUpdatePublicKey(std::string &secret_key, std::string &public_key);
};
/**
* Handler for servers using a X25519 key exchange to perform authentication via a set of authorized (public) keys of clients.
*
* This follows the method described in \c X25519AuthenticationHandler. Once all these checks have succeeded, it will
* check whether the public key of the client is in the list of authorized keys to login.
*/
class X25519AuthorizedKeyServerHandler : protected X25519AuthenticationHandler, public NetworkAuthenticationServerHandler {
private:
const NetworkAuthenticationAuthorizedKeyHandler *authorized_key_handler; ///< The handler of the authorized keys.
public:
/**
* Create the handler that uses the given authorized keys to check against.
* @param secret_key The secret key to initialize this handler with.
* @param authorized_key_handler The handler of the authorized keys.
*/
X25519AuthorizedKeyServerHandler(const X25519SecretKey &secret_key, const NetworkAuthenticationAuthorizedKeyHandler *authorized_key_handler) : X25519AuthenticationHandler(secret_key), authorized_key_handler(authorized_key_handler) {}
virtual void SendRequest(struct Packet &p) override { this->X25519AuthenticationHandler::SendRequest(p); }
virtual ResponseResult ReceiveResponse(struct Packet &p) override;
virtual std::string_view GetName() const override { return "X25519-AuthorizedKey-server"; }
virtual NetworkAuthenticationMethod GetAuthenticationMethod() const override { return NETWORK_AUTH_METHOD_X25519_AUTHORIZED_KEY; }
virtual bool CanBeUsed() const override { return this->authorized_key_handler->CanBeUsed(); }
virtual std::string GetPeerPublicKey() const override { return this->X25519AuthenticationHandler::GetPeerPublicKey(); }
virtual void SendEnableEncryption(struct Packet &p) override { this->X25519AuthenticationHandler::SendEnableEncryption(p); }
virtual std::unique_ptr<NetworkEncryptionHandler> CreateClientToServerEncryptionHandler() const override { return this->X25519AuthenticationHandler::CreateClientToServerEncryptionHandler(); }
virtual std::unique_ptr<NetworkEncryptionHandler> CreateServerToClientEncryptionHandler() const override { return this->X25519AuthenticationHandler::CreateServerToClientEncryptionHandler(); }
};
/**
* Handler for combining a number of authentication handlers, where the failure of one of the handlers will retry with
* another handler. For example when authorized keys fail, it can still fall back to a password.
*/
class CombinedAuthenticationClientHandler : public NetworkAuthenticationClientHandler {
public:
using Handler = std::unique_ptr<NetworkAuthenticationClientHandler>; ///< The type of the inner handlers.
private:
std::vector<Handler> handlers; ///< The handlers that we can authenticate with.
NetworkAuthenticationClientHandler *current_handler = nullptr; ///< The currently active handler.
public:
/**
* Add the given sub-handler to this handler.
* @param handler The handler to add.
*/
void Add(Handler &&handler) { this->handlers.push_back(std::move(handler)); }
virtual RequestResult ReceiveRequest(struct Packet &p) override;
virtual bool SendResponse(struct Packet &p) override;
virtual std::string_view GetName() const override;
virtual NetworkAuthenticationMethod GetAuthenticationMethod() const override;
virtual bool ReceiveEnableEncryption(struct Packet &p) override { return this->current_handler->ReceiveEnableEncryption(p); }
virtual std::unique_ptr<NetworkEncryptionHandler> CreateClientToServerEncryptionHandler() const override { return this->current_handler->CreateClientToServerEncryptionHandler(); }
virtual std::unique_ptr<NetworkEncryptionHandler> CreateServerToClientEncryptionHandler() const override { return this->current_handler->CreateServerToClientEncryptionHandler(); }
};
/**
* Handler for combining a number of authentication handlers, where the failure of one of the handlers will retry with
* another handler. For example when authorized keys fail, it can still fall back to a password.
*/
class CombinedAuthenticationServerHandler : public NetworkAuthenticationServerHandler {
public:
using Handler = std::unique_ptr<NetworkAuthenticationServerHandler>; ///< The type of the inner handlers.
private:
std::vector<Handler> handlers; ///< The handlers that we can (still) authenticate with.
public:
void Add(Handler &&handler);
virtual void SendRequest(struct Packet &p) override;
virtual ResponseResult ReceiveResponse(struct Packet &p) override;
virtual std::string_view GetName() const override;
virtual NetworkAuthenticationMethod GetAuthenticationMethod() const override;
virtual bool CanBeUsed() const override;
virtual std::string GetPeerPublicKey() const override { return this->handlers.back()->GetPeerPublicKey(); }
virtual void SendEnableEncryption(struct Packet &p) override { this->handlers.back()->SendEnableEncryption(p); }
virtual std::unique_ptr<NetworkEncryptionHandler> CreateClientToServerEncryptionHandler() const override { return this->handlers.back()->CreateClientToServerEncryptionHandler(); }
virtual std::unique_ptr<NetworkEncryptionHandler> CreateServerToClientEncryptionHandler() const override { return this->handlers.back()->CreateServerToClientEncryptionHandler(); }
};
#endif /* NETWORK_CRYPTO_INTERNAL_H */
+8 -8
View File
@@ -24,16 +24,14 @@
#include "../company_type.h"
#include "../string_type.h"
extern NetworkCompanyState *_network_company_states;
extern ClientID _network_own_client_id;
extern ClientID _redirect_console_to_client;
extern uint8_t _network_reconnect;
extern StringList _network_bind_list;
extern StringList _network_host_list;
extern StringList _network_ban_list;
extern bool _novarole;
byte NetworkSpectatorCount();
uint8_t NetworkSpectatorCount();
bool NetworkIsValidClientName(const std::string_view client_name);
bool NetworkValidateOurClientName();
bool NetworkValidateClientName(std::string &client_name);
@@ -41,7 +39,6 @@ bool NetworkValidateServerName(std::string &server_name);
void NetworkUpdateClientName(const std::string &client_name);
void NetworkUpdateServerGameType();
bool NetworkCompanyHasClients(CompanyID company);
std::string NetworkChangeCompanyPassword(CompanyID company_id, std::string password);
void NetworkReboot();
void NetworkDisconnect(bool close_admins = true);
void NetworkGameLoop();
@@ -51,18 +48,20 @@ void NetworkPopulateCompanyStats(NetworkCompanyStats *stats);
void NetworkUpdateClientInfo(ClientID client_id);
void NetworkClientsToSpectators(CompanyID cid);
bool NetworkClientConnectGame(const std::string &connection_string, CompanyID default_company, const std::string &join_server_password = "", const std::string &join_company_password = "");
bool NetworkClientConnectGame(const std::string &connection_string, CompanyID default_company, const std::string &join_server_password = "");
void NetworkClientJoinGame();
void NetworkClientRequestMove(CompanyID company, const std::string &pass = "");
void NetworkClientRequestMove(CompanyID company);
void NetworkClientSendRcon(const std::string &password, const std::string &command);
void NetworkClientSendChat(NetworkAction action, DestType type, int dest, const std::string &msg, int64_t data = 0);
bool NetworkClientPreferTeamChat(const NetworkClientInfo *cio);
bool NetworkCompanyIsPassworded(CompanyID company_id);
uint NetworkMaxCompaniesAllowed();
bool NetworkMaxCompaniesReached();
void NetworkPrintClients();
std::string_view NetworkGetPublicKeyOfClient(ClientID client_id);
void NetworkHandlePauseChange(PauseMode prev_mode, PauseMode changed_mode);
void NetworkOnGameStart();
/*** Commands ran by the server ***/
void NetworkServerSendConfigUpdate();
void NetworkServerUpdateGameInfo();
@@ -72,6 +71,7 @@ void NetworkServerNewCompany(const Company *company, NetworkClientInfo *ci);
bool NetworkServerChangeClientName(ClientID client_id, const std::string &new_name);
bool NetworkCanJoinCompany(CompanyID company_id);
void NetworkServerDoMove(ClientID client_id, CompanyID company_id);
void NetworkServerSendRcon(ClientID client_id, TextColour colour_code, const std::string &string);
void NetworkServerSendChat(NetworkAction action, DestType type, int dest, const std::string &msg, ClientID from_id, int64_t data = 0, bool from_admin = false);
File diff suppressed because it is too large Load Diff
+1 -2
View File
@@ -17,12 +17,11 @@
#include "network_type.h"
#include "network_gamelist.h"
void ShowNetworkNeedPassword(NetworkPasswordType npt);
void ShowNetworkNeedPassword(std::shared_ptr<class NetworkAuthenticationPasswordRequest> request);
void ShowNetworkChatQueryWindow(DestType type, int dest);
void ShowJoinStatusWindow();
void ShowNetworkGameWindow();
void ShowClientList();
void ShowNetworkCompanyPasswordWindow(Window *parent);
void ShowNetworkAskRelay(const std::string &server_connection_string, const std::string &relay_connection_string, const std::string &token);
void ShowNetworkAskSurvey();
void ShowSurveyResultTextfileWindow();
-3
View File
@@ -80,8 +80,6 @@ extern std::string _network_server_name;
extern uint8_t _network_reconnect;
extern CompanyMask _network_company_passworded;
void NetworkQueryServer(const std::string &connection_string);
void GetBindAddresses(NetworkAddressList *addresses, uint16_t port);
@@ -116,7 +114,6 @@ void NetworkTextMessage(NetworkAction action, TextColour colour, bool self_send,
uint NetworkCalculateLag(const NetworkClientSocket *cs);
StringID GetNetworkErrorMsg(NetworkErrorCode err);
bool NetworkMakeClientNameUnique(std::string &new_name);
std::string GenerateCompanyPasswordHash(const std::string &password, const std::string &password_server_id, uint32_t password_game_seed);
std::string_view ParseCompanyFromConnectionString(const std::string &connection_string, CompanyID *company_id);
NetworkAddress ParseConnectionString(const std::string &connection_string, uint16_t default_port);
+1 -1
View File
@@ -83,7 +83,7 @@ NetworkRecvStatus QueryNetworkGameSocketHandler::SendGameInfo()
{
Debug(net, 9, "Query::SendGameInfo()");
this->SendPacket(std::make_unique<Packet>(PACKET_CLIENT_GAME_INFO));
this->SendPacket(std::make_unique<Packet>(this, PACKET_CLIENT_GAME_INFO));
return NETWORK_RECV_STATUS_OKAY;
}
+196 -231
View File
@@ -60,6 +60,11 @@ INSTANTIATE_POOL_METHODS(NetworkClientSocket)
/** Instantiate the listen 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.
static NetworkAuthenticationDefaultAuthorizedKeyHandler _rcon_authorized_key_handler(_settings_client.network.rcon_authorized_keys); ///< Provides the authorized key validation for rcon.
/** Writing a savegame directly to a number of packets. */
struct PacketWriter : SaveFilter {
ServerNetworkGameSocketHandler *cs; ///< Socket we are associated with.
@@ -118,10 +123,9 @@ struct PacketWriter : SaveFilter {
/**
* Transfer all packets from here to the network's queue while holding
* the lock on our mutex.
* @param socket The network socket to write to.
* @return True iff the last packet of the map has been sent.
*/
bool TransferToNetworkQueue(ServerNetworkGameSocketHandler *socket)
bool TransferToNetworkQueue()
{
/* Unsafe check for the queue being empty or not. */
if (this->packets.empty()) return false;
@@ -130,7 +134,7 @@ struct PacketWriter : SaveFilter {
while (!this->packets.empty()) {
bool last_packet = this->packets.front()->GetPacketType() == PACKET_SERVER_MAP_DONE;
socket->SendPacket(std::move(this->packets.front()));
this->cs->SendPacket(std::move(this->packets.front()));
this->packets.pop_front();
if (last_packet) return true;
@@ -139,23 +143,22 @@ struct PacketWriter : SaveFilter {
return false;
}
void Write(byte *buf, size_t size) override
void Write(uint8_t *buf, size_t size) override
{
std::lock_guard<std::mutex> lock(this->mutex);
/* We want to abort the saving when the socket is closed. */
if (this->cs == nullptr) SlError(STR_NETWORK_ERROR_LOSTCONNECTION);
if (this->current == nullptr) this->current = std::make_unique<Packet>(PACKET_SERVER_MAP_DATA, TCP_MTU);
if (this->current == nullptr) this->current = std::make_unique<Packet>(this->cs, PACKET_SERVER_MAP_DATA, TCP_MTU);
std::lock_guard<std::mutex> lock(this->mutex);
byte *bufe = buf + size;
while (buf != bufe) {
size_t written = this->current->Send_bytes(buf, bufe);
buf += written;
std::span<const uint8_t> to_write(buf, size);
while (!to_write.empty()) {
to_write = this->current->Send_bytes(to_write);
if (!this->current->CanWriteToPacket(1)) {
this->packets.push_back(std::move(this->current));
if (buf != bufe) this->current = std::make_unique<Packet>(PACKET_SERVER_MAP_DATA, TCP_MTU);
if (!to_write.empty()) this->current = std::make_unique<Packet>(this->cs, PACKET_SERVER_MAP_DATA, TCP_MTU);
}
}
@@ -164,19 +167,19 @@ struct PacketWriter : SaveFilter {
void Finish() override
{
std::lock_guard<std::mutex> lock(this->mutex);
/* We want to abort the saving when the socket is closed. */
if (this->cs == nullptr) SlError(STR_NETWORK_ERROR_LOSTCONNECTION);
std::lock_guard<std::mutex> lock(this->mutex);
/* Make sure the last packet is flushed. */
if (this->current != nullptr) this->packets.push_back(std::move(this->current));
/* Add a packet stating that this is the end to the queue. */
this->packets.push_back(std::make_unique<Packet>(PACKET_SERVER_MAP_DONE));
this->packets.push_back(std::make_unique<Packet>(this->cs, PACKET_SERVER_MAP_DONE));
/* Fast-track the size to the client. */
auto p = std::make_unique<Packet>(PACKET_SERVER_MAP_SIZE);
auto p = std::make_unique<Packet>(this->cs, PACKET_SERVER_MAP_SIZE);
p->Send_uint32((uint32_t)this->total_size);
this->packets.push_front(std::move(p));
}
@@ -215,6 +218,8 @@ ServerNetworkGameSocketHandler::~ServerNetworkGameSocketHandler()
this->savegame->Destroy();
this->savegame = nullptr;
}
InvalidateWindowData(WC_CLIENT_LIST, 0);
}
std::unique_ptr<Packet> ServerNetworkGameSocketHandler::ReceivePacket()
@@ -271,16 +276,13 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::CloseConnection(NetworkRecvSta
/* We just lost one client :( */
if (this->status >= STATUS_AUTHORIZED) _network_game_info.clients_on--;
extern byte _network_clients_connected;
extern uint8_t _network_clients_connected;
_network_clients_connected--;
this->SendPackets(true);
this->DeferDeletion();
citymania::SetClientListDirty();
InvalidateWindowData(WC_CLIENT_LIST, 0);
return status;
}
@@ -290,7 +292,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::CloseConnection(NetworkRecvSta
*/
/* static */ bool ServerNetworkGameSocketHandler::AllowConnection()
{
extern byte _network_clients_connected;
extern uint8_t _network_clients_connected;
bool accept = _network_clients_connected < MAX_CLIENTS;
/* We can't go over the MAX_CLIENTS limit here. However, the
@@ -328,10 +330,11 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendClientInfo(NetworkClientIn
Debug(net, 9, "client[{}] SendClientInfo(): client_id={}", this->client_id, ci->client_id);
if (ci->client_id != INVALID_CLIENT_ID) {
auto p = std::make_unique<Packet>(PACKET_SERVER_CLIENT_INFO);
auto p = std::make_unique<Packet>(this, PACKET_SERVER_CLIENT_INFO);
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));
}
@@ -343,7 +346,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendGameInfo()
{
Debug(net, 9, "client[{}] SendGameInfo()", this->client_id);
auto p = std::make_unique<Packet>(PACKET_SERVER_GAME_INFO, TCP_MTU);
auto p = std::make_unique<Packet>(this, PACKET_SERVER_GAME_INFO, TCP_MTU);
SerializeNetworkGameInfo(*p, GetCurrentNetworkServerGameInfo());
this->SendPacket(std::move(p));
@@ -360,7 +363,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendError(NetworkErrorCode err
{
Debug(net, 9, "client[{}] SendError(): error={}", this->client_id, error);
auto p = std::make_unique<Packet>(PACKET_SERVER_ERROR);
auto p = std::make_unique<Packet>(this, PACKET_SERVER_ERROR);
p->Send_uint8(error);
if (!reason.empty()) p->Send_string(reason);
@@ -405,7 +408,18 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendNewGRFCheck()
{
Debug(net, 9, "client[{}] SendNewGRFCheck()", this->client_id);
auto p = std::make_unique<Packet>(PACKET_SERVER_CHECK_NEWGRFS, TCP_MTU);
/* Invalid packet when status is anything but STATUS_IDENTIFY. */
if (this->status != STATUS_IDENTIFY) return this->CloseConnection(NETWORK_RECV_STATUS_MALFORMED_PACKET);
Debug(net, 9, "client[{}] status = NEWGRFS_CHECK", this->client_id);
this->status = STATUS_NEWGRFS_CHECK;
if (_grfconfig == nullptr) {
/* There are no NewGRFs, so they're welcome. */
return this->SendWelcome();
}
auto p = std::make_unique<Packet>(this, PACKET_SERVER_CHECK_NEWGRFS, TCP_MTU);
const GRFConfig *c;
uint grf_count = 0;
@@ -423,49 +437,40 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendNewGRFCheck()
}
/** Request the game password. */
NetworkRecvStatus ServerNetworkGameSocketHandler::SendNeedGamePassword()
NetworkRecvStatus ServerNetworkGameSocketHandler::SendAuthRequest()
{
if (_settings_client.network.server_password.empty()) {
/* Do not actually need a game password, continue with the company password. */
return this->SendNeedCompanyPassword();
}
Debug(net, 9, "client[{}] SendAuthRequest()", this->client_id);
Debug(net, 9, "client[{}] SendNeedGamePassword()", this->client_id);
/* Invalid packet when status is STATUS_AUTH_GAME or higher */
if (this->status >= STATUS_AUTH_GAME) 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;
/* Reset 'lag' counters */
this->last_frame = this->last_frame_server = _frame_counter;
auto p = std::make_unique<Packet>(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;
}
/** Request the company password. */
NetworkRecvStatus ServerNetworkGameSocketHandler::SendNeedCompanyPassword()
/** Notify the client that the authentication has completed and tell that for the remainder of this socket encryption is enabled. */
NetworkRecvStatus ServerNetworkGameSocketHandler::SendEnableEncryption()
{
NetworkClientInfo *ci = this->GetInfo();
if (!Company::IsValidID(ci->client_playas) || _network_company_states[ci->client_playas].password.empty()) {
return this->SendWelcome();
}
Debug(net, 9, "client[{}] SendEnableEncryption()", this->client_id);
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 STATUS_AUTH_COMPANY or higher */
if (this->status >= STATUS_AUTH_COMPANY) return this->CloseConnection(NETWORK_RECV_STATUS_MALFORMED_PACKET);
Debug(net, 9, "client[{}] status = AUTH_COMPANY", this->client_id);
this->status = STATUS_AUTH_COMPANY;
/* Reset 'lag' counters */
this->last_frame = this->last_frame_server = _frame_counter;
auto p = std::make_unique<Packet>(PACKET_SERVER_NEED_COMPANY_PASSWORD);
p->Send_uint32(_settings_game.game_creation.generation_seed);
p->Send_string(_settings_client.network.network_id);
auto p = std::make_unique<Packet>(this, PACKET_SERVER_ENABLE_ENCRYPTION);
this->authentication_handler->SendEnableEncryption(*p);
this->SendPacket(std::move(p));
return NETWORK_RECV_STATUS_OKAY;
}
@@ -475,20 +480,19 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendWelcome()
{
Debug(net, 9, "client[{}] SendWelcome()", this->client_id);
/* Invalid packet when status is AUTH or higher */
if (this->status >= STATUS_AUTHORIZED) 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 = AUTHORIZED", this->client_id);
this->status = STATUS_AUTHORIZED;
/* Reset 'lag' counters */
this->last_frame = this->last_frame_server = _frame_counter;
_network_game_info.clients_on++;
auto p = std::make_unique<Packet>(PACKET_SERVER_WELCOME);
auto p = std::make_unique<Packet>(this, PACKET_SERVER_WELCOME);
p->Send_uint32(this->client_id);
p->Send_uint32(_settings_game.game_creation.generation_seed);
p->Send_string(_settings_client.network.network_id);
this->SendPacket(std::move(p));
/* Transmit info about all the active clients */
@@ -514,7 +518,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendWait()
if (new_cs->GetInfo()->join_date < this->GetInfo()->join_date || (new_cs->GetInfo()->join_date == this->GetInfo()->join_date && new_cs->client_id < this->client_id)) waiting++;
}
auto p = std::make_unique<Packet>(PACKET_SERVER_WAIT);
auto p = std::make_unique<Packet>(this, PACKET_SERVER_WAIT);
p->Send_uint8(waiting);
this->SendPacket(std::move(p));
return NETWORK_RECV_STATUS_OKAY;
@@ -564,7 +568,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendMap()
this->savegame = std::make_shared<PacketWriter>(this);
/* Now send the _frame_counter and how many packets are coming */
auto p = std::make_unique<Packet>(PACKET_SERVER_MAP_BEGIN);
auto p = std::make_unique<Packet>(this, PACKET_SERVER_MAP_BEGIN);
p->Send_uint32(_frame_counter);
this->SendPacket(std::move(p));
@@ -580,7 +584,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendMap()
}
if (this->status == STATUS_MAP) {
bool last_packet = this->savegame->TransferToNetworkQueue(this);
bool last_packet = this->savegame->TransferToNetworkQueue();
if (last_packet) {
Debug(net, 9, "client[{}] SendMap(): last_packet", this->client_id);
@@ -607,7 +611,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendJoin(ClientID client_id)
{
Debug(net, 9, "client[{}] SendJoin(): client_id={}", this->client_id, client_id);
auto p = std::make_unique<Packet>(PACKET_SERVER_JOIN);
auto p = std::make_unique<Packet>(this, PACKET_SERVER_JOIN);
p->Send_uint32(client_id);
@@ -618,7 +622,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendJoin(ClientID client_id)
/** Tell the client that they may run to a particular frame. */
NetworkRecvStatus ServerNetworkGameSocketHandler::SendFrame()
{
auto p = std::make_unique<Packet>(PACKET_SERVER_FRAME);
auto p = std::make_unique<Packet>(this, PACKET_SERVER_FRAME);
p->Send_uint32(_frame_counter);
p->Send_uint32(_frame_counter_max);
#ifdef ENABLE_NETWORK_SYNC_EVERY_FRAME
@@ -643,7 +647,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendSync()
{
Debug(net, 9, "client[{}] SendSync(), frame_counter={}, sync_seed_1={}", this->client_id, _frame_counter, _sync_seed_1);
auto p = std::make_unique<Packet>(PACKET_SERVER_SYNC);
auto p = std::make_unique<Packet>(this, PACKET_SERVER_SYNC);
p->Send_uint32(_frame_counter);
p->Send_uint32(_sync_seed_1);
@@ -662,7 +666,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendCommand(const CommandPacke
{
Debug(net, 9, "client[{}] SendCommand(): cmd={}", this->client_id, cp.cmd);
auto p = std::make_unique<Packet>(PACKET_SERVER_COMMAND);
auto p = std::make_unique<Packet>(this, PACKET_SERVER_COMMAND);
this->NetworkGameSocketHandler::SendCommand(*p, cp);
p->Send_uint32(cp.frame);
@@ -686,7 +690,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendChat(NetworkAction action,
if (this->status < STATUS_PRE_ACTIVE) return NETWORK_RECV_STATUS_OKAY;
auto p = std::make_unique<Packet>(PACKET_SERVER_CHAT);
auto p = std::make_unique<Packet>(this, PACKET_SERVER_CHAT);
p->Send_uint8 (action);
p->Send_uint32(client_id);
@@ -711,7 +715,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendExternalChat(const std::st
if (this->status < STATUS_PRE_ACTIVE) return NETWORK_RECV_STATUS_OKAY;
auto p = std::make_unique<Packet>(PACKET_SERVER_EXTERNAL_CHAT);
auto p = std::make_unique<Packet>(this, PACKET_SERVER_EXTERNAL_CHAT);
p->Send_string(source);
p->Send_uint16(colour);
@@ -731,7 +735,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendErrorQuit(ClientID client_
{
Debug(net, 9, "client[{}] SendErrorQuit(): client_id={}, errorno={}", this->client_id, client_id, errorno);
auto p = std::make_unique<Packet>(PACKET_SERVER_ERROR_QUIT);
auto p = std::make_unique<Packet>(this, PACKET_SERVER_ERROR_QUIT);
p->Send_uint32(client_id);
p->Send_uint8 (errorno);
@@ -748,7 +752,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendQuit(ClientID client_id)
{
Debug(net, 9, "client[{}] SendQuit(): client_id={}", this->client_id, client_id);
auto p = std::make_unique<Packet>(PACKET_SERVER_QUIT);
auto p = std::make_unique<Packet>(this, PACKET_SERVER_QUIT);
p->Send_uint32(client_id);
@@ -761,7 +765,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendShutdown()
{
Debug(net, 9, "client[{}] SendShutdown()", this->client_id);
auto p = std::make_unique<Packet>(PACKET_SERVER_SHUTDOWN);
auto p = std::make_unique<Packet>(this, PACKET_SERVER_SHUTDOWN);
this->SendPacket(std::move(p));
return NETWORK_RECV_STATUS_OKAY;
}
@@ -771,7 +775,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendNewGame()
{
Debug(net, 9, "client[{}] SendNewGame()", this->client_id);
auto p = std::make_unique<Packet>(PACKET_SERVER_NEWGAME);
auto p = std::make_unique<Packet>(this, PACKET_SERVER_NEWGAME);
this->SendPacket(std::move(p));
return NETWORK_RECV_STATUS_OKAY;
}
@@ -785,7 +789,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendRConResult(uint16_t colour
{
Debug(net, 9, "client[{}] SendRConResult()", this->client_id);
auto p = std::make_unique<Packet>(PACKET_SERVER_RCON);
auto p = std::make_unique<Packet>(this, PACKET_SERVER_RCON);
p->Send_uint16(colour);
p->Send_string(command);
@@ -802,7 +806,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendMove(ClientID client_id, C
{
Debug(net, 9, "client[{}] SendMove(): client_id={}", this->client_id, client_id);
auto p = std::make_unique<Packet>(PACKET_SERVER_MOVE);
auto p = std::make_unique<Packet>(this, PACKET_SERVER_MOVE);
p->Send_uint32(client_id);
p->Send_uint8(company_id);
@@ -810,25 +814,12 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendMove(ClientID client_id, C
return NETWORK_RECV_STATUS_OKAY;
}
/** Send an update about the company password states. */
NetworkRecvStatus ServerNetworkGameSocketHandler::SendCompanyUpdate()
{
Debug(net, 9, "client[{}] SendCompanyUpdate()", this->client_id);
auto p = std::make_unique<Packet>(PACKET_SERVER_COMPANY_UPDATE);
static_assert(sizeof(_network_company_passworded) <= sizeof(uint16_t));
p->Send_uint16(_network_company_passworded);
this->SendPacket(std::move(p));
return NETWORK_RECV_STATUS_OKAY;
}
/** Send an update about the max company/spectator counts. */
NetworkRecvStatus ServerNetworkGameSocketHandler::SendConfigUpdate()
{
Debug(net, 9, "client[{}] SendConfigUpdate()", this->client_id);
auto p = std::make_unique<Packet>(PACKET_SERVER_CONFIG_UPDATE);
auto p = std::make_unique<Packet>(this, PACKET_SERVER_CONFIG_UPDATE);
p->Send_uint8(_settings_client.network.max_companies);
p->Send_string(_settings_client.network.server_name);
@@ -856,7 +847,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_NEWGRFS_CHECKED
Debug(net, 9, "client[{}] Receive_CLIENT_NEWGRFS_CHECKED()", this->client_id);
return this->SendNeedGamePassword();
return this->SendWelcome();
}
NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_JOIN(Packet &p)
@@ -882,6 +873,15 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_JOIN(Packet &p)
return this->SendError(NETWORK_ERROR_WRONG_REVISION);
}
return this->SendAuthRequest();
}
NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_IDENTIFY(Packet &p)
{
if (this->status != STATUS_IDENTIFY) return this->SendError(NETWORK_ERROR_NOT_EXPECTED);
Debug(net, 9, "client[{}] Receive_CLIENT_IDENTIFY()", this->client_id);
std::string client_name = p.Recv_string(NETWORK_CLIENT_NAME_LENGTH);
CompanyID playas = (Owner)p.Recv_uint8();
p.Recv_uint8(); // former client_lang now 0
@@ -905,10 +905,15 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_JOIN(Packet &p)
break;
case COMPANY_SPECTATOR: // Spectator
break;
default: // Join another company (companies 1-8 (index 0-7))
default: // Join another company (companies 1..MAX_COMPANIES (index 0..(MAX_COMPANIES-1)))
if (!Company::IsValidHumanID(playas)) {
return this->SendError(NETWORK_ERROR_COMPANY_MISMATCH);
}
if (!Company::Get(playas)->allow_list.Contains(this->peer_public_key)) {
/* When we're not authorized, just bump us to a spectator. */
playas = COMPANY_SPECTATOR;
}
break;
}
@@ -930,63 +935,65 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_JOIN(Packet &p)
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 */
if (Company::IsValidID(playas)) _network_company_states[playas].months_empty = 0;
Debug(net, 9, "client[{}] status = NEWGRFS_CHECK", this->client_id);
this->status = STATUS_NEWGRFS_CHECK;
if (_grfconfig == nullptr) {
/* Continue asking for the game password. */
return this->SendNeedGamePassword();
}
Company *c = Company::GetIfValid(playas);
if (c != nullptr) c->months_empty = 0;
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->SendEnableEncryption();
if (status != NETWORK_RECV_STATUS_OKAY) return status;
NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_COMPANY_PASSWORD(Packet &p)
{
if (this->status != STATUS_AUTH_COMPANY) {
return this->SendError(NETWORK_ERROR_NOT_EXPECTED);
}
this->peer_public_key = this->authentication_handler->GetPeerPublicKey();
this->receive_encryption_handler = this->authentication_handler->CreateClientToServerEncryptionHandler();
this->send_encryption_handler = this->authentication_handler->CreateServerToClientEncryptionHandler();
this->authentication_handler = nullptr;
Debug(net, 9, "client[{}] Receive_CLIENT_COMPANY_PASSWORD()", this->client_id);
Debug(net, 9, "client[{}] status = IDENTIFY", this->client_id);
this->status = STATUS_IDENTIFY;
std::string password = p.Recv_string(NETWORK_PASSWORD_LENGTH);
/* Reset 'lag' counters */
this->last_frame = this->last_frame_server = _frame_counter;
/* Check company password. Allow joining if we cleared the password meanwhile.
* Also, check the company is still valid - client could be moved to spectators
* in the middle of the authorization process */
CompanyID playas = this->GetInfo()->client_playas;
if (Company::IsValidID(playas) && !_network_company_states[playas].password.empty() &&
_network_company_states[playas].password.compare(password) != 0) {
/* Password is invalid */
return this->SendError(NETWORK_ERROR_WRONG_PASSWORD);
}
return this->SendWelcome();
return NETWORK_RECV_STATUS_OKAY;
}
NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_GETMAP(Packet &)
@@ -1050,10 +1057,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_MAP_OK(Packet &
NetworkAdminClientInfo(this, true);
/* also update the new client with our max values */
this->SendConfigUpdate();
/* quickly update the syncing client with company details */
return this->SendCompanyUpdate();
return this->SendConfigUpdate();
}
/* Wrong status for this packet, give a warning to client, and close connection */
@@ -1125,6 +1129,24 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_COMMAND(Packet
}
}
if (cp.cmd == CMD_COMPANY_ALLOW_LIST_CTRL) {
/* 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;
/* Only allow clients to add/remove currently joined clients. The server owner does not go via this method, so is allowed to do more. */
std::string public_key = std::get<1>(EndianBufferReader::ToValue<CommandTraits<CMD_COMPANY_ALLOW_LIST_CTRL>::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);
@@ -1401,22 +1423,6 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_CHAT(Packet &p)
return NETWORK_RECV_STATUS_OKAY;
}
NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_SET_PASSWORD(Packet &p)
{
if (this->status != STATUS_ACTIVE) {
/* Illegal call, return error and ignore the packet */
return this->SendError(NETWORK_ERROR_NOT_EXPECTED);
}
Debug(net, 9, "client[{}] Receive_CLIENT_SET_PASSWORD()", this->client_id);
std::string password = p.Recv_string(NETWORK_PASSWORD_LENGTH);
const NetworkClientInfo *ci = this->GetInfo();
NetworkServerSetCompanyPassword(ci->client_playas, password);
return NETWORK_RECV_STATUS_OKAY;
}
NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_SET_NAME(Packet &p)
{
if (this->status != STATUS_ACTIVE) {
@@ -1455,14 +1461,16 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_RCON(Packet &p)
{
if (this->status != STATUS_ACTIVE) return this->SendError(NETWORK_ERROR_NOT_EXPECTED);
if (_settings_client.network.rcon_password.empty()) return NETWORK_RECV_STATUS_OKAY;
Debug(net, 9, "client[{}] Receive_CLIENT_RCON()", this->client_id);
std::string password = p.Recv_string(NETWORK_PASSWORD_LENGTH);
std::string command = p.Recv_string(NETWORK_RCONCOMMAND_LENGTH);
if (_settings_client.network.rcon_password.compare(password) != 0) {
if (_rcon_authorized_key_handler.IsAllowed(this->peer_public_key)) {
/* We are allowed, nothing more to validate. */
} else if (_settings_client.network.rcon_password.empty()) {
return NETWORK_RECV_STATUS_OKAY;
} else if (_settings_client.network.rcon_password.compare(password) != 0) {
Debug(net, 1, "[rcon] Wrong password from client-id {}", this->client_id);
return NETWORK_RECV_STATUS_OKAY;
}
@@ -1486,16 +1494,9 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_MOVE(Packet &p)
/* Check if the company is valid, we don't allow moving to AI companies */
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()) {
/* we need a password from the client - should be in this packet */
std::string password = p.Recv_string(NETWORK_PASSWORD_LENGTH);
/* Incorrect password sent, return! */
if (_network_company_states[company_id].password.compare(password) != 0) {
Debug(net, 2, "Wrong password from client-id #{} for company #{}", this->client_id, company_id + 1);
return NETWORK_RECV_STATUS_OKAY;
}
if (company_id != COMPANY_SPECTATOR && !Company::Get(company_id)->allow_list.Contains(this->peer_public_key)) {
Debug(net, 2, "Wrong public key from client-id #{} for company #{}", this->client_id, company_id + 1);
return NETWORK_RECV_STATUS_OKAY;
}
/* if we get here we can move the client */
@@ -1514,7 +1515,7 @@ void NetworkPopulateCompanyStats(NetworkCompanyStats *stats)
/* Go through all vehicles and count the type of vehicles */
for (const Vehicle *v : Vehicle::Iterate()) {
if (!Company::IsValidID(v->owner) || !v->IsPrimaryVehicle()) continue;
byte type = 0;
uint8_t type = 0;
switch (v->type) {
case VEH_TRAIN: type = NETWORK_VEH_TRAIN; break;
case VEH_ROAD: type = RoadVehicle::From(v)->IsBus() ? NETWORK_VEH_BUS : NETWORK_VEH_LORRY; break;
@@ -1560,11 +1561,10 @@ void NetworkUpdateClientInfo(ClientID client_id)
NetworkAdminClientUpdate(ci);
}
/** Check if the server has autoclean_companies activated
* Two things happen:
* 1) If a company is not protected, it is closed after 1 year (for example)
* 2) If a company is protected, protection is disabled after 3 years (for example)
* (and item 1. happens a year later)
/**
* Remove companies that have not been used depending on the \c autoclean_companies setting
* and values for \c autoclean_protected, which removes any company, and
* \c autoclean_novehicles, which removes companies without vehicles.
*/
static void NetworkAutoCleanCompanies()
{
@@ -1591,37 +1591,29 @@ static void NetworkAutoCleanCompanies()
}
/* Go through all the companies */
for (const Company *c : Company::Iterate()) {
for (Company *c : Company::Iterate()) {
/* Skip the non-active once */
if (c->is_ai) continue;
if (!HasBit(has_clients, c->index)) {
/* The company is empty for one month more */
_network_company_states[c->index].months_empty++;
if (c->months_empty != std::numeric_limits<decltype(c->months_empty)>::max()) c->months_empty++;
/* Is the company empty for autoclean_unprotected-months, and is there no protection? */
if (_settings_client.network.autoclean_unprotected != 0 && _network_company_states[c->index].months_empty > _settings_client.network.autoclean_unprotected && _network_company_states[c->index].password.empty()) {
/* Is the company empty for autoclean_protected-months? */
if (_settings_client.network.autoclean_protected != 0 && c->months_empty > _settings_client.network.autoclean_protected) {
/* Shut the company down */
Command<CMD_COMPANY_CTRL>::Post(CCA_DELETE, c->index, CRR_AUTOCLEAN, INVALID_CLIENT_ID);
IConsolePrint(CC_INFO, "Auto-cleaned company #{} with no password.", c->index + 1);
}
/* Is the company empty for autoclean_protected-months, and there is a protection? */
if (_settings_client.network.autoclean_protected != 0 && _network_company_states[c->index].months_empty > _settings_client.network.autoclean_protected && !_network_company_states[c->index].password.empty()) {
/* Unprotect the company */
_network_company_states[c->index].password.clear();
IConsolePrint(CC_INFO, "Auto-removed protection from company #{}.", c->index + 1);
_network_company_states[c->index].months_empty = 0;
NetworkServerUpdateCompanyPassworded(c->index, false);
IConsolePrint(CC_INFO, "Auto-cleaned company #{}.", c->index + 1);
}
/* Is the company empty for autoclean_novehicles-months, and has no vehicles? */
if (_settings_client.network.autoclean_novehicles != 0 && _network_company_states[c->index].months_empty > _settings_client.network.autoclean_novehicles && !HasBit(has_vehicles, c->index)) {
if (_settings_client.network.autoclean_novehicles != 0 && c->months_empty > _settings_client.network.autoclean_novehicles && !HasBit(has_vehicles, c->index)) {
/* Shut the company down */
Command<CMD_COMPANY_CTRL>::Post(CCA_DELETE, c->index, CRR_AUTOCLEAN, INVALID_CLIENT_ID);
IConsolePrint(CC_INFO, "Auto-cleaned company #{} with no vehicles.", c->index + 1);
}
} else {
/* It is not empty, reset the date */
_network_company_states[c->index].months_empty = 0;
c->months_empty = 0;
}
}
}
@@ -1688,25 +1680,6 @@ bool NetworkServerChangeClientName(ClientID client_id, const std::string &new_na
return true;
}
/**
* Set/Reset a company password on the server end.
* @param company_id ID of the company the password should be changed for.
* @param password The new password.
* @param already_hashed Is the given password already hashed?
*/
void NetworkServerSetCompanyPassword(CompanyID company_id, const std::string &password, bool already_hashed)
{
if (!Company::IsValidHumanID(company_id)) return;
if (already_hashed) {
_network_company_states[company_id].password = password;
} else {
_network_company_states[company_id].password = GenerateCompanyPasswordHash(password, _settings_client.network.network_id, _settings_game.game_creation.generation_seed);
}
NetworkServerUpdateCompanyPassworded(company_id, !_network_company_states[company_id].password.empty());
}
/**
* Handle the command-queue of a socket.
* @param cs The socket to handle the queue for.
@@ -1779,6 +1752,7 @@ void NetworkServer_Tick(bool send_frame)
break;
case NetworkClientSocket::STATUS_INACTIVE:
case NetworkClientSocket::STATUS_IDENTIFY:
case NetworkClientSocket::STATUS_NEWGRFS_CHECK:
case NetworkClientSocket::STATUS_AUTHORIZED:
/* NewGRF check and authorized states should be handled almost instantly.
@@ -1825,7 +1799,6 @@ void NetworkServer_Tick(bool send_frame)
break;
case NetworkClientSocket::STATUS_AUTH_GAME:
case NetworkClientSocket::STATUS_AUTH_COMPANY:
/* These don't block? */
if (lag > _settings_client.network.max_password_time) {
IConsolePrint(CC_WARNING, "Client #{} (IP: {}) is dropped because it took longer than {} ticks to enter the password.", cs->client_id, cs->GetClientIP(), _settings_client.network.max_password_time);
@@ -1971,9 +1944,9 @@ void NetworkServerShowStatusToConsole()
{
static const char * const stat_str[] = {
"inactive",
"authorizing",
"identifing client",
"checking NewGRFs",
"authorizing (server password)",
"authorizing (company password)",
"authorized",
"waiting",
"loading map",
@@ -2013,25 +1986,6 @@ void NetworkServerUpdateGameInfo()
if (_network_server) FillStaticNetworkServerGameInfo();
}
/**
* Tell that a particular company is (not) passworded.
* @param company_id The company that got/removed the password.
* @param passworded Whether the password was received or removed.
*/
void NetworkServerUpdateCompanyPassworded(CompanyID company_id, bool passworded)
{
if (NetworkCompanyIsPassworded(company_id) == passworded) return;
SB(_network_company_passworded, company_id, 1, !!passworded);
SetWindowClassesDirty(WC_COMPANY);
for (NetworkClientSocket *cs : NetworkClientSocket::Iterate()) {
if (cs->status >= NetworkClientSocket::STATUS_PRE_ACTIVE) cs->SendCompanyUpdate();
}
NetworkAdminCompanyUpdate(Company::GetIfValid(company_id));
}
/**
* Handle the tid-bits of moving a client from one company to another.
* @param client_id id of the client we want to move.
@@ -2190,6 +2144,18 @@ void NetworkPrintClients()
}
}
/**
* Get the public key of the client with the given id.
* @param client_id The id of the client.
* @return View of the public key, which is empty when the client does not exist.
*/
std::string_view NetworkGetPublicKeyOfClient(ClientID client_id)
{
auto socket = NetworkClientSocket::GetByClientID(client_id);
return socket == nullptr ? "" : socket->GetPeerPublicKey();
}
/**
* Perform all the server specific administration of a new company.
* @param c The newly created company; can't be nullptr.
@@ -2201,21 +2167,20 @@ void NetworkServerNewCompany(const Company *c, NetworkClientInfo *ci)
if (!_network_server) return;
_network_company_states[c->index].months_empty = 0;
_network_company_states[c->index].password.clear();
NetworkServerUpdateCompanyPassworded(c->index, false);
if (ci != nullptr) {
/* 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_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) */
/*
* This function is called from a command, but is only called for the server.
* The client information is managed out-of-band from the commands, so to not have a
* different state/president/company name in the different clients, we need to
* circumvent the normal ::Post logic and go directly to sending the command.
*/
Command<CMD_COMPANY_ALLOW_LIST_CTRL>::SendNet(STR_NULL, c->index, CALCA_ADD, ci->public_key);
Command<CMD_RENAME_PRESIDENT>::SendNet(STR_NULL, c->index, ci->client_name);
NetworkServerSendChat(NETWORK_ACTION_COMPANY_NEW, DESTTYPE_BROADCAST, 0, "", ci->client_id, c->index + 1);
}
}
+12 -12
View File
@@ -24,16 +24,18 @@ 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<ServerNetworkGameSocketHandler, PACKET_SERVER_FULL, PACKET_SERVER_BANNED> {
protected:
std::unique_ptr<class NetworkAuthenticationServerHandler> authentication_handler; ///< The handler for the authentication.
std::string peer_public_key; ///< The public key of our client.
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_COMPANY_PASSWORD(Packet &p) override;
NetworkRecvStatus Receive_CLIENT_AUTH_RESPONSE(Packet &p) override;
NetworkRecvStatus Receive_CLIENT_GETMAP(Packet &p) override;
NetworkRecvStatus Receive_CLIENT_MAP_OK(Packet &p) override;
NetworkRecvStatus Receive_CLIENT_ACK(Packet &p) override;
NetworkRecvStatus Receive_CLIENT_COMMAND(Packet &p) override;
NetworkRecvStatus Receive_CLIENT_CHAT(Packet &p) override;
NetworkRecvStatus Receive_CLIENT_SET_PASSWORD(Packet &p) override;
NetworkRecvStatus Receive_CLIENT_SET_NAME(Packet &p) override;
NetworkRecvStatus Receive_CLIENT_QUIT(Packet &p) override;
NetworkRecvStatus Receive_CLIENT_ERROR(Packet &p) override;
@@ -44,16 +46,16 @@ protected:
NetworkRecvStatus SendGameInfo();
NetworkRecvStatus SendNewGRFCheck();
NetworkRecvStatus SendWelcome();
NetworkRecvStatus SendNeedGamePassword();
NetworkRecvStatus SendNeedCompanyPassword();
NetworkRecvStatus SendAuthRequest();
NetworkRecvStatus SendEnableEncryption();
public:
/** Status of a client */
enum ClientStatus {
STATUS_INACTIVE, ///< The client is not connected nor active.
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_IDENTIFY, ///< The client is identifying itself.
STATUS_NEWGRFS_CHECK, ///< The client is checking NewGRFs.
STATUS_AUTHORIZED, ///< The client is authorized.
STATUS_MAP_WAIT, ///< The client is waiting as someone else is downloading the map.
STATUS_MAP, ///< The client is downloading the map.
@@ -63,8 +65,8 @@ public:
STATUS_END, ///< Must ALWAYS be on the end of this list!! (period).
};
byte lag_test; ///< Byte used for lag-testing the client
byte last_token; ///< The last random token we did send to verify the client is listening
uint8_t lag_test; ///< Byte used for lag-testing the client
uint8_t last_token; ///< The last random token we did send to verify the client is listening
uint32_t last_token_frame; ///< The last frame we received the right token
ClientStatus status; ///< Status of this client
CommandQueue outgoing_queue; ///< The command-queue awaiting delivery; conceptually more a bucket to gather commands in, after which the whole bucket is sent to the client.
@@ -101,7 +103,6 @@ public:
NetworkRecvStatus SendFrame();
NetworkRecvStatus SendSync();
NetworkRecvStatus SendCommand(const CommandPacket &cp);
NetworkRecvStatus SendCompanyUpdate();
NetworkRecvStatus SendConfigUpdate();
static void Send();
@@ -118,13 +119,12 @@ public:
}
const std::string &GetClientIP();
std::string_view GetPeerPublicKey() const { return this->peer_public_key; }
static ServerNetworkGameSocketHandler *GetByClientID(ClientID client_id);
};
void NetworkServer_Tick(bool send_frame);
void ChangeNetworkRestartTime(bool reset);
void NetworkServerSetCompanyPassword(CompanyID company_id, const std::string &password, bool already_hashed = true);
void NetworkServerUpdateCompanyPassworded(CompanyID company_id, bool passworded);
#endif /* NETWORK_SERVER_H */
+1 -1
View File
@@ -92,7 +92,7 @@ std::unique_ptr<ClientNetworkStunSocketHandler> ClientNetworkStunSocketHandler::
stun_handler->Connect(token, family);
auto p = std::make_unique<Packet>(PACKET_STUN_SERCLI_STUN);
auto p = std::make_unique<Packet>(stun_handler.get(), PACKET_STUN_SERCLI_STUN);
p->Send_uint8(NETWORK_COORDINATOR_VERSION);
p->Send_string(token);
p->Send_uint8(family);
+1 -1
View File
@@ -100,7 +100,7 @@ void ClientNetworkTurnSocketHandler::Connect()
{
auto turn_handler = std::make_unique<ClientNetworkTurnSocketHandler>(token, tracking_number, connection_string);
auto p = std::make_unique<Packet>(PACKET_TURN_SERCLI_CONNECT);
auto p = std::make_unique<Packet>(turn_handler.get(), PACKET_TURN_SERCLI_CONNECT);
p->Send_uint8(NETWORK_COORDINATOR_VERSION);
p->Send_string(ticket);
+16 -13
View File
@@ -70,25 +70,13 @@ struct NetworkCompanyStats {
bool ai; ///< Is this company an AI
};
/** Some state information of a company, especially for servers */
struct NetworkCompanyState {
std::string password; ///< The password for the company
uint16_t months_empty; ///< How many months the company is empty
};
struct NetworkClientInfo;
/** The type of password we're asking for. */
enum NetworkPasswordType {
NETWORK_GAME_PASSWORD, ///< The password of the game.
NETWORK_COMPANY_PASSWORD, ///< The password of the company.
};
/**
* Destination of our chat messages.
* @warning The values of the enum items are part of the admin network API. Only append at the end.
*/
enum DestType : byte {
enum DestType : uint8_t {
DESTTYPE_BROADCAST, ///< Send message/notice to all clients (All)
DESTTYPE_TEAM, ///< Send message/notice to everyone playing the same company (Team)
DESTTYPE_CLIENT, ///< Send message/notice to only a certain client (Private)
@@ -145,8 +133,23 @@ enum NetworkErrorCode {
NETWORK_ERROR_TIMEOUT_MAP,
NETWORK_ERROR_TIMEOUT_JOIN,
NETWORK_ERROR_INVALID_CLIENT_NAME,
NETWORK_ERROR_NOT_ON_ALLOW_LIST,
NETWORK_ERROR_NO_AUTHENTICATION_METHOD_AVAILABLE,
NETWORK_ERROR_END,
};
/**
* Simple helper to (more easily) manage authorized keys.
*
* The authorized keys are hexadecimal representations of their binary form.
* The authorized keys are case insensitive.
*/
class NetworkAuthorizedKeys : public std::vector<std::string> {
public:
bool Contains(std::string_view key) const;
bool Add(std::string_view key);
bool Remove(std::string_view key);
};
#endif /* NETWORK_TYPE_H */
+2 -2
View File
@@ -75,7 +75,7 @@ public:
void ServerNetworkUDPSocketHandler::Receive_CLIENT_FIND_SERVER(Packet &, NetworkAddress &client_addr)
{
Packet packet(PACKET_UDP_SERVER_RESPONSE);
Packet packet(this, PACKET_UDP_SERVER_RESPONSE);
this->SendPacket(packet, client_addr);
Debug(net, 7, "Queried from {}", client_addr.GetHostname());
@@ -104,7 +104,7 @@ static void NetworkUDPBroadCast(NetworkUDPSocketHandler *socket)
for (NetworkAddress &addr : _broadcast_list) {
Debug(net, 5, "Broadcasting to {}", addr.GetHostname());
Packet p(PACKET_UDP_CLIENT_FIND_SERVER);
Packet p(socket, PACKET_UDP_CLIENT_FIND_SERVER);
socket->SendPacket(p, addr, true, true);
}
}