aboutsummaryrefslogtreecommitdiffstats
path: root/QW/server/sv_main.c
diff options
context:
space:
mode:
Diffstat (limited to 'QW/server/sv_main.c')
-rw-r--r--QW/server/sv_main.c1658
1 files changed, 1658 insertions, 0 deletions
diff --git a/QW/server/sv_main.c b/QW/server/sv_main.c
new file mode 100644
index 0000000..275aa54
--- /dev/null
+++ b/QW/server/sv_main.c
@@ -0,0 +1,1658 @@
+/*
+Copyright (C) 1996-1997 Id Software, Inc.
+
+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 "qwsvdef.h"
+
+quakeparms_t host_parms;
+
+qboolean host_initialized; // true if into command execution (compatability)
+
+double host_frametime;
+double realtime; // without any filtering or bounding
+
+int host_hunklevel;
+
+netadr_t master_adr[MAX_MASTERS]; // address of group servers
+
+client_t *host_client; // current client
+
+cvar_t sv_mintic = {"sv_mintic","0.03"}; // bound the size of the
+cvar_t sv_maxtic = {"sv_maxtic","0.1"}; // physics time tic
+
+cvar_t developer = {"developer","0"}; // show extra messages
+
+cvar_t timeout = {"timeout","65"}; // seconds without any message
+cvar_t zombietime = {"zombietime", "2"}; // seconds to sink messages
+ // after disconnect
+
+cvar_t rcon_password = {"rcon_password", ""}; // password for remote server commands
+cvar_t password = {"password", ""}; // password for entering the game
+cvar_t spectator_password = {"spectator_password", ""}; // password for entering as a sepctator
+
+cvar_t allow_download = {"allow_download", "1"};
+cvar_t allow_download_skins = {"allow_download_skins", "1"};
+cvar_t allow_download_models = {"allow_download_models", "1"};
+cvar_t allow_download_sounds = {"allow_download_sounds", "1"};
+cvar_t allow_download_maps = {"allow_download_maps", "1"};
+
+cvar_t sv_highchars = {"sv_highchars", "1"};
+
+cvar_t sv_phs = {"sv_phs", "1"};
+
+cvar_t pausable = {"pausable", "1"};
+
+
+//
+// game rules mirrored in svs.info
+//
+cvar_t fraglimit = {"fraglimit","0",false,true};
+cvar_t timelimit = {"timelimit","0",false,true};
+cvar_t teamplay = {"teamplay","0",false,true};
+cvar_t samelevel = {"samelevel","0", false, true};
+cvar_t maxclients = {"maxclients","8", false, true};
+cvar_t maxspectators = {"maxspectators","8", false, true};
+cvar_t deathmatch = {"deathmatch","1", false, true}; // 0, 1, or 2
+cvar_t spawn = {"spawn","0", false, true};
+cvar_t watervis = {"watervis", "0", false, true};
+
+cvar_t hostname = {"hostname","unnamed", false, true};
+
+FILE *sv_logfile;
+FILE *sv_fraglogfile;
+
+void SV_AcceptClient (netadr_t adr, int userid, char *userinfo);
+void Master_Shutdown (void);
+
+//============================================================================
+
+qboolean ServerPaused(void)
+{
+ return sv.paused;
+}
+
+/*
+================
+SV_Shutdown
+
+Quake calls this before calling Sys_Quit or Sys_Error
+================
+*/
+void SV_Shutdown (void)
+{
+ Master_Shutdown ();
+ if (sv_logfile)
+ {
+ fclose (sv_logfile);
+ sv_logfile = NULL;
+ }
+ if (sv_fraglogfile)
+ {
+ fclose (sv_fraglogfile);
+ sv_logfile = NULL;
+ }
+ NET_Shutdown ();
+}
+
+/*
+================
+SV_Error
+
+Sends a datagram to all the clients informing them of the server crash,
+then exits
+================
+*/
+void SV_Error (char *error, ...)
+{
+ va_list argptr;
+ static char string[1024];
+ static qboolean inerror = false;
+
+ if (inerror)
+ Sys_Error ("SV_Error: recursively entered (%s)", string);
+
+ inerror = true;
+
+ va_start (argptr,error);
+ vsprintf (string,error,argptr);
+ va_end (argptr);
+
+ Con_Printf ("SV_Error: %s\n",string);
+
+ SV_FinalMessage (va("server crashed: %s\n", string));
+
+ SV_Shutdown ();
+
+ Sys_Error ("SV_Error: %s\n",string);
+}
+
+/*
+==================
+SV_FinalMessage
+
+Used by SV_Error and SV_Quit_f to send a final message to all connected
+clients before the server goes down. The messages are sent immediately,
+not just stuck on the outgoing message list, because the server is going
+to totally exit after returning from this function.
+==================
+*/
+void SV_FinalMessage (char *message)
+{
+ int i;
+ client_t *cl;
+
+ SZ_Clear (&net_message);
+ MSG_WriteByte (&net_message, svc_print);
+ MSG_WriteByte (&net_message, PRINT_HIGH);
+ MSG_WriteString (&net_message, message);
+ MSG_WriteByte (&net_message, svc_disconnect);
+
+ for (i=0, cl = svs.clients ; i<MAX_CLIENTS ; i++, cl++)
+ if (cl->state >= cs_spawned)
+ Netchan_Transmit (&cl->netchan, net_message.cursize
+ , net_message.data);
+}
+
+
+
+/*
+=====================
+SV_DropClient
+
+Called when the player is totally leaving the server, either willingly
+or unwillingly. This is NOT called if the entire server is quiting
+or crashing.
+=====================
+*/
+void SV_DropClient (client_t *drop)
+{
+ // add the disconnect
+ MSG_WriteByte (&drop->netchan.message, svc_disconnect);
+
+ if (drop->state == cs_spawned)
+ if (!drop->spectator)
+ {
+ // call the prog function for removing a client
+ // this will set the body to a dead frame, among other things
+ pr_global_struct->self = EDICT_TO_PROG(drop->edict);
+ PR_ExecuteProgram (pr_global_struct->ClientDisconnect);
+ }
+ else if (SpectatorDisconnect)
+ {
+ // call the prog function for removing a client
+ // this will set the body to a dead frame, among other things
+ pr_global_struct->self = EDICT_TO_PROG(drop->edict);
+ PR_ExecuteProgram (SpectatorDisconnect);
+ }
+
+ if (drop->spectator)
+ Con_Printf ("Spectator %s removed\n",drop->name);
+ else
+ Con_Printf ("Client %s removed\n",drop->name);
+
+ if (drop->download)
+ {
+ fclose (drop->download);
+ drop->download = NULL;
+ }
+ if (drop->upload)
+ {
+ fclose (drop->upload);
+ drop->upload = NULL;
+ }
+ *drop->uploadfn = 0;
+
+ drop->state = cs_zombie; // become free in a few seconds
+ drop->connection_started = realtime; // for zombie timeout
+
+ drop->old_frags = 0;
+ drop->edict->v.frags = 0;
+ drop->name[0] = 0;
+ memset (drop->userinfo, 0, sizeof(drop->userinfo));
+
+// send notification to all remaining clients
+ SV_FullClientUpdate (drop, &sv.reliable_datagram);
+}
+
+
+//====================================================================
+
+/*
+===================
+SV_CalcPing
+
+===================
+*/
+int SV_CalcPing (client_t *cl)
+{
+ float ping;
+ int i;
+ int count;
+ register client_frame_t *frame;
+
+ ping = 0;
+ count = 0;
+ for (frame = cl->frames, i=0 ; i<UPDATE_BACKUP ; i++, frame++)
+ {
+ if (frame->ping_time > 0)
+ {
+ ping += frame->ping_time;
+ count++;
+ }
+ }
+ if (!count)
+ return 9999;
+ ping /= count;
+
+ return ping*1000;
+}
+
+/*
+===================
+SV_FullClientUpdate
+
+Writes all update values to a sizebuf
+===================
+*/
+void SV_FullClientUpdate (client_t *client, sizebuf_t *buf)
+{
+ int i;
+ char info[MAX_INFO_STRING];
+
+ i = client - svs.clients;
+
+//Sys_Printf("SV_FullClientUpdate: Updated frags for client %d\n", i);
+
+ MSG_WriteByte (buf, svc_updatefrags);
+ MSG_WriteByte (buf, i);
+ MSG_WriteShort (buf, client->old_frags);
+
+ MSG_WriteByte (buf, svc_updateping);
+ MSG_WriteByte (buf, i);
+ MSG_WriteShort (buf, SV_CalcPing (client));
+
+ MSG_WriteByte (buf, svc_updatepl);
+ MSG_WriteByte (buf, i);
+ MSG_WriteByte (buf, client->lossage);
+
+ MSG_WriteByte (buf, svc_updateentertime);
+ MSG_WriteByte (buf, i);
+ MSG_WriteFloat (buf, realtime - client->connection_started);
+
+ strcpy (info, client->userinfo);
+ Info_RemovePrefixedKeys (info, '_'); // server passwords, etc
+
+ MSG_WriteByte (buf, svc_updateuserinfo);
+ MSG_WriteByte (buf, i);
+ MSG_WriteLong (buf, client->userid);
+ MSG_WriteString (buf, info);
+}
+
+/*
+===================
+SV_FullClientUpdateToClient
+
+Writes all update values to a client's reliable stream
+===================
+*/
+void SV_FullClientUpdateToClient (client_t *client, client_t *cl)
+{
+ ClientReliableCheckBlock(cl, 24 + strlen(client->userinfo));
+ if (cl->num_backbuf) {
+ SV_FullClientUpdate (client, &cl->backbuf);
+ ClientReliable_FinishWrite(cl);
+ } else
+ SV_FullClientUpdate (client, &cl->netchan.message);
+}
+
+
+/*
+==============================================================================
+
+CONNECTIONLESS COMMANDS
+
+==============================================================================
+*/
+
+/*
+================
+SVC_Status
+
+Responds with all the info that qplug or qspy can see
+This message can be up to around 5k with worst case string lengths.
+================
+*/
+void SVC_Status (void)
+{
+ int i;
+ client_t *cl;
+ int ping;
+ int top, bottom;
+
+ Cmd_TokenizeString ("status");
+ SV_BeginRedirect (RD_PACKET);
+ Con_Printf ("%s\n", svs.info);
+ for (i=0 ; i<MAX_CLIENTS ; i++)
+ {
+ cl = &svs.clients[i];
+ if ((cl->state == cs_connected || cl->state == cs_spawned ) && !cl->spectator)
+ {
+ top = atoi(Info_ValueForKey (cl->userinfo, "topcolor"));
+ bottom = atoi(Info_ValueForKey (cl->userinfo, "bottomcolor"));
+ top = (top < 0) ? 0 : ((top > 13) ? 13 : top);
+ bottom = (bottom < 0) ? 0 : ((bottom > 13) ? 13 : bottom);
+ ping = SV_CalcPing (cl);
+ Con_Printf ("%i %i %i %i \"%s\" \"%s\" %i %i\n", cl->userid,
+ cl->old_frags, (int)(realtime - cl->connection_started)/60,
+ ping, cl->name, Info_ValueForKey (cl->userinfo, "skin"), top, bottom);
+ }
+ }
+ SV_EndRedirect ();
+}
+
+/*
+===================
+SV_CheckLog
+
+===================
+*/
+#define LOG_HIGHWATER 4096
+#define LOG_FLUSH 10*60
+void SV_CheckLog (void)
+{
+ sizebuf_t *sz;
+
+ sz = &svs.log[svs.logsequence&1];
+
+ // bump sequence if allmost full, or ten minutes have passed and
+ // there is something still sitting there
+ if (sz->cursize > LOG_HIGHWATER
+ || (realtime - svs.logtime > LOG_FLUSH && sz->cursize) )
+ {
+ // swap buffers and bump sequence
+ svs.logtime = realtime;
+ svs.logsequence++;
+ sz = &svs.log[svs.logsequence&1];
+ sz->cursize = 0;
+ Con_Printf ("beginning fraglog sequence %i\n", svs.logsequence);
+ }
+
+}
+
+/*
+================
+SVC_Log
+
+Responds with all the logged frags for ranking programs.
+If a sequence number is passed as a parameter and it is
+the same as the current sequence, an A2A_NACK will be returned
+instead of the data.
+================
+*/
+void SVC_Log (void)
+{
+ int seq;
+ char data[MAX_DATAGRAM+64];
+
+ if (Cmd_Argc() == 2)
+ seq = atoi(Cmd_Argv(1));
+ else
+ seq = -1;
+
+ if (seq == svs.logsequence-1 || !sv_fraglogfile)
+ { // they allready have this data, or we aren't logging frags
+ data[0] = A2A_NACK;
+ NET_SendPacket (1, data, net_from);
+ return;
+ }
+
+ Con_DPrintf ("sending log %i to %s\n", svs.logsequence-1, NET_AdrToString(net_from));
+
+ sprintf (data, "stdlog %i\n", svs.logsequence-1);
+ strcat (data, (char *)svs.log_buf[((svs.logsequence-1)&1)]);
+
+ NET_SendPacket (strlen(data)+1, data, net_from);
+}
+
+/*
+================
+SVC_Ping
+
+Just responds with an acknowledgement
+================
+*/
+void SVC_Ping (void)
+{
+ char data;
+
+ data = A2A_ACK;
+
+ NET_SendPacket (1, &data, net_from);
+}
+
+/*
+=================
+SVC_GetChallenge
+
+Returns a challenge number that can be used
+in a subsequent client_connect command.
+We do this to prevent denial of service attacks that
+flood the server with invalid connection IPs. With a
+challenge, they must give a valid IP address.
+=================
+*/
+void SVC_GetChallenge (void)
+{
+ int i;
+ int oldest;
+ int oldestTime;
+
+ oldest = 0;
+ oldestTime = 0x7fffffff;
+
+ // see if we already have a challenge for this ip
+ for (i = 0 ; i < MAX_CHALLENGES ; i++)
+ {
+ if (NET_CompareBaseAdr (net_from, svs.challenges[i].adr))
+ break;
+ if (svs.challenges[i].time < oldestTime)
+ {
+ oldestTime = svs.challenges[i].time;
+ oldest = i;
+ }
+ }
+
+ if (i == MAX_CHALLENGES)
+ {
+ // overwrite the oldest
+ svs.challenges[oldest].challenge = (rand() << 16) ^ rand();
+ svs.challenges[oldest].adr = net_from;
+ svs.challenges[oldest].time = realtime;
+ i = oldest;
+ }
+
+ // send it back
+ Netchan_OutOfBandPrint (net_from, "%c%i", S2C_CHALLENGE,
+ svs.challenges[i].challenge);
+}
+
+/*
+==================
+SVC_DirectConnect
+
+A connection request that did not come from the master
+==================
+*/
+void SVC_DirectConnect (void)
+{
+ char userinfo[1024];
+ static int userid;
+ netadr_t adr;
+ int i;
+ client_t *cl, *newcl;
+ client_t temp;
+ edict_t *ent;
+ int edictnum;
+ char *s;
+ int clients, spectators;
+ qboolean spectator;
+ int qport;
+ int version;
+ int challenge;
+
+ version = atoi(Cmd_Argv(1));
+ if (version != PROTOCOL_VERSION)
+ {
+ Netchan_OutOfBandPrint (net_from, "%c\nServer is version %4.2f.\n", A2C_PRINT, VERSION);
+ Con_Printf ("* rejected connect from version %i\n", version);
+ return;
+ }
+
+ qport = atoi(Cmd_Argv(2));
+
+ challenge = atoi(Cmd_Argv(3));
+
+ // note an extra byte is needed to replace spectator key
+ strncpy (userinfo, Cmd_Argv(4), sizeof(userinfo)-2);
+ userinfo[sizeof(userinfo) - 2] = 0;
+
+ // see if the challenge is valid
+ for (i=0 ; i<MAX_CHALLENGES ; i++)
+ {
+ if (NET_CompareBaseAdr (net_from, svs.challenges[i].adr))
+ {
+ if (challenge == svs.challenges[i].challenge)
+ break; // good
+ Netchan_OutOfBandPrint (net_from, "%c\nBad challenge.\n", A2C_PRINT);
+ return;
+ }
+ }
+ if (i == MAX_CHALLENGES)
+ {
+ Netchan_OutOfBandPrint (net_from, "%c\nNo challenge for address.\n", A2C_PRINT);
+ return;
+ }
+
+ // check for password or spectator_password
+ s = Info_ValueForKey (userinfo, "spectator");
+ if (s[0] && strcmp(s, "0"))
+ {
+ if (spectator_password.string[0] &&
+ stricmp(spectator_password.string, "none") &&
+ strcmp(spectator_password.string, s) )
+ { // failed
+ Con_Printf ("%s:spectator password failed\n", NET_AdrToString (net_from));
+ Netchan_OutOfBandPrint (net_from, "%c\nrequires a spectator password\n\n", A2C_PRINT);
+ return;
+ }
+ Info_RemoveKey (userinfo, "spectator"); // remove passwd
+ Info_SetValueForStarKey (userinfo, "*spectator", "1", MAX_INFO_STRING);
+ spectator = true;
+ }
+ else
+ {
+ s = Info_ValueForKey (userinfo, "password");
+ if (password.string[0] &&
+ stricmp(password.string, "none") &&
+ strcmp(password.string, s) )
+ {
+ Con_Printf ("%s:password failed\n", NET_AdrToString (net_from));
+ Netchan_OutOfBandPrint (net_from, "%c\nserver requires a password\n\n", A2C_PRINT);
+ return;
+ }
+ spectator = false;
+ Info_RemoveKey (userinfo, "password"); // remove passwd
+ }
+
+ adr = net_from;
+ userid++; // so every client gets a unique id
+
+ newcl = &temp;
+ memset (newcl, 0, sizeof(client_t));
+
+ newcl->userid = userid;
+
+ // works properly
+ if (!sv_highchars.value) {
+ byte *p, *q;
+
+ for (p = (byte *)newcl->userinfo, q = (byte *)userinfo;
+ *q && p < (byte *)newcl->userinfo + sizeof(newcl->userinfo)-1; q++)
+ if (*q > 31 && *q <= 127)
+ *p++ = *q;
+ } else
+ strncpy (newcl->userinfo, userinfo, sizeof(newcl->userinfo)-1);
+
+ // if there is allready a slot for this ip, drop it
+ for (i=0,cl=svs.clients ; i<MAX_CLIENTS ; i++,cl++)
+ {
+ if (cl->state == cs_free)
+ continue;
+ if (NET_CompareBaseAdr (adr, cl->netchan.remote_address)
+ && ( cl->netchan.qport == qport
+ || adr.port == cl->netchan.remote_address.port ))
+ {
+ if (cl->state == cs_connected) {
+ Con_Printf("%s:dup connect\n", NET_AdrToString (adr));
+ userid--;
+ return;
+ }
+
+ Con_Printf ("%s:reconnect\n", NET_AdrToString (adr));
+ SV_DropClient (cl);
+ break;
+ }
+ }
+
+ // count up the clients and spectators
+ clients = 0;
+ spectators = 0;
+ for (i=0,cl=svs.clients ; i<MAX_CLIENTS ; i++,cl++)
+ {
+ if (cl->state == cs_free)
+ continue;
+ if (cl->spectator)
+ spectators++;
+ else
+ clients++;
+ }
+
+ // if at server limits, refuse connection
+ if ( maxclients.value > MAX_CLIENTS )
+ Cvar_SetValue ("maxclients", MAX_CLIENTS);
+ if (maxspectators.value > MAX_CLIENTS)
+ Cvar_SetValue ("maxspectators", MAX_CLIENTS);
+ if (maxspectators.value + maxclients.value > MAX_CLIENTS)
+ Cvar_SetValue ("maxspectators", MAX_CLIENTS - maxspectators.value + maxclients.value);
+ if ( (spectator && spectators >= (int)maxspectators.value)
+ || (!spectator && clients >= (int)maxclients.value) )
+ {
+ Con_Printf ("%s:full connect\n", NET_AdrToString (adr));
+ Netchan_OutOfBandPrint (adr, "%c\nserver is full\n\n", A2C_PRINT);
+ return;
+ }
+
+ // find a client slot
+ newcl = NULL;
+ for (i=0,cl=svs.clients ; i<MAX_CLIENTS ; i++,cl++)
+ {
+ if (cl->state == cs_free)
+ {
+ newcl = cl;
+ break;
+ }
+ }
+ if (!newcl)
+ {
+ Con_Printf ("WARNING: miscounted available clients\n");
+ return;
+ }
+
+
+ // build a new connection
+ // accept the new client
+ // this is the only place a client_t is ever initialized
+ *newcl = temp;
+
+ Netchan_OutOfBandPrint (adr, "%c", S2C_CONNECTION );
+
+ edictnum = (newcl-svs.clients)+1;
+
+ Netchan_Setup (&newcl->netchan , adr, qport);
+
+ newcl->state = cs_connected;
+
+ newcl->datagram.allowoverflow = true;
+ newcl->datagram.data = newcl->datagram_buf;
+ newcl->datagram.maxsize = sizeof(newcl->datagram_buf);
+
+ // spectator mode can ONLY be set at join time
+ newcl->spectator = spectator;
+
+ ent = EDICT_NUM(edictnum);
+ newcl->edict = ent;
+
+ // parse some info from the info strings
+ SV_ExtractFromUserinfo (newcl);
+
+ // JACK: Init the floodprot stuff.
+ for (i=0; i<10; i++)
+ newcl->whensaid[i] = 0.0;
+ newcl->whensaidhead = 0;
+ newcl->lockedtill = 0;
+
+ // call the progs to get default spawn parms for the new client
+ PR_ExecuteProgram (pr_global_struct->SetNewParms);
+ for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
+ newcl->spawn_parms[i] = (&pr_global_struct->parm1)[i];
+
+ if (newcl->spectator)
+ Con_Printf ("Spectator %s connected\n", newcl->name);
+ else
+ Con_DPrintf ("Client %s connected\n", newcl->name);
+ newcl->sendinfo = true;
+}
+
+int Rcon_Validate (void)
+{
+ if (!strlen (rcon_password.string))
+ return 0;
+
+ if (strcmp (Cmd_Argv(1), rcon_password.string) )
+ return 0;
+
+ return 1;
+}
+
+/*
+===============
+SVC_RemoteCommand
+
+A client issued an rcon command.
+Shift down the remaining args
+Redirect all printfs
+===============
+*/
+void SVC_RemoteCommand (void)
+{
+ int i;
+ char remaining[1024];
+
+
+ if (!Rcon_Validate ()) {
+ Con_Printf ("Bad rcon from %s:\n%s\n"
+ , NET_AdrToString (net_from), net_message.data+4);
+
+ SV_BeginRedirect (RD_PACKET);
+
+ Con_Printf ("Bad rcon_password.\n");
+
+ } else {
+
+ Con_Printf ("Rcon from %s:\n%s\n"
+ , NET_AdrToString (net_from), net_message.data+4);
+
+ SV_BeginRedirect (RD_PACKET);
+
+ remaining[0] = 0;
+
+ for (i=2 ; i<Cmd_Argc() ; i++)
+ {
+ strcat (remaining, Cmd_Argv(i) );
+ strcat (remaining, " ");
+ }
+
+ Cmd_ExecuteString (remaining);
+
+ }
+
+ SV_EndRedirect ();
+}
+
+
+/*
+=================
+SV_ConnectionlessPacket
+
+A connectionless packet has four leading 0xff
+characters to distinguish it from a game channel.
+Clients that are in the game can still send
+connectionless packets.
+=================
+*/
+void SV_ConnectionlessPacket (void)
+{
+ char *s;
+ char *c;
+
+ MSG_BeginReading ();
+ MSG_ReadLong (); // skip the -1 marker
+
+ s = MSG_ReadStringLine ();
+
+ Cmd_TokenizeString (s);
+
+ c = Cmd_Argv(0);
+
+ if (!strcmp(c, "ping") || ( c[0] == A2A_PING && (c[1] == 0 || c[1] == '\n')) )
+ {
+ SVC_Ping ();
+ return;
+ }
+ if (c[0] == A2A_ACK && (c[1] == 0 || c[1] == '\n') )
+ {
+ Con_Printf ("A2A_ACK from %s\n", NET_AdrToString (net_from));
+ return;
+ }
+ else if (!strcmp(c,"status"))
+ {
+ SVC_Status ();
+ return;
+ }
+ else if (!strcmp(c,"log"))
+ {
+ SVC_Log ();
+ return;
+ }
+ else if (!strcmp(c,"connect"))
+ {
+ SVC_DirectConnect ();
+ return;
+ }
+ else if (!strcmp(c,"getchallenge"))
+ {
+ SVC_GetChallenge ();
+ return;
+ }
+ else if (!strcmp(c, "rcon"))
+ SVC_RemoteCommand ();
+ else
+ Con_Printf ("bad connectionless packet from %s:\n%s\n"
+ , NET_AdrToString (net_from), s);
+}
+
+/*
+==============================================================================
+
+PACKET FILTERING
+
+
+You can add or remove addresses from the filter list with:
+
+addip <ip>
+removeip <ip>
+
+The ip address is specified in dot format, and any unspecified digits will match any value, so you can specify an entire class C network with "addip 192.246.40".
+
+Removeip will only remove an address specified exactly the same way. You cannot addip a subnet, then removeip a single host.
+
+listip
+Prints the current list of filters.
+
+writeip
+Dumps "addip <ip>" commands to listip.cfg so it can be execed at a later date. The filter lists are not saved and restored by default, because I beleive it would cause too much confusion.
+
+filterban <0 or 1>
+
+If 1 (the default), then ip addresses matching the current list will be prohibited from entering the game. This is the default setting.
+
+If 0, then only addresses matching the list will be allowed. This lets you easily set up a private game, or a game that only allows players from your local network.
+
+
+==============================================================================
+*/
+
+
+typedef struct
+{
+ unsigned mask;
+ unsigned compare;
+} ipfilter_t;
+
+#define MAX_IPFILTERS 1024
+
+ipfilter_t ipfilters[MAX_IPFILTERS];
+int numipfilters;
+
+cvar_t filterban = {"filterban", "1"};
+
+/*
+=================
+StringToFilter
+=================
+*/
+qboolean StringToFilter (char *s, ipfilter_t *f)
+{
+ char num[128];
+ int i, j;
+ byte b[4];
+ byte m[4];
+
+ for (i=0 ; i<4 ; i++)
+ {
+ b[i] = 0;
+ m[i] = 0;
+ }
+
+ for (i=0 ; i<4 ; i++)
+ {
+ if (*s < '0' || *s > '9')
+ {
+ Con_Printf ("Bad filter address: %s\n", s);
+ return false;
+ }
+
+ j = 0;
+ while (*s >= '0' && *s <= '9')
+ {
+ num[j++] = *s++;
+ }
+ num[j] = 0;
+ b[i] = atoi(num);
+ if (b[i] != 0)
+ m[i] = 255;
+
+ if (!*s)
+ break;
+ s++;
+ }
+
+ f->mask = *(unsigned *)m;
+ f->compare = *(unsigned *)b;
+
+ return true;
+}
+
+/*
+=================
+SV_AddIP_f
+=================
+*/
+void SV_AddIP_f (void)
+{
+ int i;
+
+ for (i=0 ; i<numipfilters ; i++)
+ if (ipfilters[i].compare == 0xffffffff)
+ break; // free spot
+ if (i == numipfilters)
+ {
+ if (numipfilters == MAX_IPFILTERS)
+ {
+ Con_Printf ("IP filter list is full\n");
+ return;
+ }
+ numipfilters++;
+ }
+
+ if (!StringToFilter (Cmd_Argv(1), &ipfilters[i]))
+ ipfilters[i].compare = 0xffffffff;
+}
+
+/*
+=================
+SV_RemoveIP_f
+=================
+*/
+void SV_RemoveIP_f (void)
+{
+ ipfilter_t f;
+ int i, j;
+
+ if (!StringToFilter (Cmd_Argv(1), &f))
+ return;
+ for (i=0 ; i<numipfilters ; i++)
+ if (ipfilters[i].mask == f.mask
+ && ipfilters[i].compare == f.compare)
+ {
+ for (j=i+1 ; j<numipfilters ; j++)
+ ipfilters[j-1] = ipfilters[j];
+ numipfilters--;
+ Con_Printf ("Removed.\n");
+ return;
+ }
+ Con_Printf ("Didn't find %s.\n", Cmd_Argv(1));
+}
+
+/*
+=================
+SV_ListIP_f
+=================
+*/
+void SV_ListIP_f (void)
+{
+ int i;
+ byte b[4];
+
+ Con_Printf ("Filter list:\n");
+ for (i=0 ; i<numipfilters ; i++)
+ {
+ *(unsigned *)b = ipfilters[i].compare;
+ Con_Printf ("%3i.%3i.%3i.%3i\n", b[0], b[1], b[2], b[3]);
+ }
+}
+
+/*
+=================
+SV_WriteIP_f
+=================
+*/
+void SV_WriteIP_f (void)
+{
+ FILE *f;
+ char name[MAX_OSPATH];
+ byte b[4];
+ int i;
+
+ sprintf (name, "%s/listip.cfg", com_gamedir);
+
+ Con_Printf ("Writing %s.\n", name);
+
+ f = fopen (name, "wb");
+ if (!f)
+ {
+ Con_Printf ("Couldn't open %s\n", name);
+ return;
+ }
+
+ for (i=0 ; i<numipfilters ; i++)
+ {
+ *(unsigned *)b = ipfilters[i].compare;
+ fprintf (f, "addip %i.%i.%i.%i\n", b[0], b[1], b[2], b[3]);
+ }
+
+ fclose (f);
+}
+
+/*
+=================
+SV_SendBan
+=================
+*/
+void SV_SendBan (void)
+{
+ char data[128];
+
+ data[0] = data[1] = data[2] = data[3] = 0xff;
+ data[4] = A2C_PRINT;
+ data[5] = 0;
+ strcat (data, "\nbanned.\n");
+
+ NET_SendPacket (strlen(data), data, net_from);
+}
+
+/*
+=================
+SV_FilterPacket
+=================
+*/
+qboolean SV_FilterPacket (void)
+{
+ int i;
+ unsigned in;
+
+ in = *(unsigned *)net_from.ip;
+
+ for (i=0 ; i<numipfilters ; i++)
+ if ( (in & ipfilters[i].mask) == ipfilters[i].compare)
+ return filterban.value;
+
+ return !filterban.value;
+}
+
+//============================================================================
+
+/*
+=================
+SV_ReadPackets
+=================
+*/
+void SV_ReadPackets (void)
+{
+ int i;
+ client_t *cl;
+ qboolean good;
+ int qport;
+
+ good = false;
+ while (NET_GetPacket ())
+ {
+ if (SV_FilterPacket ())
+ {
+ SV_SendBan (); // tell them we aren't listening...
+ continue;
+ }
+
+ // check for connectionless packet (0xffffffff) first
+ if (*(int *)net_message.data == -1)
+ {
+ SV_ConnectionlessPacket ();
+ continue;
+ }
+
+ // read the qport out of the message so we can fix up
+ // stupid address translating routers
+ MSG_BeginReading ();
+ MSG_ReadLong (); // sequence number
+ MSG_ReadLong (); // sequence number
+ qport = MSG_ReadShort () & 0xffff;
+
+ // check for packets from connected clients
+ for (i=0, cl=svs.clients ; i<MAX_CLIENTS ; i++,cl++)
+ {
+ if (cl->state == cs_free)
+ continue;
+ if (!NET_CompareBaseAdr (net_from, cl->netchan.remote_address))
+ continue;
+ if (cl->netchan.qport != qport)
+ continue;
+ if (cl->netchan.remote_address.port != net_from.port)
+ {
+ Con_DPrintf ("SV_ReadPackets: fixing up a translated port\n");
+ cl->netchan.remote_address.port = net_from.port;
+ }
+ if (Netchan_Process(&cl->netchan))
+ { // this is a valid, sequenced packet, so process it
+ svs.stats.packets++;
+ good = true;
+ cl->send_message = true; // reply at end of frame
+ if (cl->state != cs_zombie)
+ SV_ExecuteClientMessage (cl);
+ }
+ break;
+ }
+
+ if (i != MAX_CLIENTS)
+ continue;
+
+ // packet is not from a known client
+ // Con_Printf ("%s:sequenced packet without connection\n"
+ // ,NET_AdrToString(net_from));
+ }
+}
+
+/*
+==================
+SV_CheckTimeouts
+
+If a packet has not been received from a client in timeout.value
+seconds, drop the conneciton.
+
+When a client is normally dropped, the client_t goes into a zombie state
+for a few seconds to make sure any final reliable message gets resent
+if necessary
+==================
+*/
+void SV_CheckTimeouts (void)
+{
+ int i;
+ client_t *cl;
+ float droptime;
+ int nclients;
+
+ droptime = realtime - timeout.value;
+ nclients = 0;
+
+ for (i=0,cl=svs.clients ; i<MAX_CLIENTS ; i++,cl++)
+ {
+ if (cl->state == cs_connected || cl->state == cs_spawned) {
+ if (!cl->spectator)
+ nclients++;
+ if (cl->netchan.last_received < droptime) {
+ SV_BroadcastPrintf (PRINT_HIGH, "%s timed out\n", cl->name);
+ SV_DropClient (cl);
+ cl->state = cs_free; // don't bother with zombie state
+ }
+ }
+ if (cl->state == cs_zombie &&
+ realtime - cl->connection_started > zombietime.value)
+ {
+ cl->state = cs_free; // can now be reused
+ }
+ }
+ if (sv.paused && !nclients) {
+ // nobody left, unpause the server
+ SV_TogglePause("Pause released since no players are left.\n");
+ }
+}
+
+/*
+===================
+SV_GetConsoleCommands
+
+Add them exactly as if they had been typed at the console
+===================
+*/
+void SV_GetConsoleCommands (void)
+{
+ char *cmd;
+
+ while (1)
+ {
+ cmd = Sys_ConsoleInput ();
+ if (!cmd)
+ break;
+ Cbuf_AddText (cmd);
+ }
+}
+
+/*
+===================
+SV_CheckVars
+
+===================
+*/
+void SV_CheckVars (void)
+{
+ static char *pw, *spw;
+ int v;
+
+ if (password.string == pw && spectator_password.string == spw)
+ return;
+ pw = password.string;
+ spw = spectator_password.string;
+
+ v = 0;
+ if (pw && pw[0] && strcmp(pw, "none"))
+ v |= 1;
+ if (spw && spw[0] && strcmp(spw, "none"))
+ v |= 2;
+
+ Con_Printf ("Updated needpass.\n");
+ if (!v)
+ Info_SetValueForKey (svs.info, "needpass", "", MAX_SERVERINFO_STRING);
+ else
+ Info_SetValueForKey (svs.info, "needpass", va("%i",v), MAX_SERVERINFO_STRING);
+}
+
+/*
+==================
+SV_Frame
+
+==================
+*/
+void SV_Frame (float time)
+{
+ static double start, end;
+
+ start = Sys_DoubleTime ();
+ svs.stats.idle += start - end;
+
+// keep the random time dependent
+ rand ();
+
+// decide the simulation time
+ if (!sv.paused) {
+ realtime += time;
+ sv.time += time;
+ }
+
+// check timeouts
+ SV_CheckTimeouts ();
+
+// toggle the log buffer if full
+ SV_CheckLog ();
+
+// move autonomous things around if enough time has passed
+ if (!sv.paused)
+ SV_Physics ();
+
+// get packets
+ SV_ReadPackets ();
+
+// check for commands typed to the host
+ SV_GetConsoleCommands ();
+
+// process console commands
+ Cbuf_Execute ();
+
+ SV_CheckVars ();
+
+// send messages back to the clients that had packets read this frame
+ SV_SendClientMessages ();
+
+// send a heartbeat to the master if needed
+ Master_Heartbeat ();
+
+// collect timing statistics
+ end = Sys_DoubleTime ();
+ svs.stats.active += end-start;
+ if (++svs.stats.count == STATFRAMES)
+ {
+ svs.stats.latched_active = svs.stats.active;
+ svs.stats.latched_idle = svs.stats.idle;
+ svs.stats.latched_packets = svs.stats.packets;
+ svs.stats.active = 0;
+ svs.stats.idle = 0;
+ svs.stats.packets = 0;
+ svs.stats.count = 0;
+ }
+}
+
+/*
+===============
+SV_InitLocal
+===============
+*/
+void SV_InitLocal (void)
+{
+ int i;
+ extern cvar_t sv_maxvelocity;
+ extern cvar_t sv_gravity;
+ extern cvar_t sv_aim;
+ extern cvar_t sv_stopspeed;
+ extern cvar_t sv_spectatormaxspeed;
+ extern cvar_t sv_accelerate;
+ extern cvar_t sv_airaccelerate;
+ extern cvar_t sv_wateraccelerate;
+ extern cvar_t sv_friction;
+ extern cvar_t sv_waterfriction;
+
+ SV_InitOperatorCommands ();
+ SV_UserInit ();
+
+ Cvar_RegisterVariable (&rcon_password);
+ Cvar_RegisterVariable (&password);
+ Cvar_RegisterVariable (&spectator_password);
+
+ Cvar_RegisterVariable (&sv_mintic);
+ Cvar_RegisterVariable (&sv_maxtic);
+
+ Cvar_RegisterVariable (&fraglimit);
+ Cvar_RegisterVariable (&timelimit);
+ Cvar_RegisterVariable (&teamplay);
+ Cvar_RegisterVariable (&samelevel);
+ Cvar_RegisterVariable (&maxclients);
+ Cvar_RegisterVariable (&maxspectators);
+ Cvar_RegisterVariable (&hostname);
+ Cvar_RegisterVariable (&deathmatch);
+ Cvar_RegisterVariable (&spawn);
+ Cvar_RegisterVariable (&watervis);
+
+ Cvar_RegisterVariable (&developer);
+
+ Cvar_RegisterVariable (&timeout);
+ Cvar_RegisterVariable (&zombietime);
+
+ Cvar_RegisterVariable (&sv_maxvelocity);
+ Cvar_RegisterVariable (&sv_gravity);
+ Cvar_RegisterVariable (&sv_stopspeed);
+ Cvar_RegisterVariable (&sv_maxspeed);
+ Cvar_RegisterVariable (&sv_spectatormaxspeed);
+ Cvar_RegisterVariable (&sv_accelerate);
+ Cvar_RegisterVariable (&sv_airaccelerate);
+ Cvar_RegisterVariable (&sv_wateraccelerate);
+ Cvar_RegisterVariable (&sv_friction);
+ Cvar_RegisterVariable (&sv_waterfriction);
+
+ Cvar_RegisterVariable (&sv_aim);
+
+ Cvar_RegisterVariable (&filterban);
+
+ Cvar_RegisterVariable (&allow_download);
+ Cvar_RegisterVariable (&allow_download_skins);
+ Cvar_RegisterVariable (&allow_download_models);
+ Cvar_RegisterVariable (&allow_download_sounds);
+ Cvar_RegisterVariable (&allow_download_maps);
+
+ Cvar_RegisterVariable (&sv_highchars);
+
+ Cvar_RegisterVariable (&sv_phs);
+
+ Cvar_RegisterVariable (&pausable);
+
+ Cmd_AddCommand ("addip", SV_AddIP_f);
+ Cmd_AddCommand ("removeip", SV_RemoveIP_f);
+ Cmd_AddCommand ("listip", SV_ListIP_f);
+ Cmd_AddCommand ("writeip", SV_WriteIP_f);
+
+ for (i=0 ; i<MAX_MODELS ; i++)
+ sprintf (localmodels[i], "*%i", i);
+
+ Info_SetValueForStarKey (svs.info, "*version", va("%4.2f", VERSION), MAX_SERVERINFO_STRING);
+
+ // init fraglog stuff
+ svs.logsequence = 1;
+ svs.logtime = realtime;
+ svs.log[0].data = svs.log_buf[0];
+ svs.log[0].maxsize = sizeof(svs.log_buf[0]);
+ svs.log[0].cursize = 0;
+ svs.log[0].allowoverflow = true;
+ svs.log[1].data = svs.log_buf[1];
+ svs.log[1].maxsize = sizeof(svs.log_buf[1]);
+ svs.log[1].cursize = 0;
+ svs.log[1].allowoverflow = true;
+}
+
+
+//============================================================================
+
+/*
+================
+Master_Heartbeat
+
+Send a message to the master every few minutes to
+let it know we are alive, and log information
+================
+*/
+#define HEARTBEAT_SECONDS 300
+void Master_Heartbeat (void)
+{
+ char string[2048];
+ int active;
+ int i;
+
+ if (realtime - svs.last_heartbeat < HEARTBEAT_SECONDS)
+ return; // not time to send yet
+
+ svs.last_heartbeat = realtime;
+
+ //
+ // count active users
+ //
+ active = 0;
+ for (i=0 ; i<MAX_CLIENTS ; i++)
+ if (svs.clients[i].state == cs_connected ||
+ svs.clients[i].state == cs_spawned )
+ active++;
+
+ svs.heartbeat_sequence++;
+ sprintf (string, "%c\n%i\n%i\n", S2M_HEARTBEAT,
+ svs.heartbeat_sequence, active);
+
+
+ // send to group master
+ for (i=0 ; i<MAX_MASTERS ; i++)
+ if (master_adr[i].port)
+ {
+ Con_Printf ("Sending heartbeat to %s\n", NET_AdrToString (master_adr[i]));
+ NET_SendPacket (strlen(string), string, master_adr[i]);
+ }
+}
+
+/*
+=================
+Master_Shutdown
+
+Informs all masters that this server is going down
+=================
+*/
+void Master_Shutdown (void)
+{
+ char string[2048];
+ int i;
+
+ sprintf (string, "%c\n", S2M_SHUTDOWN);
+
+ // send to group master
+ for (i=0 ; i<MAX_MASTERS ; i++)
+ if (master_adr[i].port)
+ {
+ Con_Printf ("Sending heartbeat to %s\n", NET_AdrToString (master_adr[i]));
+ NET_SendPacket (strlen(string), string, master_adr[i]);
+ }
+}
+
+/*
+=================
+SV_ExtractFromUserinfo
+
+Pull specific info from a newly changed userinfo string
+into a more C freindly form.
+=================
+*/
+void SV_ExtractFromUserinfo (client_t *cl)
+{
+ char *val, *p, *q;
+ int i;
+ client_t *client;
+ int dupc = 1;
+ char newname[80];
+
+
+ // name for C code
+ val = Info_ValueForKey (cl->userinfo, "name");
+
+ // trim user name
+ strncpy(newname, val, sizeof(newname) - 1);
+ newname[sizeof(newname) - 1] = 0;
+
+ for (p = newname; (*p == ' ' || *p == '\r' || *p == '\n') && *p; p++)
+ ;
+
+ if (p != newname && !*p) {
+ //white space only
+ strcpy(newname, "unnamed");
+ p = newname;
+ }
+
+ if (p != newname && *p) {
+ for (q = newname; *p; *q++ = *p++)
+ ;
+ *q = 0;
+ }
+ for (p = newname + strlen(newname) - 1; p != newname && (*p == ' ' || *p == '\r' || *p == '\n') ; p--)
+ ;
+ p[1] = 0;
+
+ if (strcmp(val, newname)) {
+ Info_SetValueForKey (cl->userinfo, "name", newname, MAX_INFO_STRING);
+ val = Info_ValueForKey (cl->userinfo, "name");
+ }
+
+ if (!val[0] || !stricmp(val, "console")) {
+ Info_SetValueForKey (cl->userinfo, "name", "unnamed", MAX_INFO_STRING);
+ val = Info_ValueForKey (cl->userinfo, "name");
+ }
+
+ // check to see if another user by the same name exists
+ while (1) {
+ for (i=0, client = svs.clients ; i<MAX_CLIENTS ; i++, client++) {
+ if (client->state != cs_spawned || client == cl)
+ continue;
+ if (!stricmp(client->name, val))
+ break;
+ }
+ if (i != MAX_CLIENTS) { // dup name
+ if (strlen(val) > sizeof(cl->name) - 1)
+ val[sizeof(cl->name) - 4] = 0;
+ p = val;
+
+ if (val[0] == '(')
+ if (val[2] == ')')
+ p = val + 3;
+ else if (val[3] == ')')
+ p = val + 4;
+
+ sprintf(newname, "(%d)%-.40s", dupc++, p);
+ Info_SetValueForKey (cl->userinfo, "name", newname, MAX_INFO_STRING);
+ val = Info_ValueForKey (cl->userinfo, "name");
+ } else
+ break;
+ }
+
+ if (strncmp(val, cl->name, strlen(cl->name))) {
+ if (!sv.paused) {
+ if (!cl->lastnametime || realtime - cl->lastnametime > 5) {
+ cl->lastnamecount = 0;
+ cl->lastnametime = realtime;
+ } else if (cl->lastnamecount++ > 4) {
+ SV_BroadcastPrintf (PRINT_HIGH, "%s was kicked for name spam\n", cl->name);
+ SV_ClientPrintf (cl, PRINT_HIGH, "You were kicked from the game for name spamming\n");
+ SV_DropClient (cl);
+ return;
+ }
+ }
+
+ if (cl->state >= cs_spawned && !cl->spectator)
+ SV_BroadcastPrintf (PRINT_HIGH, "%s changed name to %s\n", cl->name, val);
+ }
+
+
+ strncpy (cl->name, val, sizeof(cl->name)-1);
+
+ // rate command
+ val = Info_ValueForKey (cl->userinfo, "rate");
+ if (strlen(val))
+ {
+ i = atoi(val);
+ if (i < 500)
+ i = 500;
+ if (i > 10000)
+ i = 10000;
+ cl->netchan.rate = 1.0/i;
+ }
+
+ // msg command
+ val = Info_ValueForKey (cl->userinfo, "msg");
+ if (strlen(val))
+ {
+ cl->messagelevel = atoi(val);
+ }
+
+}
+
+
+//============================================================================
+
+/*
+====================
+SV_InitNet
+====================
+*/
+void SV_InitNet (void)
+{
+ int port;
+ int p;
+
+ port = PORT_SERVER;
+ p = COM_CheckParm ("-port");
+ if (p && p < com_argc)
+ {
+ port = atoi(com_argv[p+1]);
+ Con_Printf ("Port: %i\n", port);
+ }
+ NET_Init (port);
+
+ Netchan_Init ();
+
+ // heartbeats will allways be sent to the id master
+ svs.last_heartbeat = -99999; // send immediately
+// NET_StringToAdr ("192.246.40.70:27000", &idmaster_adr);
+}
+
+
+/*
+====================
+SV_Init
+====================
+*/
+void SV_Init (quakeparms_t *parms)
+{
+ COM_InitArgv (parms->argc, parms->argv);
+ COM_AddParm ("-game");
+ COM_AddParm ("qw");
+
+ if (COM_CheckParm ("-minmemory"))
+ parms->memsize = MINIMUM_MEMORY;
+
+ host_parms = *parms;
+
+ if (parms->memsize < MINIMUM_MEMORY)
+ SV_Error ("Only %4.1f megs of memory reported, can't execute game", parms->memsize / (float)0x100000);
+
+ Memory_Init (parms->membase, parms->memsize);
+ Cbuf_Init ();
+ Cmd_Init ();
+
+ COM_Init ();
+
+ PR_Init ();
+ Mod_Init ();
+
+ SV_InitNet ();
+
+ SV_InitLocal ();
+ Sys_Init ();
+ Pmove_Init ();
+
+ Hunk_AllocName (0, "-HOST_HUNKLEVEL-");
+ host_hunklevel = Hunk_LowMark ();
+
+ Cbuf_InsertText ("exec server.cfg\n");
+
+ host_initialized = true;
+
+ Con_Printf ("Exe: "__TIME__" "__DATE__"\n");
+ Con_Printf ("%4.1f megabyte heap\n",parms->memsize/ (1024*1024.0));
+
+ Con_Printf ("\nServer Version %4.2f (Build %04d)\n\n", VERSION, build_number());
+
+ Con_Printf ("======== QuakeWorld Initialized ========\n");
+
+// process command line arguments
+ Cmd_StuffCmds_f ();
+ Cbuf_Execute ();
+
+// if a map wasn't specified on the command line, spawn start.map
+ if (sv.state == ss_dead)
+ Cmd_ExecuteString ("map start");
+ if (sv.state == ss_dead)
+ SV_Error ("Couldn't spawn a server");
+}