summaryrefslogtreecommitdiff
path: root/src/master
diff options
context:
space:
mode:
Diffstat (limited to 'src/master')
-rw-r--r--src/master/Makefile64
-rw-r--r--src/master/common.h91
-rw-r--r--src/master/master.c877
-rw-r--r--src/master/messages.c574
-rw-r--r--src/master/messages.h35
-rw-r--r--src/master/servers.c666
-rw-r--r--src/master/servers.h106
-rw-r--r--src/master/stats.c136
8 files changed, 2549 insertions, 0 deletions
diff --git a/src/master/Makefile b/src/master/Makefile
new file mode 100644
index 0000000..a3a3f87
--- /dev/null
+++ b/src/master/Makefile
@@ -0,0 +1,64 @@
+BD_DEBUG=debug-$(PLATFORM)-$(ARCH)
+BD_RELEASE=release-$(PLATFORM)-$(ARCH)
+
+ifndef PLATFORM
+ PLATFORM=$(shell uname|sed -e s/_.*//|tr '[:upper:]' '[:lower:]')
+endif
+ifndef ARCH
+ ARCH=$(shell uname -m | sed -e s/i.86/x86/)
+endif
+
+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
+
+# for f in *.c ; do cpp -MM ${f} -MT\$\(BD\)/${f%.c}.o ; done
+$(BD)/master.o: master.c common.h messages.h servers.h
+$(BD)/messages.o: messages.c common.h messages.h servers.h
+$(BD)/servers.o: servers.c common.h servers.h
+$(BD)/stats.o: stats.c common.h
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..fd6e66f
--- /dev/null
+++ b/src/master/master.c
@@ -0,0 +1,877 @@
+/*
+ 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 10 // 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;
+ }
+
+ // 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));
+
+ // Ignore abusers
+ if( ignoreAddress( inet_ntoa( address.sin_addr ) ) )
+ {
+ server_t* abuser = Sv_GetByAddr( &address, qfalse );
+ if( abuser != NULL )
+ {
+ abuser->timeout = crt_time - 1;
+ Sv_GetByAddr( &address, qfalse );
+ MsgPrint( MSG_WARNING, "WARNING: removing abuser %s\n", peer_address );
+ }
+ continue;
+ }
+
+ // 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..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;
+ }
+}
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..b4ea5f8
--- /dev/null
+++ b/src/master/stats.c
@@ -0,0 +1,136 @@
+/*
+stats.c
+
+Statistics for tremmaster
+
+Copyright (C) 2009 Darklegion Development
+
+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 <signal.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