diff options
Diffstat (limited to 'src/master/messages.c')
-rw-r--r-- | src/master/messages.c | 574 |
1 files changed, 574 insertions, 0 deletions
diff --git a/src/master/messages.c b/src/master/messages.c new file mode 100644 index 0000000..a86e795 --- /dev/null +++ b/src/master/messages.c @@ -0,0 +1,574 @@ +/* + messages.c + + Message management for tremmaster + + Copyright (C) 2004 Mathieu Olivier + + This program 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; either version 2 of the License, or + (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include "common.h" +#include "messages.h" +#include "servers.h" + + +// ---------- Constants ---------- // + +// Timeouts (in secondes) +#define TIMEOUT_HEARTBEAT 2 +#define TIMEOUT_INFORESPONSE (15 * 60) + +// Period of validity for a challenge string (in secondes) +#define TIMEOUT_CHALLENGE 2 + +// Maximum size of a reponse packet +#define MAX_PACKET_SIZE 1400 + + +// Types of messages (with samples): + +// "heartbeat Tremulous\n" +#define S2M_HEARTBEAT "heartbeat" + +// "gamestat <data>" +#define S2M_GAMESTAT "gamestat" + +// "getinfo A_Challenge" +#define M2S_GETINFO "getinfo" + +// "infoResponse\n\\pure\\1\\..." +#define S2M_INFORESPONSE "infoResponse\x0A" + +// "getservers 67 empty full" +#define C2M_GETSERVERS "getservers " + +// "getserversResponse\\...(6 bytes)...\\...(6 bytes)...\\EOT\0\0\0" +#define M2C_GETSERVERSREPONSE "getserversResponse" + +#define C2M_GETMOTD "getmotd" +#define M2C_MOTD "motd " + + +// ---------- Private functions ---------- // + +/* +==================== +SearchInfostring + +Search an infostring for the value of a key +==================== +*/ +static char* SearchInfostring (const char* infostring, const char* key) +{ + static char value [256]; + char crt_key [256]; + size_t value_ind, key_ind; + char c; + + if (*infostring++ != '\\') + return NULL; + + value_ind = 0; + for (;;) + { + key_ind = 0; + + // Get the key name + for (;;) + { + c = *infostring++; + + if (c == '\0') + return NULL; + if (c == '\\' || key_ind == sizeof (crt_key) - 1) + { + crt_key[key_ind] = '\0'; + break; + } + + crt_key[key_ind++] = c; + } + + // If it's the key we are looking for, save it in "value" + if (!strcmp (crt_key, key)) + { + for (;;) + { + c = *infostring++; + + if (c == '\0' || c == '\\' || value_ind == sizeof (value) - 1) + { + value[value_ind] = '\0'; + return value; + } + + value[value_ind++] = c; + } + } + + // Else, skip the value + for (;;) + { + c = *infostring++; + + if (c == '\0') + return NULL; + if (c == '\\') + break; + } + } +} + + +/* +==================== +BuildChallenge + +Build a challenge string for a "getinfo" message +==================== +*/ +static const char* BuildChallenge (void) +{ + static char challenge [CHALLENGE_MAX_LENGTH]; + size_t ind; + size_t length = CHALLENGE_MIN_LENGTH - 1; // We start at the minimum size + + // ... then we add a random number of characters + length += rand () % (CHALLENGE_MAX_LENGTH - CHALLENGE_MIN_LENGTH + 1); + + for (ind = 0; ind < length; ind++) + { + char c; + do + { + c = 33 + rand () % (126 - 33 + 1); // -> c = 33..126 + } while (c == '\\' || c == ';' || c == '"' || c == '%' || c == '/'); + + challenge[ind] = c; + } + + challenge[length] = '\0'; + return challenge; +} + + +/* +==================== +SendGetInfo + +Send a "getinfo" message to a server +==================== +*/ +static void SendGetInfo (server_t* server) +{ + char msg [64] = "\xFF\xFF\xFF\xFF" M2S_GETINFO " "; + + if (!server->challenge_timeout || server->challenge_timeout < crt_time) + { + strncpy (server->challenge, BuildChallenge (), + sizeof (server->challenge) - 1); + server->challenge_timeout = crt_time + TIMEOUT_CHALLENGE; + } + + strncat (msg, server->challenge, sizeof (msg) - strlen (msg) - 1); + sendto (outSock, msg, strlen (msg), 0, + (const struct sockaddr*)&server->address, + sizeof (server->address)); + + MsgPrint (MSG_DEBUG, "%s <--- getinfo with challenge \"%s\"\n", + peer_address, server->challenge); +} + + +/* +==================== +HandleGetServers + +Parse getservers requests and send the appropriate response +==================== +*/ +static void HandleGetServers (const char* msg, const struct sockaddr_in* addr) +{ + const char* packetheader = "\xFF\xFF\xFF\xFF" M2C_GETSERVERSREPONSE "\\"; + const size_t headersize = strlen (packetheader); + char packet [MAX_PACKET_SIZE]; + size_t packetind; + server_t* sv; + unsigned int protocol; + unsigned int sv_addr; + unsigned short sv_port; + qboolean no_empty; + qboolean no_full; + unsigned int numServers = 0; + + // Check if there's a name before the protocol number + // In this case, the message comes from a DarkPlaces-compatible client + protocol = atoi (msg); + + MsgPrint (MSG_NORMAL, "%s ---> getservers( protocol version %d )\n", + peer_address, protocol ); + + no_empty = (strstr (msg, "empty") == NULL); + no_full = (strstr (msg, "full") == NULL); + + // Initialize the packet contents with the header + packetind = headersize; + memcpy(packet, packetheader, headersize); + + // Add every relevant server + for (sv = Sv_GetFirst (); /* see below */; sv = Sv_GetNext ()) + { + // If we're done, or if the packet is full, send the packet + if (sv == NULL || packetind > sizeof (packet) - (7 + 6)) + { + // End Of Transmission + packet[packetind ] = 'E'; + packet[packetind + 1] = 'O'; + packet[packetind + 2] = 'T'; + packet[packetind + 3] = '\0'; + packet[packetind + 4] = '\0'; + packet[packetind + 5] = '\0'; + packetind += 6; + + // Send the packet to the client + sendto (inSock, packet, packetind, 0, (const struct sockaddr*)addr, + sizeof (*addr)); + + MsgPrint (MSG_DEBUG, "%s <--- getserversResponse (%u servers)\n", + peer_address, numServers); + + // If we're done + if (sv == NULL) + return; + + // Reset the packet index (no need to change the header) + packetind = headersize; + } + + sv_addr = ntohl (sv->address.sin_addr.s_addr); + sv_port = ntohs (sv->address.sin_port); + + // Extra debugging info + if (max_msg_level >= MSG_DEBUG) + { + MsgPrint (MSG_DEBUG, + "Comparing server: IP:\"%u.%u.%u.%u:%hu\", p:%u, c:%hu\n", + sv_addr >> 24, (sv_addr >> 16) & 0xFF, + (sv_addr >> 8) & 0xFF, sv_addr & 0xFF, + sv_port, sv->protocol, sv->nbclients ); + + if (sv->protocol != protocol) + MsgPrint (MSG_DEBUG, + "Reject: protocol %u != requested %u\n", + sv->protocol, protocol); + if (sv->nbclients == 0 && no_empty) + MsgPrint (MSG_DEBUG, + "Reject: nbclients is %hu/%hu && no_empty\n", + sv->nbclients, sv->maxclients); + if (sv->nbclients == sv->maxclients && no_full) + MsgPrint (MSG_DEBUG, + "Reject: nbclients is %hu/%hu && no_full\n", + sv->nbclients, sv->maxclients); + } + + // Check protocol, options + if (sv->protocol != protocol || + (sv->nbclients == 0 && no_empty) || + (sv->nbclients == sv->maxclients && no_full)) + { + + // Skip it + continue; + } + + // Use the address mapping associated with the server, if any + if (sv->addrmap != NULL) + { + const addrmap_t* addrmap = sv->addrmap; + + sv_addr = ntohl (addrmap->to.sin_addr.s_addr); + if (addrmap->to.sin_port != 0) + sv_port = ntohs (addrmap->to.sin_port); + + MsgPrint (MSG_DEBUG, + "Server address mapped to %u.%u.%u.%u:%hu\n", + sv_addr >> 24, (sv_addr >> 16) & 0xFF, + (sv_addr >> 8) & 0xFF, sv_addr & 0xFF, + sv_port); + } + + // IP address + packet[packetind ] = sv_addr >> 24; + packet[packetind + 1] = (sv_addr >> 16) & 0xFF; + packet[packetind + 2] = (sv_addr >> 8) & 0xFF; + packet[packetind + 3] = sv_addr & 0xFF; + + // Port + packet[packetind + 4] = sv_port >> 8; + packet[packetind + 5] = sv_port & 0xFF; + + // Trailing '\' + packet[packetind + 6] = '\\'; + + MsgPrint (MSG_DEBUG, " - Sending server %u.%u.%u.%u:%hu\n", + (qbyte)packet[packetind ], (qbyte)packet[packetind + 1], + (qbyte)packet[packetind + 2], (qbyte)packet[packetind + 3], + sv_port); + + packetind += 7; + numServers++; + } +} + + +/* +==================== +HandleInfoResponse + +Parse infoResponse messages +==================== +*/ +static void HandleInfoResponse (server_t* server, const char* msg) +{ + char* value; + unsigned int new_protocol = 0, new_maxclients = 0; + + MsgPrint (MSG_DEBUG, "%s ---> infoResponse\n", peer_address); + + // Check the challenge + if (!server->challenge_timeout || server->challenge_timeout < crt_time) + { + MsgPrint (MSG_WARNING, + "WARNING: infoResponse with obsolete challenge from %s\n", + peer_address); + return; + } + value = SearchInfostring (msg, "challenge"); + if (!value || strcmp (value, server->challenge)) + { + MsgPrint (MSG_ERROR, "ERROR: invalid challenge from %s (%s)\n", + peer_address, value); + return; + } + // some people enjoy making it hard to find them + value = SearchInfostring (msg, "hostname"); + if (!value || !value[0]) + { + MsgPrint (MSG_ERROR, "ERROR: no hostname from %s\n", + peer_address, value); + return; + } + + // Check and save the values of "protocol" and "maxclients" + value = SearchInfostring (msg, "protocol"); + if (value) + new_protocol = atoi (value); + value = SearchInfostring (msg, "sv_maxclients"); + if (value) + new_maxclients = atoi (value); + if (!new_protocol || !new_maxclients) + { + MsgPrint (MSG_ERROR, + "ERROR: invalid infoResponse from %s (protocol: %d, maxclients: %d)\n", + peer_address, new_protocol, new_maxclients); + return; + } + server->protocol = new_protocol; + server->maxclients = new_maxclients; + + // Save some other useful values + value = SearchInfostring (msg, "clients"); + if (value) + server->nbclients = atoi (value); + + // Set a new timeout + server->timeout = crt_time + TIMEOUT_INFORESPONSE; +} + + +#define CHALLENGE_KEY "challenge\\" +#define MOTD_KEY "motd\\" + +/* +==================== +HandleGetMotd + +Parse getservers requests and send the appropriate response +==================== +*/ +static void HandleGetMotd( const char* msg, const struct sockaddr_in* addr ) +{ + const char *packetheader = "\xFF\xFF\xFF\xFF" M2C_MOTD "\""; + const size_t headersize = strlen (packetheader); + char packet[ MAX_PACKET_SIZE ]; + char challenge[ MAX_PACKET_SIZE ]; + const char *motd = ""; //FIXME + size_t packetind; + char *value; + char version[ 1024 ], renderer[ 1024 ]; + + MsgPrint( MSG_DEBUG, "%s ---> getmotd\n", peer_address ); + + value = SearchInfostring( msg, "challenge" ); + if( !value ) + { + MsgPrint( MSG_ERROR, "ERROR: invalid challenge from %s (%s)\n", + peer_address, value ); + return; + } + + strncpy( challenge, value, sizeof(challenge)-1 ); + challenge[sizeof(challenge)-1] = '\0'; + + value = SearchInfostring( msg, "renderer" ); + if( value ) + { + strncpy( renderer, value, sizeof(renderer)-1 ); + renderer[sizeof(renderer)-1] = '\0'; + MsgPrint( MSG_DEBUG, "%s is using renderer %s\n", peer_address, value ); + } + + value = SearchInfostring( msg, "version" ); + if( value ) + { + strncpy( version, value, sizeof(version)-1 ); + version[sizeof(version)-1] = '\0'; + MsgPrint( MSG_DEBUG, "%s is using version %s\n", peer_address, value ); + } + +#ifndef _WIN32 + RecordClientStat( peer_address, version, renderer ); +#endif + + // Initialize the packet contents with the header + packetind = headersize; + memcpy( packet, packetheader, headersize ); + + strncpy( &packet[ packetind ], CHALLENGE_KEY, MAX_PACKET_SIZE - packetind - 2 ); + packetind += strlen( CHALLENGE_KEY ); + + strncpy( &packet[ packetind ], challenge, MAX_PACKET_SIZE - packetind - 2 ); + packetind += strlen( challenge ); + packet[ packetind++ ] = '\\'; + + strncpy( &packet[ packetind ], MOTD_KEY, MAX_PACKET_SIZE - packetind - 2 ); + packetind += strlen( MOTD_KEY ); + + strncpy( &packet[ packetind ], motd, MAX_PACKET_SIZE - packetind - 2 ); + packetind += strlen( motd ); + + if (packetind > MAX_PACKET_SIZE - 2) + packetind = MAX_PACKET_SIZE - 2; + + packet[ packetind++ ] = '\"'; + packet[ packetind++ ] = '\0'; + + MsgPrint( MSG_DEBUG, "%s <--- motd\n", peer_address ); + + // Send the packet to the client + sendto( inSock, packet, packetind, 0, (const struct sockaddr*)addr, + sizeof( *addr ) ); +} + +/* +==================== +HandleGameStat +==================== +*/ +static void HandleGameStat( const char* msg, const struct sockaddr_in* addr ) +{ +#ifndef _WIN32 + RecordGameStat( peer_address, msg ); +#endif +} + +// ---------- Public functions ---------- // + +/* +==================== +HandleMessage + +Parse a packet to figure out what to do with it +==================== +*/ +void HandleMessage (const char* msg, size_t length, + const struct sockaddr_in* address) +{ + server_t* server; + + // If it's an heartbeat + if (!strncmp (S2M_HEARTBEAT, msg, strlen (S2M_HEARTBEAT))) + { + char gameId [64]; + + // Extract the game id + sscanf (msg + strlen (S2M_HEARTBEAT) + 1, "%63s", gameId); + MsgPrint (MSG_DEBUG, "%s ---> heartbeat (%s)\n", + peer_address, gameId); + + // Get the server in the list (add it to the list if necessary) + server = Sv_GetByAddr (address, qtrue); + if (server == NULL) + return; + + server->active = qtrue; + + // If we haven't yet received any infoResponse from this server, + // we let it some more time to contact us. After that, only + // infoResponse messages can update the timeout value. + if (!server->maxclients) + server->timeout = crt_time + TIMEOUT_HEARTBEAT; + + // Ask for some infos + SendGetInfo (server); + } + + // If it's an infoResponse message + else if (!strncmp (S2M_INFORESPONSE, msg, strlen (S2M_INFORESPONSE))) + { + server = Sv_GetByAddr (address, qfalse); + if (server == NULL) + return; + + HandleInfoResponse (server, msg + strlen (S2M_INFORESPONSE)); + } + + // If it's a getservers request + else if (!strncmp (C2M_GETSERVERS, msg, strlen (C2M_GETSERVERS))) + { + HandleGetServers (msg + strlen (C2M_GETSERVERS), address); + } + + // If it's a getmotd request + else if (!strncmp (C2M_GETMOTD, msg, strlen (C2M_GETMOTD))) + { + HandleGetMotd (msg + strlen (C2M_GETMOTD), address); + } + + // If it's a game statistic + else if( !strncmp( S2M_GAMESTAT, msg, strlen ( S2M_GAMESTAT ) ) ) + { + server = Sv_GetByAddr(address, qfalse); + if (server == NULL) + return; + if( crt_time - server->lastGameStat > MIN_GAMESTAT_DELAY ) + HandleGameStat( msg + strlen( S2M_GAMESTAT ), address ); + else + MsgPrint( MSG_NORMAL, "%s flooding GAMESTAT messages\n", peer_address ); + server->lastGameStat = crt_time; + } +} |