diff options
| author | Paweł Redman <pawel.redman@gmail.com> | 2017-03-22 17:56:34 +0100 | 
|---|---|---|
| committer | Paweł Redman <pawel.redman@gmail.com> | 2017-03-22 17:56:34 +0100 | 
| commit | 6a777afc079c2a8d3af3ecd2145fe8dd50567a39 (patch) | |
| tree | 520f4489cebf8564ef6cb27064ceea45cbc005b3 /src/master | |
Diffstat (limited to 'src/master')
| -rw-r--r-- | src/master/Makefile | 51 | ||||
| -rw-r--r-- | src/master/common.h | 91 | ||||
| -rw-r--r-- | src/master/master.c | 868 | ||||
| -rw-r--r-- | src/master/messages.c | 566 | ||||
| -rw-r--r-- | src/master/messages.h | 35 | ||||
| -rw-r--r-- | src/master/servers.c | 666 | ||||
| -rw-r--r-- | src/master/servers.h | 106 | ||||
| -rw-r--r-- | src/master/stats.c | 135 | 
8 files changed, 2518 insertions, 0 deletions
diff --git a/src/master/Makefile b/src/master/Makefile new file mode 100644 index 0000000..2f60b41 --- /dev/null +++ b/src/master/Makefile @@ -0,0 +1,51 @@ +BD_DEBUG=debug-$(PLATFORM)-$(ARCH) +BD_RELEASE=release-$(PLATFORM)-$(ARCH) + +ifeq ($(PLATFORM),mingw32) +  BINEXT=.exe +  RELEASE_LDFLAGS=-lwsock32 +  DEBUG_LDFLAGS=-lwsock32 +  RM=rm -f +  MKDIR=mkdir +else +  BINEXT= +  RELEASE_LDFLAGS=-ltdb +  DEBUG_LDFLAGS=-ltdb +  RM=rm -f +  MKDIR=mkdir +endif + +CC=gcc +RELEASE_CFLAGS=-Wall -O2 +DEBUG_CFLAGS=-g +OBJECTS= \ +	$(BD)/master.o \ +	$(BD)/messages.o \ +	$(BD)/stats.o \ +	$(BD)/servers.o + +release: makedirs +	$(MAKE) $(BD_RELEASE)/tremmaster BD=$(BD_RELEASE) \ +	CFLAGS="$(CFLAGS) $(RELEASE_CFLAGS)" LDFLAGS="$(LDFLAGS) $(RELEASE_LDFLAGS)" + +debug: makedirs +	$(MAKE) $(BD_DEBUG)/tremmaster BD=$(BD_DEBUG) \ +	CFLAGS="$(CFLAGS) $(DEBUG_CFLAGS)" LDFLAGS="$(LDFLAGS) $(DEBUG_LDFLAGS)" + +all: release debug + +$(BD)/%.o: %.c +	$(CC) $(CFLAGS) -o $@ -c $< + +$(BD)/tremmaster: $(OBJECTS) +	$(CC) -o $@ $(OBJECTS) $(LDFLAGS) + +clean: +	-$(RM) $(BD_DEBUG)/* +	-$(RM) $(BD_RELEASE)/* + +makedirs: +	@if [ ! -d $(BD_RELEASE) ];then $(MKDIR) $(BD_RELEASE);fi +	@if [ ! -d $(BD_DEBUG) ];then $(MKDIR) $(BD_DEBUG);fi + +.PHONY: all clean release debug makedirs diff --git a/src/master/common.h b/src/master/common.h new file mode 100644 index 0000000..8c5c3b6 --- /dev/null +++ b/src/master/common.h @@ -0,0 +1,91 @@ +/* +	common.h + +	Common header file for dpmaster + +	Copyright (C) 2004-2005  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 +*/ + + +#ifndef _COMMON_H_ +#define _COMMON_H_ + + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <time.h> + +#ifdef WIN32 +# include <winsock2.h> +#else +# include <netinet/in.h> +# include <arpa/inet.h> +# include <netdb.h> +# include <sys/socket.h> +#endif + + +// ---------- Types ---------- // + +// A few basic types +typedef enum {qfalse, qtrue} qboolean; +typedef unsigned char qbyte; + +// The various messages levels +typedef enum +{ +	MSG_NOPRINT,	// used by "max_msg_level" (= no printings) +	MSG_ERROR,		// errors +	MSG_WARNING,	// warnings +	MSG_NORMAL,		// standard messages +	MSG_DEBUG		// for debugging purpose +} msg_level_t; + + +// ---------- Public variables ---------- // + +// The master socket +extern int inSock; +extern int outSock; + +// The current time (updated every time we receive a packet) +extern time_t crt_time; + +// Maximum level for a message to be printed +extern msg_level_t max_msg_level; + +// Peer address. We rebuild it every time we receive a new packet +extern char peer_address [128]; + + +// ---------- Public functions ---------- // + +// Win32 uses a different name for some standard functions +#ifdef WIN32 +# define snprintf _snprintf +#endif + +// Print a message to screen, depending on its verbose level +int MsgPrint (msg_level_t msg_level, const char* format, ...); + +void RecordClientStat( const char *address, const char *version, const char *renderer ); +void RecordGameStat( const char *address, const char *dataText ); + +#endif  // #ifndef _COMMON_H_ diff --git a/src/master/master.c b/src/master/master.c new file mode 100644 index 0000000..11cec6a --- /dev/null +++ b/src/master/master.c @@ -0,0 +1,868 @@ +/* +	master.c + +	A master server for Tremulous + +	Copyright (C) 2002-2005  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 <stdarg.h> +#include <signal.h> +#include <ctype.h> + +#ifndef WIN32 +# include <pwd.h> +# include <unistd.h> +#endif + +#include "common.h" +#include "messages.h" +#include "servers.h" + + +// ---------- Constants ---------- // + +// Version of dpmaster +#define VERSION "1.6" + +// Default master port +#define DEFAULT_MASTER_PORT 30710 + +// Maximum and minimum sizes for a valid packet +#define MAX_PACKET_SIZE 2048 +#define MIN_PACKET_SIZE 5 + +#ifndef WIN32 +// Default path we use for chroot +# define DEFAULT_JAIL_PATH "/var/empty/" + +// User we use by default for dropping super-user privileges +# define DEFAULT_LOW_PRIV_USER "nobody" +#endif + + +// ---------- Types ---------- // + +#ifdef WIN32 +typedef int socklen_t; +#endif + + +// ---------- Private variables ---------- // + +// The port we use +static unsigned short master_port = DEFAULT_MASTER_PORT; + +// Local address we listen on, if any +static const char* listen_name = NULL; +static struct in_addr listen_addr; + +#ifndef WIN32 +// On UNIX systems, we can run as a daemon +static qboolean daemon_mode = qfalse; + +// Path we use for chroot +static const char* jail_path = DEFAULT_JAIL_PATH; + +// Low privileges user +static const char* low_priv_user = DEFAULT_LOW_PRIV_USER; +#endif + + +// ---------- Public variables ---------- // + +// The master socket +int inSock = -1; +int outSock = -1; + +// The current time (updated every time we receive a packet) +time_t crt_time; + +// Maximum level for a message to be printed +msg_level_t max_msg_level = MSG_NORMAL; + +// Peer address. We rebuild it every time we receive a new packet +char peer_address [128]; + + +// ---------- Private functions ---------- // + +/* +==================== +PrintPacket + +Print the contents of a packet on stdout +==================== +*/ +static void PrintPacket (const char* packet, size_t length) +{ +	size_t i; + +	// Exceptionally, we use MSG_NOPRINT here because if the function is +	// called, the user probably wants this text to be displayed +	// whatever the maximum message level is. +	MsgPrint (MSG_NOPRINT, "\""); + +	for (i = 0; i < length; i++) +	{ +		char c = packet[i]; +		if (c == '\\') +			MsgPrint (MSG_NOPRINT, "\\\\"); +		else if (c == '\"') +			MsgPrint (MSG_NOPRINT, "\""); +		else if (c >= 32 && (qbyte)c <= 127) +		 	MsgPrint (MSG_NOPRINT, "%c", c); +		else +			MsgPrint (MSG_NOPRINT, "\\x%02X", c); +	} + +	MsgPrint (MSG_NOPRINT, "\" (%u bytes)\n", length); +} + + +/* +==================== +SysInit + +System dependent initializations +==================== +*/ +static qboolean SysInit (void) +{ +#ifdef WIN32 +	WSADATA winsockdata; + +	if (WSAStartup (MAKEWORD (1, 1), &winsockdata)) +	{ +		MsgPrint (MSG_ERROR, "ERROR: can't initialize winsocks\n"); +		return qfalse; +	} +#endif + +	return qtrue; +} + + +/* +==================== +UnsecureInit + +System independent initializations, called BEFORE the security initializations. +We need this intermediate step because DNS requests may not be able to resolve +after the security initializations, due to chroot. +==================== +*/ +static qboolean UnsecureInit (void) +{ +	// Resolve the address mapping list +	if (! Sv_ResolveAddressMappings ()) +		return qfalse; + +	// Resolve the listen address if one was specified +	if (listen_name != NULL) +	{ +		struct hostent* itf; + +		itf = gethostbyname (listen_name); +		if (itf == NULL) +		{ +			MsgPrint (MSG_ERROR, "ERROR: can't resolve %s\n", listen_name); +			return qfalse; +		} +		if (itf->h_addrtype != AF_INET) +		{ +			MsgPrint (MSG_ERROR, "ERROR: %s is not an IPv4 address\n", +					  listen_name); +			return qfalse; +		} + +		memcpy (&listen_addr.s_addr, itf->h_addr, +				sizeof (listen_addr.s_addr)); +	} + +	return qtrue; +} + + +/* +==================== +SecInit + +Security initializations (system dependent) +==================== +*/ +static qboolean SecInit (void) +{ +#ifndef WIN32 +	// Should we run as a daemon? +	if (daemon_mode && daemon (0, 0)) +	{ +		MsgPrint (MSG_NOPRINT, "ERROR: daemonization failed (%s)\n", +				  strerror (errno)); +		return qfalse; +	} + +	// UNIX allows us to be completely paranoid, so let's go for it +	if (geteuid () == 0) +	{ +		struct passwd* pw; + +		MsgPrint (MSG_WARNING, +				  "WARNING: running with super-user privileges\n"); + +		// We must get the account infos before the calls to chroot and chdir +		pw = getpwnam (low_priv_user); +		if (pw == NULL) +		{ +			MsgPrint (MSG_ERROR, "ERROR: can't get user \"%s\" properties\n", +					  low_priv_user); +			return qfalse; +		} + +		// Chroot ourself +		MsgPrint (MSG_NORMAL, "  - chrooting myself to %s... ", jail_path); +		if (chroot (jail_path) || chdir ("/")) +		{ +			MsgPrint (MSG_ERROR, "FAILED (%s)\n", strerror (errno)); +			return qfalse; +		} +		MsgPrint (MSG_NORMAL, "succeeded\n"); + +		// Switch to lower privileges +		MsgPrint (MSG_NORMAL, "  - switching to user \"%s\" privileges... ", +				  low_priv_user); +		if (setgid (pw->pw_gid) || setuid (pw->pw_uid)) +		{ +			MsgPrint (MSG_ERROR, "FAILED (%s)\n", strerror (errno)); +			return qfalse; +		} +		MsgPrint (MSG_NORMAL, "succeeded (UID: %u, GID: %u)\n", +				  pw->pw_uid, pw->pw_gid); + +		MsgPrint (MSG_NORMAL, "\n"); +	} +#endif + +	return qtrue; +} + + +/* +==================== +ParseCommandLine + +Parse the options passed by the command line +==================== +*/ +static qboolean ParseCommandLine (int argc, const char* argv []) +{ +	int ind = 1; +	unsigned int vlevel = max_msg_level; +	qboolean valid_options = qtrue; + +	while (ind < argc && valid_options) +	{ +		// If it doesn't even look like an option, why bother? +		if (argv[ind][0] != '-') +			valid_options = qfalse; + +		else switch (argv[ind][1]) +		{ +#ifndef WIN32 +			// Daemon mode +			case 'D': +				daemon_mode = qtrue; +				break; +#endif + +			// Help +			case 'h': +				valid_options = qfalse; +				break; + +			// Hash size +			case 'H': +				ind++; +				if (ind < argc) +					valid_options = Sv_SetHashSize (atoi (argv[ind])); +				else +					valid_options = qfalse; +				break; + +#ifndef WIN32 +			// Jail path +			case 'j': +				ind++; +				if (ind < argc) +					jail_path = argv[ind]; +				else +					valid_options = qfalse; +				break; +#endif + +			// Listen address +			case 'l': +				ind++; +				if (ind >= argc || argv[ind][0] == '\0') +					valid_options = qfalse; +				else +					listen_name = argv[ind]; +				break; + +			// Address mapping +			case 'm': +				ind++; +				if (ind < argc) +					valid_options = Sv_AddAddressMapping (argv[ind]); +				else +					valid_options = qfalse; +				break; + +			// Maximum number of servers +			case 'n': +				ind++; +				if (ind < argc) +					valid_options = Sv_SetMaxNbServers (atoi (argv[ind])); +				else +					valid_options = qfalse; +				break; + +			// Port number +			case 'p': +			{ +				unsigned short port_num = 0; +				ind++; +				if (ind < argc) +					port_num = atoi (argv[ind]); +				if (!port_num) +					valid_options = qfalse; +				else +					master_port = port_num; +				break; +			} + +#ifndef WIN32 +			// Low privileges user +			case 'u': +				ind++; +				if (ind < argc) +					low_priv_user = argv[ind]; +				else +					valid_options = qfalse; +				break; +#endif + +			// Verbose level +			case 'v': +				// If a verbose level has been specified +				if (ind + 1 < argc && argv[ind + 1][0] != '-') +				{ +					ind++; +					vlevel = atoi (argv[ind]); +					if (vlevel > MSG_DEBUG) +						valid_options = qfalse; +				} +				else +					vlevel = MSG_DEBUG; +				break; + +			default: +				valid_options = qfalse; +		} + +		ind++; +	} + +	// If the command line is OK, we can set the verbose level now +	if (valid_options) +	{ +#ifndef WIN32 +		// If we run as a daemon, don't bother printing anything +		if (daemon_mode) +			max_msg_level = MSG_NOPRINT; +		else +#endif +			max_msg_level = vlevel; +	} + +	return valid_options; +} + + +/* +==================== +PrintHelp + +Print the command line syntax and the available options +==================== +*/ +static void PrintHelp (void) +{ +	MsgPrint (MSG_ERROR, +			  "Syntax: dpmaster [options]\n" +			  "Available options are:\n" +#ifndef WIN32 +			  "  -D               : run as a daemon\n" +#endif +			  "  -h               : this help\n" +			  "  -H <hash_size>   : hash size in bits, up to %u (default: %u)\n" +#ifndef WIN32 +			  "  -j <jail_path>   : use <jail_path> as chroot path (default: %s)\n" +			  "                     only available when running with super-user privileges\n" +#endif +			  "  -l <address>     : listen on local address <address>\n" +			  "  -m <a1>=<a2>     : map address <a1> to <a2> when sending it to clients\n" +			  "                     addresses can contain a port number (ex: myaddr.net:1234)\n" +			  "  -n <max_servers> : maximum number of servers recorded (default: %u)\n" +			  "  -p <port_num>    : use port <port_num> (default: %u)\n" +#ifndef WIN32 +			  "  -u <user>        : use <user> privileges (default: %s)\n" +			  "                     only available when running with super-user privileges\n" +#endif +			  "  -v [verbose_lvl] : verbose level, up to %u (default: %u; no value means max)\n" +			  "\n", +			  MAX_HASH_SIZE, DEFAULT_HASH_SIZE, +#ifndef WIN32 +			  DEFAULT_JAIL_PATH, +#endif +			  DEFAULT_MAX_NB_SERVERS, +			  DEFAULT_MASTER_PORT, +#ifndef WIN32 +			  DEFAULT_LOW_PRIV_USER, +#endif +			  MSG_DEBUG, MSG_NORMAL); +} + + +/* +==================== +SecureInit + +System independent initializations, called AFTER the security initializations +==================== +*/ +static qboolean SecureInit (void) +{ +	struct sockaddr_in address; + +	// Init the time and the random seed +	crt_time = time (NULL); +	srand (crt_time); + +	// Initialize the server list and hash table +	if (!Sv_Init ()) +		return qfalse; + +	// Open the socket +	inSock = socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP); +	if (inSock < 0) +	{ +		MsgPrint (MSG_ERROR, "ERROR: socket creation failed (%s)\n", +				  strerror (errno)); +		return qfalse; +	} + +	outSock = socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP); +	if (outSock < 0) +	{ +		MsgPrint (MSG_ERROR, "ERROR: socket creation failed (%s)\n", +				  strerror (errno)); +		return qfalse; +	} + +	// Bind it to the master port +	memset (&address, 0, sizeof (address)); +	address.sin_family = AF_INET; +	if (listen_name != NULL) +	{ +		MsgPrint (MSG_NORMAL, "Listening on address %s (%s)\n", +				  listen_name, inet_ntoa (listen_addr)); +		address.sin_addr.s_addr = listen_addr.s_addr; +	} +	else +		address.sin_addr.s_addr = htonl (INADDR_ANY); + +	address.sin_port = htons (master_port); +	if (bind (inSock, (struct sockaddr*)&address, sizeof (address)) != 0) +	{ +		MsgPrint (MSG_ERROR, "ERROR: socket binding failed (%s)\n", +				  strerror (errno)); +#ifdef WIN32 +		closesocket (inSock); +#else +		close (inSock); +#endif +		return qfalse; +	} +	MsgPrint (MSG_NORMAL, "Listening on UDP port %hu\n", +			  ntohs (address.sin_port)); + +	// Deliberately use a different port for outgoing traffic in order +	// to confuse NAT UDP "connection" tracking and thus delist servers +	// hidden by NAT +	address.sin_port = htons (master_port+1); +	if (bind (outSock, (struct sockaddr*)&address, sizeof (address)) != 0) +	{ +		MsgPrint (MSG_ERROR, "ERROR: socket binding failed (%s)\n", +				  strerror (errno)); +#ifdef WIN32 +		closesocket (outSock); +#else +		close (outSock); +#endif +		return qfalse; +	} + +	return qtrue; +} + +static qboolean exitNow = qfalse; + +/* +=============== +cleanUp + +Clean up +=============== +*/ +static void cleanUp( int signal ) +{ +	MsgPrint( MSG_NORMAL, "Caught signal %d, exiting...\n", signal ); + +	exitNow = qtrue; +} + +#define ADDRESS_LENGTH 16 +static const char *ignoreFile = "ignore.txt"; + +typedef struct +{ +	char address[ ADDRESS_LENGTH ]; // Dotted quad +} ignoreAddress_t; + +#define PARSE_INTERVAL		60 // seconds + +static time_t							lastParseTime				= 0; +static int								numIgnoreAddresses	= 0; +static ignoreAddress_t		*ignoreAddresses		= NULL; + +/* +==================== +parseIgnoreAddress +==================== +*/ +static qboolean parseIgnoreAddress( void ) +{ +	int		numAllocIgnoreAddresses	= 1; +	FILE	*f											= NULL; +	int		i; + +	// Only reparse periodically +	if( crt_time - lastParseTime < PARSE_INTERVAL ) +		return qtrue; + +	lastParseTime = time( NULL ); + +	// Free existing list +	if( ignoreAddresses != NULL ) +	{ +		free( ignoreAddresses ); +		ignoreAddresses = NULL; +	} + +	numIgnoreAddresses			= 0; +	ignoreAddresses					= malloc( sizeof( ignoreAddress_t ) * numAllocIgnoreAddresses ); + +	// Alloc failed, fail parsing +	if( ignoreAddresses == NULL ) +		return qfalse; + +	f = fopen( ignoreFile, "r" ); + +	if( !f ) +	{ +		free( ignoreAddresses ); +		ignoreAddresses = NULL; +		return qfalse; +	} + +	while( !feof( f ) ) +	{ +		char	c; +		char	buffer[ ADDRESS_LENGTH ]; + +		i = 0; + +		// Skip whitespace +		do +		{ +			c = fgetc( f ); +		} +		while( c != EOF && isspace( c ) ); + +		if( c != EOF ) +		{ +			do +			{ +				if( i >= ADDRESS_LENGTH ) +				{ +					buffer[ i - 1 ] = '\0'; +					break; +				} + +				buffer[ i ] = c; + +				if( isspace( c ) ) +				{ +					buffer[ i ] = '\0'; +					break; +				} + +				i++; +			} while( ( c = fgetc( f ) ) != EOF ); + +			strcpy( ignoreAddresses[ numIgnoreAddresses ].address, buffer ); + +			numIgnoreAddresses++; + +			// Make list bigger +			if( numIgnoreAddresses >= numAllocIgnoreAddresses ) +			{ +				ignoreAddress_t *new; + +				numAllocIgnoreAddresses *= 2; +				new = realloc( ignoreAddresses, +						sizeof( ignoreAddress_t ) * numAllocIgnoreAddresses ); + +				// Alloc failed, fail parsing +				if( new == NULL ) +				{ +					fclose( f ); +					free( ignoreAddresses ); +					ignoreAddresses = NULL; +					return qfalse; +				} + +				ignoreAddresses = new; +			} +		} +	} + +	fclose( f ); + +	return qtrue; +} + +/* +==================== +ignoreAddress + +Check whether or not to ignore a specific address +==================== +*/ +static qboolean ignoreAddress( const char *address ) +{ +	int i; + +	if( !parseIgnoreAddress( ) ) +	{ +		// Couldn't parse, allow the address +		return qfalse; +	} + +	for( i = 0; i < numIgnoreAddresses; i++ ) +	{ +		if( strcmp( address, ignoreAddresses[ i ].address ) == 0 ) +			break; +	} + +	// It matched +	if( i < numIgnoreAddresses ) +		return qtrue; + +	return qfalse; +} + +/* +==================== +max + +Maximum of two ints +==================== +*/ +static inline int max( int a, int b ) +{ +	return a > b ? a : b; +} + +/* +==================== +main + +Main function +==================== +*/ +int main (int argc, const char* argv []) +{ +	struct sockaddr_in address; +	socklen_t addrlen; +	int nb_bytes; +	int sock; +	char packet [MAX_PACKET_SIZE + 1];  // "+ 1" because we append a '\0' +	qboolean valid_options; +	fd_set rfds; +	struct timeval tv; + + +	signal( SIGINT, cleanUp ); +	signal( SIGTERM, cleanUp ); + +	// Get the options from the command line +	valid_options = ParseCommandLine (argc, argv); + +	MsgPrint (MSG_NORMAL, +			"tremmaster (version " VERSION " " __DATE__ " " __TIME__ ")\n" ); + +	// If there was a mistake in the command line, print the help and exit +	if (!valid_options) +	{ +		PrintHelp (); +		return EXIT_FAILURE; +	} + +	// Initializations +	if (!SysInit () || !UnsecureInit () || !SecInit () || !SecureInit ()) +		return EXIT_FAILURE; +	MsgPrint (MSG_NORMAL, "\n"); + +	// Until the end of times... +	while( !exitNow ) +	{ +		FD_ZERO( &rfds ); +		FD_SET( inSock, &rfds ); +		FD_SET( outSock, &rfds ); +		tv.tv_sec = tv.tv_usec = 0; + +		// Check for new data every 100ms +		if( select( max( inSock, outSock ) + 1, &rfds, NULL, NULL, &tv ) <= 0 ) +		{ +#ifdef _WIN32 +			Sleep( 100 ); +#else +			usleep( 100000 ); +#endif +			continue; +		} + +		if( FD_ISSET( inSock, &rfds ) ) +			sock = inSock; +		else if( FD_ISSET( outSock, &rfds ) ) +			sock = outSock; +		else +			continue; + +		// Get the next valid message +		addrlen = sizeof (address); +		nb_bytes = recvfrom (sock, packet, sizeof (packet) - 1, 0, +							 (struct sockaddr*)&address, &addrlen); +		if (nb_bytes <= 0) +		{ +			MsgPrint (MSG_WARNING, +					  "WARNING: \"recvfrom\" returned %d\n", nb_bytes); +			continue; +		} + +		// Ignore abusers +		if( ignoreAddress( inet_ntoa( address.sin_addr ) ) ) +			continue; + +		// If we may have to print something, rebuild the peer address buffer +		if (max_msg_level != MSG_NOPRINT) +			snprintf (peer_address, sizeof (peer_address), "%s:%hu", +					  inet_ntoa (address.sin_addr), ntohs (address.sin_port)); + +		// We print the packet contents if necessary +		// TODO: print the current time here +		if (max_msg_level >= MSG_DEBUG) +		{ +			MsgPrint (MSG_DEBUG, "New packet received from %s: ", +					  peer_address); +			PrintPacket (packet, nb_bytes); +		} + +		// A few sanity checks +		if (nb_bytes < MIN_PACKET_SIZE) +		{ +			MsgPrint (MSG_WARNING, +					  "WARNING: rejected packet from %s (size = %d bytes)\n", +					  peer_address, nb_bytes); +			continue; +		} +		if (*((unsigned int*)packet) != 0xFFFFFFFF) +		{ +			MsgPrint (MSG_WARNING, +					  "WARNING: rejected packet from %s (invalid header)\n", +					  peer_address); +			continue; +		} +		if( ntohs( address.sin_port ) < 1024 ) +		{ +			MsgPrint (MSG_WARNING, +					  "WARNING: rejected packet from %s (source port = 0)\n", +					  peer_address); +			continue; +		} + +		// Append a '\0' to make the parsing easier and update the current time +		packet[nb_bytes] = '\0'; +		crt_time = time (NULL); + +		// Call HandleMessage with the remaining contents +		HandleMessage (packet + 4, nb_bytes - 4, &address); +	} + +	return 0; +} + + +// ---------- Public functions ---------- // + +/* +==================== +MsgPrint + +Print a message to screen, depending on its verbose level +==================== +*/ +int MsgPrint (msg_level_t msg_level, const char* format, ...) +{ +	va_list args; +	int result; + +	// If the message level is above the maximum level, don't print it +	if (msg_level > max_msg_level) +		return 0; + +	va_start (args, format); +	result = vprintf (format, args); +	va_end (args); + +	fflush (stdout); + +	return result; +} diff --git a/src/master/messages.c b/src/master/messages.c new file mode 100644 index 0000000..563023e --- /dev/null +++ b/src/master/messages.c @@ -0,0 +1,566 @@ +/* +	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; +	} + +	// 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; +  } +} diff --git a/src/master/messages.h b/src/master/messages.h new file mode 100644 index 0000000..93b5271 --- /dev/null +++ b/src/master/messages.h @@ -0,0 +1,35 @@ +/* +	messages.h + +	Message management for dpmaster + +	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 +*/ + + +#ifndef _MESSAGES_H_ +#define _MESSAGES_H_ + + +// ---------- Public functions ---------- // + +// Parse a packet to figure out what to do with it +void HandleMessage (const char* msg, size_t length, +					const struct sockaddr_in* address); + + +#endif  // #ifndef _MESSAGES_H_ diff --git a/src/master/servers.c b/src/master/servers.c new file mode 100644 index 0000000..a8412a1 --- /dev/null +++ b/src/master/servers.c @@ -0,0 +1,666 @@ +/* +	servers.c + +	Server list and address mapping management for dpmaster + +	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 "servers.h" + + +// ---------- Constants ---------- // + +// Address hash bitmask +#define HASH_BITMASK (hash_table_size - 1) + + +// ---------- Variables ---------- // + +// All server structures are allocated in one block in the "servers" array. +// Each used slot is also part of a linked list in "hash_table". A simple +// hash of the address and port of a server gives its index in the table. +static server_t* servers = NULL; +static size_t max_nb_servers = DEFAULT_MAX_NB_SERVERS; +static size_t nb_servers = 0; +static server_t** hash_table = NULL; +static size_t hash_table_size = (1 << DEFAULT_HASH_SIZE); + +// Last allocated entry in the "servers" array. +// Used as a start index for finding a free slot in "servers" more quickly +static unsigned int last_alloc; + +// Variables for Sv_GetFirst, Sv_GetNext and Sv_RemoveCurrentAndGetNext +static server_t* crt_server = NULL; +static server_t** prev_pointer = NULL; +static int crt_hash_ind = -1; + +// List of address mappings. They are sorted by "from" field (IP, then port) +static addrmap_t* addrmaps = NULL; + + +// ---------- Private functions ---------- // + +/* +==================== +Sv_AddressHash + +Compute the hash of a server address +==================== +*/ +static unsigned int Sv_AddressHash (const struct sockaddr_in* address) +{ +	qbyte* addr = (qbyte*)&address->sin_addr.s_addr; +	qbyte* port = (qbyte*)&address->sin_port; +	qbyte hash; + +	hash = addr[0] ^ addr[1] ^ addr[2] ^ addr[3] ^ port[0] ^ port[1]; +	return hash & HASH_BITMASK; +} + + +/* +==================== +Sv_RemoveAndGetNextPtr + +Remove a server from the list and returns its "next" pointer +==================== +*/ +static server_t* Sv_RemoveAndGetNextPtr (server_t* sv, server_t** prev) +{ +	nb_servers--; +	MsgPrint (MSG_NORMAL, +	          "%s:%hu timed out; %u servers currently registered\n", +	          inet_ntoa (sv->address.sin_addr), ntohs (sv->address.sin_port), +			  nb_servers); + +	// Mark this structure as "free" +	sv->active = qfalse; + +	*prev = sv->next; +	return sv->next; +} + + +/* +==================== +Sv_ResolveAddr + +Resolve an internet address +name may include a port number, after a ':' +==================== +*/ +static qboolean Sv_ResolveAddr (const char* name, struct sockaddr_in* addr) +{ +	char *namecpy, *port; +	struct hostent* host; + +	// Create a work copy +	namecpy = strdup (name); +	if (namecpy == NULL) +	{ +		MsgPrint (MSG_ERROR, +				  "ERROR: can't allocate enough memory to resolve %s\n", +				  name); +		return qfalse; +	} + +	// Find the port in the address +	port = strchr (namecpy, ':'); +	if (port != NULL) +		*port++ = '\0'; + +	// Resolve the address +	host = gethostbyname (namecpy); +	if (host == NULL) +	{ +		MsgPrint (MSG_ERROR, "ERROR: can't resolve %s\n", namecpy); +		free (namecpy); +		return qfalse; +	} +	if (host->h_addrtype != AF_INET) +	{ +		MsgPrint (MSG_ERROR, "ERROR: %s is not an IPv4 address\n", +				  namecpy); +		free (namecpy); +		return qfalse; +	} + +	// Build the structure +	memset (addr, 0, sizeof (*addr)); +	addr->sin_family = AF_INET; +	memcpy (&addr->sin_addr.s_addr, host->h_addr, +			sizeof (addr->sin_addr.s_addr)); +	if (port != NULL) +		addr->sin_port = htons ((unsigned short)atoi (port)); + +	MsgPrint (MSG_DEBUG, "\"%s\" resolved to %s:%hu\n", +			  name, inet_ntoa (addr->sin_addr), ntohs (addr->sin_port)); + +	free (namecpy); +	return qtrue; +} + + +/* +==================== +Sv_InsertAddrmapIntoList + +Insert an addrmap structure to the addrmaps list +==================== +*/ +static void Sv_InsertAddrmapIntoList (addrmap_t* new_map) +{ +	addrmap_t* addrmap = addrmaps; +	addrmap_t** prev = &addrmaps; + +	// Stop at the end of the list, or if the addresses become too high +	while (addrmap != NULL && +		   addrmap->from.sin_addr.s_addr <= new_map->from.sin_addr.s_addr) +	{ +		// If we found the right place +		if (addrmap->from.sin_addr.s_addr == new_map->from.sin_addr.s_addr && +			addrmap->from.sin_port >= new_map->from.sin_port) +		{ +			// If a mapping is already recorded for this address +			if (addrmap->from.sin_port == new_map->from.sin_port) +			{ +				MsgPrint (MSG_WARNING, +						  "WARNING: Address %s:%hu has several mappings\n", +						  inet_ntoa (new_map->from.sin_addr), +						  ntohs (new_map->from.sin_port)); + +				*prev = addrmap->next; +				free (addrmap); +			} +			break; +		} + +		prev = &addrmap->next; +		addrmap = addrmap->next; +	} + +	// Insert it +	new_map->next = *prev; +	*prev = new_map; + +	MsgPrint (MSG_NORMAL, "Address \"%s\" mapped to \"%s\" (%s:%hu)\n", +			  new_map->from_string, new_map->to_string, +			  inet_ntoa (new_map->to.sin_addr), ntohs (new_map->to.sin_port)); +} + + +/* +==================== +Sv_GetAddrmap + +Look for an address mapping corresponding to addr +==================== +*/ +static const addrmap_t* Sv_GetAddrmap (const struct sockaddr_in* addr) +{ +	const addrmap_t* addrmap = addrmaps; +	const addrmap_t* found = NULL; + +	// Stop at the end of the list, or if the addresses become too high +	while (addrmap != NULL && +		   addrmap->from.sin_addr.s_addr <= addr->sin_addr.s_addr) +	{ +		// If it's the right address +		if (addrmap->from.sin_addr.s_addr == addr->sin_addr.s_addr) +		{ +			// If the exact mapping isn't there +			if (addrmap->from.sin_port > addr->sin_port) +				return found; + +			// If we found the exact address +			if (addrmap->from.sin_port == addr->sin_port) +				return addrmap; + +			// General mapping +			// Store it in case we don't find the exact address mapping +			if (addrmap->from.sin_port == 0) +				found = addrmap; +		} + +		addrmap = addrmap->next; +	} + +	return found; +} + + +/* +==================== +Sv_ResolveAddrmap + +Resolve an addrmap structure and check the parameters validity +==================== +*/ +static qboolean Sv_ResolveAddrmap (addrmap_t* addrmap) +{ +	// Resolve the addresses +	if (!Sv_ResolveAddr (addrmap->from_string, &addrmap->from) || +		!Sv_ResolveAddr (addrmap->to_string, &addrmap->to)) +		return qfalse; + +	// 0.0.0.0 addresses are forbidden +	if (addrmap->from.sin_addr.s_addr == 0 || +		addrmap->to.sin_addr.s_addr == 0) +	{ +		MsgPrint (MSG_ERROR, +				  "ERROR: Mapping from or to 0.0.0.0 is forbidden\n"); +		return qfalse; +	} + +	// Do NOT allow mapping to loopback addresses +	if ((ntohl (addrmap->to.sin_addr.s_addr) >> 24) == 127) +	{ +		MsgPrint (MSG_ERROR, +				  "ERROR: Mapping to a loopback address is forbidden\n"); +		return qfalse; +	} + +	return qtrue; +} + + +/* +==================== +Sv_IsActive + +Return qtrue if a server is active. +Test if the server has timed out and remove it if it's the case. +==================== +*/ +static qboolean Sv_IsActive (server_t* server) +{ + +	// If the entry isn't even used +	if (! server->active) +		return qfalse; + +	// If the server has timed out +	if (server->timeout < crt_time) +	{ +		unsigned int hash; +		server_t **prev, *sv; + +		hash = Sv_AddressHash (&server->address); +		prev = &hash_table[hash]; +		sv = hash_table[hash]; + +		while (sv != server) +		{ +			prev = &sv->next; +			sv = sv->next; +		} + +		Sv_RemoveAndGetNextPtr (sv, prev); +		return qfalse; +	} + +	return qtrue; +} + + +// ---------- Public functions (servers) ---------- // + +/* +==================== +Sv_SetHashSize + +Set a new hash size value +==================== +*/ +qboolean Sv_SetHashSize (unsigned int size) +{ +	// Too late? +	if (hash_table != NULL) +		return qfalse; + +	// Too big? +	if (size > MAX_HASH_SIZE) +		return qfalse; + +	hash_table_size = 1 << size; +	return qtrue; +} + + +/* +==================== +Sv_SetMaxNbServers + +Set a new hash size value +==================== +*/ +qboolean Sv_SetMaxNbServers (unsigned int nb) +{ +	// Too late? +	if (servers != NULL) +		return qfalse; + +	max_nb_servers = nb; +	return qtrue; +} + + +/* +==================== +Sv_Init + +Initialize the server list and hash table +==================== +*/ +qboolean Sv_Init (void) +{ +	size_t array_size; + +	// Allocate "servers" and clean it +	array_size = max_nb_servers * sizeof (servers[0]); +	servers = malloc (array_size); +	if (!servers) +	{ +		MsgPrint (MSG_ERROR, +				  "ERROR: can't allocate the servers array (%s)\n", +				  strerror (errno)); +		return qfalse; +	} +	last_alloc = max_nb_servers - 1; +	memset (servers, 0, array_size); +	MsgPrint (MSG_NORMAL, "%u server records allocated\n", max_nb_servers); + +	// Allocate "hash_table" and clean it +	array_size = hash_table_size * sizeof (hash_table[0]); +	hash_table = malloc (array_size); +	if (!hash_table) +	{ +		MsgPrint (MSG_ERROR, "ERROR: can't allocate the hash table (%s)\n", +				  strerror (errno)); +		free (servers); +		return qfalse; +	} +	memset (hash_table, 0, array_size); +	MsgPrint (MSG_NORMAL, +			  "Hash table allocated (%u entries)\n", hash_table_size); + +	return qtrue; +} + + +/* +==================== +Sv_GetByAddr + +Search for a particular server in the list; add it if necessary +==================== +*/ +server_t* Sv_GetByAddr (const struct sockaddr_in* address, qboolean add_it) +{ +	server_t **prev, *sv; +	unsigned int hash; +	const addrmap_t* addrmap = Sv_GetAddrmap (address); +	unsigned int startpt; + +	// Allow servers on a loopback address ONLY if a mapping is defined for them +	if ((ntohl (address->sin_addr.s_addr) >> 24) == 127 && addrmap == NULL) +	{ +		MsgPrint (MSG_WARNING, +				  "WARNING: server %s isn't allowed (loopback address)\n", +				  peer_address); +		return NULL; +	} + +	hash = Sv_AddressHash (address); +	prev = &hash_table[hash]; +	sv = hash_table[hash]; + +	while (sv != NULL) +	{ +		// We check the timeout values while browsing this list +		if (sv->timeout < crt_time) +		{ +			sv = Sv_RemoveAndGetNextPtr (sv, prev); +			continue; +		} + +		// Found! +		if (sv->address.sin_addr.s_addr == address->sin_addr.s_addr && +			sv->address.sin_port == address->sin_port) +		{ +			// Put it on top of the list (it's useful because heartbeats +			// are almost always followed by infoResponses) +			*prev = sv->next; +			sv->next = hash_table[hash]; +			hash_table[hash] = sv; + +			return sv; +		} + +		prev = &sv->next; +		sv = sv->next; +	} + +	if (! add_it) +		return NULL; + +	// Look for the first free entry in "servers" +	startpt = last_alloc; +	for (;;) +	{ +		last_alloc = (last_alloc + 1) % max_nb_servers; + +		// Free entry found? +		if (!Sv_IsActive (&servers[last_alloc])) +			break; + +		// No more room +		if (last_alloc == startpt) +			return NULL; +	} +	sv = &servers[last_alloc]; + +	// Initialize the structure +	memset (sv, 0, sizeof (*sv)); +	memcpy (&sv->address, address, sizeof (sv->address)); +	sv->addrmap = addrmap; + +	// Add it to the list it belongs to +	sv->next = hash_table[hash]; +	hash_table[hash] = sv; +	nb_servers++; + +	MsgPrint (MSG_NORMAL, +			  "New server added: %s; %u servers are currently registered\n", +			  peer_address, nb_servers); +	MsgPrint (MSG_DEBUG, +			  "  - index: %u\n" +			  "  - hash: 0x%02X\n", +			  last_alloc, hash); + +	return sv; +} + + +/* +==================== +Sv_GetFirst + +Get the first server in the list +==================== +*/ +server_t* Sv_GetFirst (void) +{ +	crt_server = NULL; +	prev_pointer = NULL; +	crt_hash_ind = -1; + +	return Sv_GetNext (); +} + + +/* +==================== +Sv_GetNext + +Get the next server in the list +==================== +*/ +server_t* Sv_GetNext (void) +{ +	for (;;) +	{ +		// If there is a current server, follow the link +		if (crt_server != NULL) +		{ +			prev_pointer = &crt_server->next; +			crt_server = crt_server->next; +		} + +		// If we don't have the next server yet +		if (crt_server == NULL) +		{ +			// Search the hash table for the next server +			while (crt_hash_ind < (int)(hash_table_size - 1)) +			{ +				crt_hash_ind++; + +				if (hash_table[crt_hash_ind] != NULL) +				{ +					crt_server = hash_table[crt_hash_ind]; +					prev_pointer = &hash_table[crt_hash_ind]; +					break; +				} +			} +		} + +		// Did we hit the end of the list? +		if (crt_server == NULL) +			return NULL; + +		// If the new current server has timed out, remove it +		if (crt_server->timeout < crt_time) +			crt_server = Sv_RemoveAndGetNextPtr (crt_server, prev_pointer); +		else +			return crt_server; +	} +} + + +// ---------- Public functions (address mappings) ---------- // + +/* +==================== +Sv_AddAddressMapping + +Add an unresolved address mapping to the list +mapping must be of the form "addr1:port1=addr2:port2", ":portX" are optional +==================== +*/ +qboolean Sv_AddAddressMapping (const char* mapping) +{ +	char *map_string, *to_ip;  +	addrmap_t* addrmap; + +	// Get a working copy of the mapping string +	map_string = strdup (mapping); +	if (map_string == NULL) +	{ +		MsgPrint (MSG_ERROR, +				  "ERROR: can't allocate address mapping string\n"); +		return qfalse; +	} + +	// Find the '=' +	to_ip = strchr (map_string, '='); +	if (to_ip == NULL) +	{ +		MsgPrint (MSG_ERROR, +				  "ERROR: invalid syntax in address mapping string\n"); +		free (map_string); +		return qfalse; +	} +	*to_ip++ = '\0'; + +	// Allocate the structure +	addrmap = malloc (sizeof (*addrmap)); +	if (addrmap == NULL) +	{ +		MsgPrint (MSG_ERROR, +				  "ERROR: can't allocate address mapping structure\n"); +		free (map_string); +		return qfalse; +	} +	memset (addrmap, 0, sizeof (*addrmap)); +	addrmap->from_string = strdup (map_string); +	addrmap->to_string = strdup (to_ip); +	if (addrmap->from_string == NULL || addrmap->to_string == NULL) +	{ +		MsgPrint (MSG_ERROR, +				  "ERROR: can't allocate address mapping strings\n"); +		free (addrmap->to_string); +		free (addrmap->from_string); +		free (map_string); +		return qfalse; +	} + +	// Add it on top of "addrmaps" +	addrmap->next = addrmaps; +	addrmaps = addrmap; + +	return qtrue; +} + + +/* +==================== +Sv_ResolveAddressMappings + +Resolve the address mapping list +==================== +*/ +qboolean Sv_ResolveAddressMappings (void) +{ +	addrmap_t* unresolved = addrmaps; +	addrmap_t* addrmap; +	qboolean succeeded = qtrue; + +	addrmaps = NULL; + +	while (unresolved != NULL) +	{ +		// Remove it from the unresolved list +		addrmap = unresolved; +		unresolved = unresolved->next; + +		// Continue the resolution, even if there's an error +		if (!Sv_ResolveAddrmap (addrmap)) +		{ +			free (addrmap->from_string); +			free (addrmap->to_string); +			free (addrmap); +			succeeded = qfalse; +		} +		else +			Sv_InsertAddrmapIntoList (addrmap); +	} + +	return succeeded; +} diff --git a/src/master/servers.h b/src/master/servers.h new file mode 100644 index 0000000..5864c03 --- /dev/null +++ b/src/master/servers.h @@ -0,0 +1,106 @@ +/* +	servers.h + +	Server list and address mapping management for dpmaster + +	Copyright (C) 2004-2005  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 +*/ + + +#ifndef _SERVERS_H_ +#define _SERVERS_H_ + + +// ---------- Constants ---------- // + +// Maximum number of servers in all lists by default +#define DEFAULT_MAX_NB_SERVERS 1024 + +// Address hash size in bits (between 0 and MAX_HASH_SIZE) +#define DEFAULT_HASH_SIZE	6 +#define MAX_HASH_SIZE		8 + +// Number of characters in a challenge, including the '\0' +#define CHALLENGE_MIN_LENGTH 9 +#define CHALLENGE_MAX_LENGTH 12 + +// Minimum number of seconds between gamestat messages per server +#define MIN_GAMESTAT_DELAY 120 + +// ---------- Types ---------- // + +// Address mapping +typedef struct addrmap_s +{ +	struct addrmap_s* next; +	struct sockaddr_in from; +	struct sockaddr_in to; +	char* from_string; +	char* to_string; +} addrmap_t; + +// Server properties +typedef struct server_s +{ +	struct server_s* next; +	struct sockaddr_in address; +	unsigned int protocol; +	char challenge [CHALLENGE_MAX_LENGTH]; +	unsigned short nbclients; +	unsigned short maxclients; +	time_t timeout; +	time_t challenge_timeout; +	const struct addrmap_s* addrmap; +	qboolean active; +	time_t lastGameStat; +} server_t; + + +// ---------- Public functions (servers) ---------- // + +// Will simply return "false" if called after Sv_Init +qboolean Sv_SetHashSize (unsigned int size); +qboolean Sv_SetMaxNbServers (unsigned int nb); + +// Initialize the server list and hash table +qboolean Sv_Init (void); + +// Search for a particular server in the list; add it if necessary +// NOTE: doesn't change the current position for "Sv_GetNext" +server_t* Sv_GetByAddr (const struct sockaddr_in* address, qboolean add_it); + +// Get the first server in the list +server_t* Sv_GetFirst (void); + +// Get the next server in the list +server_t* Sv_GetNext (void); + + +// ---------- Public functions (address mappings) ---------- // + +// NOTE: this is a 2-step process because resolving address mappings directly +// during the parsing of the command line would cause several problems + +// Add an unresolved address mapping to the list +// mapping must be of the form "addr1:port1=addr2:port2", ":portX" are optional +qboolean Sv_AddAddressMapping (const char* mapping); + +// Resolve the address mapping list +qboolean Sv_ResolveAddressMappings (void); + + +#endif  // #ifndef _SERVERS_H_ diff --git a/src/master/stats.c b/src/master/stats.c new file mode 100644 index 0000000..98ebb32 --- /dev/null +++ b/src/master/stats.c @@ -0,0 +1,135 @@ +/* +stats.c + +Statistics for tremmaster + +Copyright (C) 2006 Tim Angus + +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 +*/ + +#ifndef _WIN32 + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <tdb.h> + +#include "common.h" + +#define MAX_DATA_SIZE 1024 +#define CS_FILENAME   "clientStats.tdb" + +/* +==================== +RecordClientStat +==================== +*/ +void RecordClientStat( const char *address, const char *version, const char *renderer ) +{ +  TDB_CONTEXT *tctx = NULL; +  TDB_DATA    key, data; +  char        ipText[ 22 ]; +  char        dataText[ MAX_DATA_SIZE ] = { 0 }; +  char        *p; +  int         i; + +  tctx = tdb_open( CS_FILENAME, 0, 0, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR ); + +  if( !tctx ) +  { +		MsgPrint( MSG_DEBUG, "Couldn't open %s\n", CS_FILENAME ); +    return; +  } + +  strncpy( ipText, address, 22 ); +  if( ( p = strrchr( ipText, ':' ) ) ) // Remove port +    *p = '\0'; + +  key.dptr = ipText; +  key.dsize = strlen( ipText ); + +  strncat( dataText, "\"", MAX_DATA_SIZE ); +  strncat( dataText, version, MAX_DATA_SIZE ); + +  // Remove last three tokens (the date) +  for( i = 0; i < 3; i++ ) +  { +    if( ( p = strrchr( dataText, ' ' ) ) ) +      *p = '\0'; +  } +  strncat( dataText, "\"", MAX_DATA_SIZE ); + +  strncat( dataText, " \"", MAX_DATA_SIZE ); +  strncat( dataText, renderer, MAX_DATA_SIZE ); +  strncat( dataText, "\"", MAX_DATA_SIZE ); + +  data.dptr = dataText; +  data.dsize = strlen( dataText ); + +  if( tdb_store( tctx, key, data, 0 ) < 0 ) +		MsgPrint( MSG_DEBUG, "tdb_store failed\n" ); + +  tdb_close( tctx ); +	MsgPrint( MSG_DEBUG, "Recorded client stat for %s\n", address ); +} + +#define GS_FILENAME   "gameStats.tdb" + +/* +==================== +RecordGameStat +==================== +*/ +void RecordGameStat( const char *address, const char *dataText ) +{ +  TDB_CONTEXT *tctx = NULL; +  TDB_DATA    key, data; +  char        keyText[ MAX_DATA_SIZE ] = { 0 }; +  char        *p; +  time_t      tm = time( NULL ); + +  tctx = tdb_open( GS_FILENAME, 0, 0, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR ); + +  if( !tctx ) +  { +		MsgPrint( MSG_DEBUG, "Couldn't open %s\n", GS_FILENAME ); +    return; +  } + +  strncpy( keyText, address, 22 ); +  if( ( p = strrchr( keyText, ':' ) ) ) // Remove port +    *p = '\0'; + +  strncat( keyText, " ", MAX_DATA_SIZE ); +  strncat( keyText, asctime( gmtime( &tm ) ), MAX_DATA_SIZE ); + +  key.dptr = keyText; +  key.dsize = strlen( keyText ); + +  data.dptr = (char *)dataText; +  data.dsize = strlen( dataText ); + +  if( tdb_store( tctx, key, data, 0 ) < 0 ) +		MsgPrint( MSG_DEBUG, "tdb_store failed\n" ); + +  tdb_close( tctx ); +	MsgPrint( MSG_NORMAL, "Recorded game stat from %s\n", address ); +} + +#endif  | 
