diff options
Diffstat (limited to 'src/sv_main.c')
-rw-r--r-- | src/sv_main.c | 2078 |
1 files changed, 2078 insertions, 0 deletions
diff --git a/src/sv_main.c b/src/sv_main.c new file mode 100644 index 0000000..e50414f --- /dev/null +++ b/src/sv_main.c @@ -0,0 +1,2078 @@ +/* +Copyright (C) 1997-2001 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 "sv_local.h" + +pmoveParams_t sv_pmp; + +LIST_DECL( sv_masterlist ); // address of group servers +LIST_DECL( sv_banlist ); +LIST_DECL( sv_blacklist ); +LIST_DECL( sv_cmdlist_connect ); +LIST_DECL( sv_cmdlist_begin ); +LIST_DECL( sv_filterlist ); + +client_t *sv_client; // current client + +cvar_t *sv_enforcetime; +cvar_t *sv_allow_nodelta; +#if USE_FPS +cvar_t *sv_fps; +#endif + +cvar_t *sv_timeout; // seconds without any message +cvar_t *sv_zombietime; // seconds to sink messages after disconnect +cvar_t *sv_ghostime; + +cvar_t *sv_password; +cvar_t *sv_reserved_password; + +cvar_t *sv_force_reconnect; +cvar_t *sv_show_name_changes; + +cvar_t *sv_airaccelerate; +cvar_t *sv_qwmod; // atu QW Physics modificator +cvar_t *sv_novis; + +cvar_t *sv_maxclients; +cvar_t *sv_reserved_slots; +cvar_t *sv_showclamp; +cvar_t *sv_locked; +cvar_t *sv_downloadserver; +cvar_t *sv_redirect_address; + +cvar_t *sv_hostname; +cvar_t *sv_public; // should heartbeats be sent + +#ifdef _DEBUG +cvar_t *sv_debug; +cvar_t *sv_pad_packets; +#endif +cvar_t *sv_lan_force_rate; +cvar_t *sv_calcpings_method; +cvar_t *sv_changemapcmd; + +cvar_t *sv_strafejump_hack; +cvar_t *sv_waterjump_hack; +#ifndef _WIN32 +cvar_t *sv_oldgame_hack; +#endif +#if USE_PACKETDUP +cvar_t *sv_packetdup_hack; +#endif +cvar_t *sv_allow_map; +#if !USE_CLIENT +cvar_t *sv_recycle; +#endif +cvar_t *sv_enhanced_setplayer; + +cvar_t *sv_iplimit; +cvar_t *sv_status_limit; +cvar_t *sv_status_show; +cvar_t *sv_uptime; +cvar_t *sv_auth_limit; +cvar_t *sv_rcon_limit; + +cvar_t *g_features; + +//============================================================================ + +void SV_RemoveClient( client_t *client ) { + if( client->msg_pool ) { + SV_ShutdownClientSend( client ); + } + + if( client->netchan ) { + Netchan_Close( client->netchan ); + client->netchan = NULL; + } + + // unlink them from active client list + List_Remove( &client->entry ); + +#if USE_MVD_CLIENT + // unlink them from MVD client list + if( sv.state == ss_broadcast ) { + MVD_RemoveClient( client ); + } +#endif + + Com_DPrintf( "Going from cs_zombie to cs_free for %s\n", client->name ); + + client->state = cs_free; // can now be reused + client->name[0] = 0; +} + +void SV_CleanClient( client_t *client ) { + int i; +#if USE_AC_SERVER + string_entry_t *bad, *next; + + for( bad = client->ac_bad_files; bad; bad = next ) { + next = bad->next; + Z_Free( bad ); + } + client->ac_bad_files = NULL; +#endif + + // close any existing donwload + SV_CloseDownload( client ); + + if( client->versionString ) { + Z_Free( client->versionString ); + client->versionString = NULL; + } + + // free baselines allocated for this client + for( i = 0; i < SV_BASELINES_CHUNKS; i++ ) { + if( client->baselines[i] ) { + Z_Free( client->baselines[i] ); + client->baselines[i] = NULL; + } + } +} + +/* +===================== +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 *client, const char *reason ) { + int oldstate = client->state; + + if( client->state <= cs_zombie ) { + return; // called recursively? + } + + client->state = cs_zombie; // become free in a few seconds + client->lastmessage = svs.realtime; + + if( reason ) { + if( oldstate == cs_spawned ) { + // announce to others +#if USE_MVD_CLIENT + if( sv.state == ss_broadcast ) { + MVD_GameClientDrop( client->edict, reason ); + } else +#endif + { + SV_BroadcastPrintf( PRINT_HIGH, "%s was dropped: %s\n", + client->name, reason ); + } + } + + // print this to client as they will not receive broadcast + SV_ClientPrintf( client, PRINT_HIGH, "%s was dropped: %s\n", + client->name, reason ); + + // print to server console + if( Com_IsDedicated() ) { + Com_Printf( "%s[%s] was dropped: %s\n", client->name, + NET_AdrToString( &client->netchan->remote_address ), reason ); + } + } + + // add the disconnect + MSG_WriteByte( svc_disconnect ); + SV_ClientAddMessage( client, MSG_RELIABLE|MSG_CLEAR ); + + if( oldstate == cs_spawned || ( g_features->integer & GMF_WANT_ALL_DISCONNECTS ) ) { + // call the prog function for removing a client + // this will remove the body, among other things + ge->ClientDisconnect( client->edict ); + } + +#if USE_AC_SERVER + AC_ClientDisconnect( client ); +#endif + + SV_CleanClient( client ); + + Com_DPrintf( "Going to cs_zombie for %s\n", client->name ); + +#if USE_MVD_SERVER + // give MVD server a chance to detect if it's dummy client was dropped + SV_MvdClientDropped( client, reason ); +#endif +} + + +/* +============================================================================== + +CONNECTIONLESS COMMANDS + +============================================================================== +*/ + +static qboolean SV_RateLimited( ratelimit_t *r ) { + if( !r->limit ) { + return qfalse; + } + if( svs.realtime - r->time > r->period ) { + r->count = 0; + r->time = svs.realtime; + return qfalse; + } + + if( r->count < r->limit ) { + return qfalse; + } + + return qtrue; +} + +// <limit>[/<period>[sec|min|hour]] +static void SV_RateInit( ratelimit_t *r, const char *s ) { + unsigned limit; + unsigned period, scale; + char *p; + + limit = strtoul( s, &p, 10 ); + if( *p == '/' ) { + period = strtoul( p + 1, &p, 10 ); + if( *p == 0 || *p == 's' || *p == 'S' ) { + scale = 1000; + } else if( *p == 'm' || *p == 'M' ) { + scale = 60*1000; + } else if( *p == 'h' || *p == 'H' ) { + scale = 60*60*1000; + } else { + // everything else is milliseconds + scale = 1; + } + if( period > UINT_MAX / scale ) { + period = UINT_MAX; + } else { + if( !period ) { + period = 1; + } + period *= scale; + } + } else { + // default is one second + period = 1000; + } + + r->count = 0; + r->time = svs.realtime; + r->limit = limit; + r->period = period; +} + +addrmatch_t *SV_MatchAddress( list_t *list, netadr_t *address ) { + uint32_t addr = *( uint32_t * )address->ip; + addrmatch_t *match; + + LIST_FOR_EACH( addrmatch_t, match, list, entry ) { + if( ( addr & match->mask ) == ( match->addr & match->mask ) ) { + match->hits++; + match->time = time( NULL ); + return match; + } + } + + return NULL; +} + +/* +=============== +SV_StatusString + +Builds the string that is sent as heartbeats and status replies. +It is assumed that size of status buffer is at least SV_OUTPUTBUF_LENGTH! +=============== +*/ +static size_t SV_StatusString( char *status ) { + char entry[MAX_STRING_CHARS]; + client_t *cl; + size_t total, len; + char *tmp = sv_maxclients->string; + + // XXX: ugly hack to hide reserved slots + if( sv_reserved_slots->integer ) { + Q_snprintf( entry, sizeof( entry ), "%d", + sv_maxclients->integer - sv_reserved_slots->integer ); + sv_maxclients->string = entry; + } + + // add server info + total = Cvar_BitInfo( status, CVAR_SERVERINFO ); + + sv_maxclients->string = tmp; + + // add uptime + if( sv_uptime->integer > 0 ) { + if( sv_uptime->integer > 1 ) { + len = Com_UptimeLong_m( entry, MAX_INFO_VALUE ); + } else { + len = Com_Uptime_m( entry, MAX_INFO_VALUE ); + } + if( total + 8 + len < MAX_INFO_STRING ) { + memcpy( status + total, "\\uptime\\", 8 ); + memcpy( status + total + 8, entry, len ); + total += 8 + len; + } + } + + status[total++] = '\n'; + + // add player list + if( sv_status_show->integer > 1 ) { + FOR_EACH_CLIENT( cl ) { + if( cl->state == cs_zombie ) { + continue; + } + len = Q_snprintf( entry, sizeof( entry ), + "%i %i \"%s\"\n", + cl->edict->client->ps.stats[STAT_FRAGS], + cl->ping, cl->name ); + if( len >= sizeof( entry ) ) { + continue; + } + if( total + len >= SV_OUTPUTBUF_LENGTH ) { + break; // can't hold any more + } + memcpy( status + total, entry, len ); + total += len; + } + } + + status[total] = 0; + + return total; +} + +/* +================ +SVC_Status + +Responds with all the info that qplug or qspy can see +================ +*/ +static void SVC_Status( void ) { + char buffer[MAX_PACKETLEN_DEFAULT]; + size_t len; + + if( !sv_status_show->integer ) { + return; + } + + if( SV_RateLimited( &svs.ratelimit_status ) ) { + Com_DPrintf( "Dropping status request from %s\n", + NET_AdrToString( &net_from ) ); + return; + } + + svs.ratelimit_status.count++; + + // write the packet header + memcpy( buffer, "\xff\xff\xff\xffprint\n", 10 ); + len = 10; + + len += SV_StatusString( buffer + len ); + + // send the datagram + NET_SendPacket( NS_SERVER, &net_from, len, buffer ); +} + +/* +================ +SVC_Ack + +================ +*/ +static void SVC_Ack( void ) { + master_t *m; + + FOR_EACH_MASTER( m ) { + if( !m->adr.port ) { + continue; + } + if( NET_IsEqualBaseAdr( &m->adr, &net_from ) ) { + Com_DPrintf( "Ping acknowledge from %s\n", + NET_AdrToString( &net_from ) ); + m->last_ack = svs.realtime; + break; + } + } +} + +/* +================ +SVC_Info + +Responds with short info for broadcast scans +The second parameter should be the current protocol version number. +================ +*/ +static void SVC_Info( void ) { + char string[MAX_QPATH]; + size_t len; + int count; + int version; + + if (sv_maxclients->integer == 1) + return; // ignore in single player + + version = atoi (Cmd_Argv(1)); + + if (version != PROTOCOL_VERSION_DEFAULT) { + return; + } + + count = SV_CountClients(); + + len = Q_snprintf (string, sizeof(string), + "\xff\xff\xff\xffinfo\n%16s %8s %2i/%2i\n", + sv_hostname->string, sv.name, count, sv_maxclients->integer - + sv_reserved_slots->integer ); + if( len >= sizeof( string ) ) { + return; + } + + NET_SendPacket( NS_SERVER, &net_from, len, string ); +} + +/* +================ +SVC_Ping + +Just responds with an acknowledgement +================ +*/ +static void SVC_Ping( void ) { + OOB_PRINT( NS_SERVER, &net_from, "ack" ); +} + +/* +================= +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. +================= +*/ +static void SVC_GetChallenge( void ) { + int i, oldest; + unsigned challenge; + unsigned oldestTime; + + oldest = 0; + oldestTime = 0xffffffff; + + // see if we already have a challenge for this ip + for( i = 0; i < MAX_CHALLENGES; i++ ) { + if( NET_IsEqualBaseAdr( &net_from, &svs.challenges[i].adr ) ) + break; + if( svs.challenges[i].time > com_eventTime ) { + svs.challenges[i].time = com_eventTime; + } + if( svs.challenges[i].time < oldestTime ) { + oldestTime = svs.challenges[i].time; + oldest = i; + } + } + + challenge = ( ( rand() << 16 ) | rand() ) & 0x7fffffff; + if( i == MAX_CHALLENGES ) { + // overwrite the oldest + svs.challenges[oldest].challenge = challenge; + svs.challenges[oldest].adr = net_from; + svs.challenges[oldest].time = com_eventTime; + } else { + svs.challenges[i].challenge = challenge; + svs.challenges[i].time = com_eventTime; + } + + // send it back + Netchan_OutOfBand( NS_SERVER, &net_from, + "challenge %u p=34,35,36", challenge ); +} + +static void send_redirect_hack( const char *addr ) { + Netchan_OutOfBand( NS_SERVER, &net_from, "client_connect" ); + + MSG_WriteLong( 1 ); + MSG_WriteLong( 0 ); + MSG_WriteByte( svc_print ); + MSG_WriteByte( PRINT_HIGH ); + MSG_WriteString( va( "Server is full.\nRedirecting you to %s...\n", addr ) ); + MSG_WriteByte( svc_stufftext ); + MSG_WriteString( va( "connect %s\n", addr ) ); + + NET_SendPacket( NS_SERVER, &net_from, msg_write.cursize, msg_write.data ); + SZ_Clear( &msg_write ); +} + +#define SV_OobPrintf(...) \ + Netchan_OutOfBand( NS_SERVER, &net_from, "print\n" __VA_ARGS__ ) + +/* +================== +SVC_DirectConnect + +A connection request that did not come from the master +================== +*/ +static void SVC_DirectConnect( void ) { + char userinfo[MAX_INFO_STRING]; + char reconnect_var[16]; + char reconnect_val[16]; + int i, number, count; + size_t length; + client_t *cl, *newcl, *lastcl; + int protocol, version; + int qport; + int challenge; + qboolean allow; + char *info, *s; + int maxlength; + netchan_type_t nctype; + char *ncstring, *acstring; + char dlstring[MAX_INFO_STRING]; + int reserved; + qboolean has_zlib; + + protocol = atoi( Cmd_Argv( 1 ) ); + qport = atoi( Cmd_Argv( 2 ) ) ; + challenge = atoi( Cmd_Argv( 3 ) ); + + Com_DPrintf( "%s: protocol=%i, qport=%i, challenge=%i\n", + __func__, protocol, qport, challenge ); + + if( protocol < PROTOCOL_VERSION_DEFAULT || + protocol > PROTOCOL_VERSION_Q2PRO ) + { + SV_OobPrintf( "Unsupported protocol version %d.\n", protocol ); + Com_DPrintf( " rejected connect with protocol %i\n", protocol ); + return; + } + + if( !NET_IsLocalAddress( &net_from ) ) { + addrmatch_t *match; + + // see if the challenge is valid + for( i = 0; i < MAX_CHALLENGES; i++ ) { + if( !svs.challenges[i].challenge ) { + continue; + } + if( NET_IsEqualBaseAdr( &net_from, &svs.challenges[i].adr ) ) { + if( svs.challenges[i].challenge == challenge ) + break; // good + SV_OobPrintf( "Bad challenge.\n" ); + Com_DPrintf( " rejected - bad challenge.\n" ); + return; + } + } + if( i == MAX_CHALLENGES ) { + SV_OobPrintf( "No challenge for address.\n" ); + Com_DPrintf( " rejected - no challenge.\n" ); + return; + } + svs.challenges[i].challenge = 0; + + // check for banned address + if( ( match = SV_MatchAddress( &sv_banlist, &net_from ) ) != NULL ) { + s = match->comment; + if( !*s ) { + s = "Your IP address is banned from this server."; + } + SV_OobPrintf( "%s\nConnection refused.\n", s ); + Com_DPrintf( " rejected connect from banned IP\n" ); + return; + } + + if( sv_locked->integer ) { + SV_OobPrintf( "Server is locked.\n" ); + Com_DPrintf( " rejected - server is locked.\n" ); + return; + } + + // limit number of connections from single IP + if( sv_iplimit->integer > 0 ) { + count = 0; + FOR_EACH_CLIENT( cl ) { + if( NET_IsEqualBaseAdr( &net_from, + &cl->netchan->remote_address ) ) + { + if( cl->state == cs_zombie ) { + count++; + } else { + count += 2; + } + } + } + count >>= 1; + if( count >= sv_iplimit->integer ) { + SV_OobPrintf( "Too many connections from your IP address.\n" ); + Com_DPrintf( " rejected - %d connections from this IP.\n", count ); + return; + } + } + } + + // set maximum message length + maxlength = MAX_PACKETLEN_WRITABLE_DEFAULT; + has_zlib = qfalse; + if( protocol >= PROTOCOL_VERSION_R1Q2 ) { + has_zlib = qtrue; + s = Cmd_Argv( 5 ); + if( *s ) { + maxlength = atoi( s ); + if( maxlength < 0 || maxlength > MAX_PACKETLEN_WRITABLE ) { + SV_OobPrintf( "Invalid maximum message length.\n" ); + Com_DPrintf( " rejected - bad maxmsglen.\n" ); + return; + } + if( !maxlength ) { + maxlength = MAX_PACKETLEN_WRITABLE; + } else if( maxlength < MIN_PACKETLEN ) { + maxlength = MIN_PACKETLEN; + } + } + } + + if( !NET_IsLocalAddress( &net_from ) ) { + // cap maximum message length for real connections + if( net_maxmsglen->integer > 0 && maxlength > net_maxmsglen->integer ) { + maxlength = net_maxmsglen->integer; + } + } + + if( protocol == PROTOCOL_VERSION_R1Q2 ) { + // set minor protocol version + s = Cmd_Argv( 6 ); + if( *s ) { + version = atoi( s ); + clamp( version, PROTOCOL_VERSION_R1Q2_MINIMUM, + PROTOCOL_VERSION_R1Q2_CURRENT ); + } else { + version = PROTOCOL_VERSION_R1Q2_MINIMUM; + } + nctype = NETCHAN_OLD; + ncstring = ""; + } else if( protocol == PROTOCOL_VERSION_Q2PRO ) { + // set netchan type + s = Cmd_Argv( 6 ); + if( *s ) { + nctype = atoi( s ); + if( nctype == NETCHAN_OLD ) { + ncstring = " nc=0"; + } else if( nctype == NETCHAN_NEW ) { + ncstring = " nc=1"; + } else { + SV_OobPrintf( "Invalid netchan type.\n" ); + Com_DPrintf( " rejected - bad nctype.\n" ); + return; + } + } else { + nctype = NETCHAN_NEW; + ncstring = " nc=1"; + } + + // set zlib + s = Cmd_Argv( 7 ); + if( *s && !atoi( s ) ) { + has_zlib = qfalse; + } + + // set minor protocol version + s = Cmd_Argv( 8 ); + if( *s ) { + version = atoi( s ); + clamp( version, PROTOCOL_VERSION_Q2PRO_MINIMUM, + PROTOCOL_VERSION_Q2PRO_CURRENT ); + if( version == PROTOCOL_VERSION_Q2PRO_RESERVED ) { + version--; // never use this version + } + } else { + version = PROTOCOL_VERSION_Q2PRO_MINIMUM; + } + } else { + nctype = NETCHAN_OLD; + ncstring = ""; + version = 0; + } + + // validate userinfo + info = Cmd_Argv( 4 ); + if( !info[0] ) { + SV_OobPrintf( "Empty userinfo string.\n" ); + Com_DPrintf( " rejected - empty userinfo.\n" ); + return; + } + + if( !Info_Validate( info ) ) { + SV_OobPrintf( "Malformed userinfo string.\n" ); + Com_DPrintf( " rejected - malformed userinfo.\n" ); + return; + } + + s = Info_ValueForKey( info, "name" ); + if( COM_iswhite( s ) ) { + SV_OobPrintf( "Please set your name before connecting.\n" ); + Com_DPrintf( " rejected - empty name.\n" ); + return; + } + + s = Info_ValueForKey( info, "skin" ); + if( !s[0] ) { + SV_OobPrintf( "Please set your skin before connecting.\n" ); + Com_DPrintf( " rejected - empty skin.\n" ); + return; + } + + length = strlen( s ); + if( !Q_ispath( s[0] ) || !Q_ispath( s[ length - 1 ] ) || strchr( s, '.' ) ) + { + SV_OobPrintf( "Malformed skin.\n" ); + Com_DPrintf( " rejected - malformed skin.\n" ); + return; + } + + // check password + s = Info_ValueForKey( info, "password" ); + reserved = 0; + if( sv_password->string[0] ) { + if( !s[0] ) { + SV_OobPrintf( "Please set your password before connecting.\n" ); + Com_DPrintf( " rejected - empty password.\n" ); + return; + } + if( SV_RateLimited( &svs.ratelimit_auth ) ) { + SV_OobPrintf( "Invalid password.\n" ); + Com_DPrintf( " rejected - auth attempt limit exceeded.\n" ); + return; + } + if( strcmp( sv_password->string, s ) ) { + svs.ratelimit_auth.count++; + SV_OobPrintf( "Invalid password.\n" ); + Com_DPrintf( " rejected - invalid password.\n" ); + return; + } + // allow them to use reserved slots + } else if( !sv_reserved_password->string[0] || + strcmp( sv_reserved_password->string, s ) ) + { + // in no reserved password is set on the server, do not allow + // anyone to access reserved slots at all + reserved = sv_reserved_slots->integer; + } + + Q_strlcpy( userinfo, info, sizeof( userinfo ) ); + + // make sure mvdspec key is not set + Info_RemoveKey( userinfo, "mvdspec" ); + + if( sv_password->string[0] || sv_reserved_password->string[0] ) { + // unset password key to make game mod happy + Info_RemoveKey( userinfo, "password" ); + } + + // force the IP key/value pair so the game can filter based on ip + s = NET_AdrToString( &net_from ); + if( !Info_SetValueForKey( userinfo, "ip", s ) ) { + SV_OobPrintf( "Oversize userinfo string.\n" ); + Com_DPrintf( " rejected - oversize userinfo.\n" ); + return; + } + + newcl = NULL; + reconnect_var[0] = 0; + reconnect_val[0] = 0; + + // if there is already a slot for this ip, reuse it + FOR_EACH_CLIENT( cl ) { + if( NET_IsEqualAdr( &net_from, &cl->netchan->remote_address ) ) { + if( cl->state == cs_zombie ) { + strcpy( reconnect_var, cl->reconnect_var ); + strcpy( reconnect_val, cl->reconnect_val ); + } else { + SV_DropClient( cl, "reconnected" ); + } + + Com_DPrintf( "%s: reconnect\n", NET_AdrToString( &net_from ) ); + newcl = cl; + + // NOTE: it's safe to call SV_Remove since we exit the loop + SV_RemoveClient( cl ); + break; + } + } + + // find a client slot + if( !newcl ) { + lastcl = svs.udp_client_pool + sv_maxclients->integer - reserved; + for( newcl = svs.udp_client_pool; newcl < lastcl; newcl++ ) { + if( !newcl->state ) { + break; + } + } + if( newcl == lastcl ) { + if( sv_reserved_slots->integer && !reserved ) { + SV_OobPrintf( "Server and reserved slots are full.\n" ); + Com_DPrintf( " rejected - reserved slots are full.\n" ); + } else { + SV_OobPrintf( "Server is full.\n" ); + Com_DPrintf( " rejected - server is full.\n" ); + + // optionally redirect them to a different address + if( sv_redirect_address->string[0] ) { + send_redirect_hack( sv_redirect_address->string ); + } + } + return; + } + } + + // build a new connection + // accept the new client + // this is the only place a client_t is ever initialized + memset( newcl, 0, sizeof( *newcl ) ); + number = newcl - svs.udp_client_pool; + newcl->number = newcl->slot = number; + newcl->challenge = challenge; // save challenge for checksumming + newcl->protocol = protocol; + newcl->version = version; + newcl->has_zlib = has_zlib; + newcl->edict = EDICT_NUM( number + 1 ); + newcl->gamedir = fs_game->string; + newcl->mapname = sv.name; + newcl->configstrings = ( char * )sv.configstrings; + newcl->pool = ( edict_pool_t * )&ge->edicts; + newcl->cm = &sv.cm; + newcl->spawncount = sv.spawncount; + newcl->maxclients = sv_maxclients->integer; + strcpy( newcl->reconnect_var, reconnect_var ); + strcpy( newcl->reconnect_val, reconnect_val ); + + // copy default pmove parameters + newcl->pmp = sv_pmp; + newcl->pmp.airaccelerate = sv_airaccelerate->integer ? qtrue : qfalse; + + // r1q2 extensions + i = 2; + if( protocol == PROTOCOL_VERSION_R1Q2 || + protocol == PROTOCOL_VERSION_Q2PRO ) + { + newcl->pmp.speedmult = 2; + i = 1; + } + newcl->pmp.strafehack = sv_strafejump_hack->integer >= i ? qtrue : qfalse; + + if( protocol == PROTOCOL_VERSION_R1Q2 ) { + if( version >= PROTOCOL_VERSION_R1Q2_LONG_SOLID ) { + newcl->esFlags |= MSG_ES_LONGSOLID; + } + } + + // q2pro extensions + i = 2; + if( protocol == PROTOCOL_VERSION_Q2PRO ) { + if( sv_qwmod->integer ) { + PmoveEnableQW( &newcl->pmp ); + } + newcl->pmp.flyhack = qtrue; + newcl->pmp.flyfriction = 4; + newcl->esFlags |= MSG_ES_UMASK; + if( version >= PROTOCOL_VERSION_Q2PRO_LONG_SOLID ) { + newcl->esFlags |= MSG_ES_LONGSOLID; + } + if( version >= PROTOCOL_VERSION_Q2PRO_WATERJUMP_HACK ) { + i = 1; + } + } + newcl->pmp.waterhack = sv_waterjump_hack->integer >= i ? qtrue : qfalse; + + // get the game a chance to reject this connection or modify the userinfo + sv_client = newcl; + sv_player = newcl->edict; + allow = ge->ClientConnect( newcl->edict, userinfo ); + sv_client = NULL; + sv_player = NULL; + if ( !allow ) { + char *reason; + + reason = Info_ValueForKey( userinfo, "rejmsg" ); + if( *reason ) { + SV_OobPrintf( "%s\nConnection refused.\n", reason ); + } else { + SV_OobPrintf( "Connection refused.\n" ); + } + Com_DPrintf( " game rejected a connection.\n" ); + return; + } + + // setup netchan + newcl->netchan = Netchan_Setup( NS_SERVER, nctype, &net_from, + qport, maxlength, protocol ); + newcl->numpackets = 1; + + // parse some info from the info strings + Q_strlcpy( newcl->userinfo, userinfo, sizeof( newcl->userinfo ) ); + SV_UserinfoChanged( newcl ); + +#if USE_AC_SERVER + if( !sv_force_reconnect->string[0] || reconnect_var[0] ) { + acstring = AC_ClientConnect( newcl ); + } else +#endif + { + acstring = ""; + } + + if( sv_downloadserver->string[0] ) { + Q_snprintf( dlstring, sizeof( dlstring ), " dlserver=%s", + sv_downloadserver->string ); + } else { + dlstring[0] = 0; + } + + // send the connect packet to the client + Netchan_OutOfBand( NS_SERVER, &net_from, "client_connect%s%s%s map=%s", + ncstring, acstring, dlstring, newcl->mapname ); + + SV_InitClientSend( newcl ); + + if( protocol == PROTOCOL_VERSION_DEFAULT ) { + newcl->WriteFrame = SV_WriteFrameToClient_Default; + } else { + newcl->WriteFrame = SV_WriteFrameToClient_Enhanced; + } + + // loopback client doesn't need to reconnect + if( NET_IsLocalAddress( &net_from ) ) { + newcl->reconnected = qtrue; + } + + // add them to the linked list of connected clients + List_SeqAdd( &svs.udp_client_list, &newcl->entry ); + + Com_DPrintf( "Going from cs_free to cs_assigned for %s\n", newcl->name ); + newcl->state = cs_assigned; + newcl->lastframe = -1; + newcl->lastmessage = svs.realtime; // don't timeout + newcl->min_ping = 9999; +} + +static int Rcon_Validate (void) +{ + if (!rcon_password->string[0]) + return 0; + + if (strcmp (Cmd_Argv(1), rcon_password->string) ) + return 0; + + return 1; +} + +/* +=============== +SVC_RemoteCommand + +A client issued an rcon command. +Redirect all printfs. +=============== +*/ +static void SVC_RemoteCommand( void ) { + int i; + char *string; + + if( SV_RateLimited( &svs.ratelimit_rcon ) ) { + Com_DPrintf( "Dropping rcon from %s\n", + NET_AdrToString( &net_from ) ); + return; + } + + i = Rcon_Validate(); + string = Cmd_RawArgsFrom( 2 ); + if( i == 0 ) { + Com_Printf( "Invalid rcon from %s:\n%s\n", + NET_AdrToString( &net_from ), string ); + SV_OobPrintf( "Bad rcon_password.\n" ); + svs.ratelimit_rcon.count++; + return; + } + + Com_Printf( "Rcon from %s:\n%s\n", + NET_AdrToString( &net_from ), string ); + + SV_BeginRedirect( RD_PACKET ); + + Cmd_ExecuteString( &cmd_buffer, string ); + + Com_EndRedirect(); +} + +static const ucmd_t svcmds[] = { + { "ping", SVC_Ping }, + { "ack", SVC_Ack }, + { "status", SVC_Status }, + { "info", SVC_Info }, + { "getchallenge", SVC_GetChallenge }, + { "connect", SVC_DirectConnect }, + { NULL } +}; + +/* +================= +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. +================= +*/ +static void SV_ConnectionlessPacket( void ) { + char string[MAX_STRING_CHARS]; + char *c; + int i; + size_t len; + + if( SV_MatchAddress( &sv_blacklist, &net_from ) ) { + Com_DPrintf( "ignored blackholed connectionless packet\n" ); + return; + } + + MSG_BeginReading(); + MSG_ReadLong(); // skip the -1 marker + + len = MSG_ReadStringLine( string, sizeof( string ) ); + if( len >= sizeof( string ) ) { + Com_DPrintf( "ignored oversize connectionless packet\n" ); + return; + } + + Cmd_TokenizeString( string, qfalse ); + + c = Cmd_Argv( 0 ); + Com_DPrintf( "ServerPacket[%s]: %s\n", NET_AdrToString( &net_from ), c ); + + if( !NET_IsLocalAddress( &net_from ) && net_from.ip[0] == 127 && + net_from.port == Cvar_VariableInteger( "net_port" ) ) + { + Com_DPrintf( "dropped connectionless packet from self\n" ); + return; + } + + if( !strcmp( c, "rcon" ) ) { + SVC_RemoteCommand(); + return; // accept rcon commands even if not active + } + + if( !svs.initialized ) { + Com_DPrintf( "ignored connectionless packet\n" ); + return; + } + + for( i = 0; svcmds[i].name; i++ ) { + if( !strcmp( c, svcmds[i].name ) ) { + svcmds[i].func(); + return; + } + } + + Com_DPrintf( "bad connectionless packet\n" ); +} + + +//============================================================================ + +int SV_CountClients( void ) { + client_t *cl; + int count = 0; + + FOR_EACH_CLIENT( cl ) { + if( cl->state > cs_zombie ) { + count++; + } + } + return count; +} + +static int ping_nop( client_t *cl ) { + return 0; +} + +static int ping_min( client_t *cl ) { + int j, count = 9999; + + for( j = 0; j < LATENCY_COUNTS; j++ ) { + if( cl->frame_latency[j] > 0 ) { + if( count > cl->frame_latency[j] ) { + count = cl->frame_latency[j]; + } + } + } + return count == 9999 ? 0 : count; +} + +static int ping_avg( client_t *cl ) { + int j, total = 0, count = 0; + + for( j = 0; j < LATENCY_COUNTS; j++ ) { + if( cl->frame_latency[j] > 0 ) { + count++; + total += cl->frame_latency[j]; + } + } + return count ? total / count : 0; +} + +/* +=================== +SV_CalcPings + +Updates the cl->ping variables +=================== +*/ +static void SV_CalcPings( void ) { + client_t *cl; + int (*calc)( client_t * ); + int res; + + switch( sv_calcpings_method->integer ) { + case 0: calc = ping_nop; break; + case 2: calc = ping_min; break; + default: calc = ping_avg; break; + } + + // update avg ping every 10 seconds + res = sv.framenum % 100; + + FOR_EACH_CLIENT( cl ) { + if( cl->state == cs_spawned ) { + cl->ping = calc( cl ); + if( cl->ping ) { + if( cl->ping < cl->min_ping ) { + cl->min_ping = cl->ping; + } else if( cl->ping > cl->max_ping ) { + cl->max_ping = cl->ping; + } + if( !res ) { + cl->avg_ping_time += cl->ping; + cl->avg_ping_count++; + } + } + } else { + cl->ping = 0; + } + + // let the game dll know about the ping + cl->edict->client->ping = cl->ping; + } +} + + +/* +=================== +SV_GiveMsec + +Every few frames, gives all clients an allotment of milliseconds +for their command moves. If they exceed it, assume cheating. +=================== +*/ +static void SV_GiveMsec( void ) { + client_t *cl; + + if( sv.framenum & 15 ) + return; + + FOR_EACH_CLIENT( cl ) { + cl->commandMsec = 18 * SV_FRAMETIME; // 1600 + some slop + } + + if( sv.framenum & 63 ) + return; + + FOR_EACH_CLIENT( cl ) { + cl->fps = ( cl->numMoves * SV_FPS ) >> 6; + cl->numMoves = 0; + } +} + + +/* +================= +SV_PacketEvent +================= +*/ +static void SV_PacketEvent( void ) { + client_t *client; + netchan_t *netchan; + int qport; + + // check for connectionless packet (0xffffffff) first + // connectionless packets are processed even if the server is down + if( *( int * )msg_read.data == -1 ) { + SV_ConnectionlessPacket(); + return; + } + + if( !svs.initialized ) { + return; + } + + // check for packets from connected clients + FOR_EACH_CLIENT( client ) { + netchan = client->netchan; + if( !NET_IsEqualBaseAdr( &net_from, &netchan->remote_address ) ) { + continue; + } + + // read the qport out of the message so we can fix up + // stupid address translating routers + if( client->protocol == PROTOCOL_VERSION_DEFAULT ) { + qport = msg_read.data[8] | ( msg_read.data[9] << 8 ); + if( netchan->qport != qport ) { + continue; + } + } else if( netchan->qport ) { + qport = msg_read.data[8]; + if( netchan->qport != qport ) { + continue; + } + } else { + if( netchan->remote_address.port != net_from.port ) { + continue; + } + } + + if( netchan->remote_address.port != net_from.port ) { + Com_DPrintf( "Fixing up a translated port for %s: %d --> %d\n", + client->name, netchan->remote_address.port, net_from.port ); + netchan->remote_address.port = net_from.port; + } + + if( netchan->Process( netchan ) ) { + // this is a valid, sequenced packet, so process it + if( client->state != cs_zombie ) { + //if( client->state != cs_assigned ) { + client->lastmessage = svs.realtime; // don't timeout + //} +#if USE_ICMP + client->unreachable = qfalse; // don't drop +#endif + SV_ExecuteClientMessage( client ); + } + } + + break; + } +} + +#if USE_ICMP +/* +================= +SV_ErrorEvent +================= +*/ +void SV_ErrorEvent( int info ) { + client_t *client; + netchan_t *netchan; + + if( !svs.initialized ) { + return; + } + + // check for errors from connected clients + FOR_EACH_CLIENT( client ) { + if( client->state == cs_zombie ) { + continue; // already a zombie + } + netchan = client->netchan; + if( !NET_IsEqualBaseAdr( &net_from, &netchan->remote_address ) ) { + continue; + } + if( net_from.port && netchan->remote_address.port != net_from.port ) { + continue; + } +#if USE_PMTUDISC + if( info ) { + // we are doing path MTU discovery and got ICMP fragmentation-needed + // update MTU only for connecting clients to minimize spoofed ICMP interference + // MTU info has already been sanity checked for us by network code + // assume total 64 bytes of headers + if( client->state == cs_primed && + netchan->reliable_length && + info < netchan->maxpacketlen + 64 ) + { + if( netchan->type == NETCHAN_OLD ) { + // TODO: old clients require entire queue flush :( + continue; + } + Com_Printf( "Fixing up maxmsglen for %s: %d --> %d\n", + client->name, (int)netchan->maxpacketlen, info - 64 ); + netchan->maxpacketlen = info - 64; + } + continue; + } +#endif + client->unreachable = qtrue; // drop them soon + break; + } +} +#endif + +static void SV_ReadPackets( void ) { +#if USE_CLIENT + memset( &net_from, 0, sizeof( net_from ) ); + net_from.type = NA_LOOPBACK; + + // process loopback packets + while( NET_GetLoopPacket( NS_SERVER ) ) { + SV_PacketEvent(); + } +#endif + + // process network packets + while( NET_GetPacket( NS_SERVER ) ) { + SV_PacketEvent(); + } +} + + +/* +================== +SV_CheckTimeouts + +If a packet has not been received from a client for 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 +================== +*/ +static void SV_CheckTimeouts( void ) { + client_t *client; + unsigned zombie_time = 1000 * sv_zombietime->value; + unsigned drop_time = 1000 * sv_timeout->value; + unsigned ghost_time = 1000 * sv_ghostime->value; + unsigned delta; + + FOR_EACH_CLIENT( client ) { + // never timeout local clients + if( NET_IsLocalAddress( &client->netchan->remote_address ) ) { + continue; + } + // NOTE: delta calculated this way is not sensitive to overflow + delta = svs.realtime - client->lastmessage; + if( client->state == cs_zombie ) { + if( delta > zombie_time ) { + SV_RemoveClient( client ); + } + continue; + } + if( client->drop_hack ) { + SV_DropClient( client, NULL ); + continue; + } +#if USE_ICMP + if( client->unreachable ) { + if( delta > ghost_time ) { + SV_DropClient( client, "connection reset by peer" ); + SV_RemoveClient( client ); // don't bother with zombie state + continue; + } + } +#endif + if( delta > drop_time || ( client->state == cs_assigned && delta > ghost_time ) ) { + SV_DropClient( client, "connection timed out" ); + SV_RemoveClient( client ); // don't bother with zombie state + continue; + } + + if( client->frames_nodelta > 64 && !sv_allow_nodelta->integer ) { + SV_DropClient( client, "too many nodelta frames" ); + continue; + } + } +} + +/* +================ +SV_PrepWorldFrame + +This has to be done before the world logic, because +player processing happens outside RunWorldFrame +================ +*/ +static void SV_PrepWorldFrame( void ) { + edict_t *ent; + int i; + +#if USE_MVD_CLIENT + if( sv.state == ss_broadcast ) { + MVD_PrepWorldFrame(); + return; + } +#endif + + for( i = 1; i < ge->num_edicts; i++, ent++ ) { + ent = EDICT_NUM( i ); + // events only last for a single message + ent->s.event = 0; + } + + sv.tracecount = 0; +} + +#if USE_CLIENT +static inline qboolean check_paused( void ) { + if( !dedicated->integer && cl_paused->integer && + LIST_SINGLE( &svs.udp_client_list ) ) + { + if( !sv_paused->integer ) { + Cvar_Set( "sv_paused", "1" ); + IN_Activate(); + } + return qtrue; // don't run if paused + } + + if( sv_paused->integer ) { + Cvar_Set( "sv_paused", "0" ); + IN_Activate(); + } + return qfalse; + +} +#endif + + +/* +================= +SV_RunGameFrame +================= +*/ +static void SV_RunGameFrame( void ) { + // we always need to bump framenum, even if we + // don't run the world, otherwise the delta + // compression can get confused when a client + // has the "current" frame + sv.framenum++; + +#if USE_CLIENT + // pause if there is only local client on the server + if( check_paused() ) { + return; + } +#endif + +#if USE_MVD_SERVER + // save the entire world state if recording a serverdemo + SV_MvdBeginFrame(); +#endif + +#if USE_CLIENT + if( host_speeds->integer ) + time_before_game = Sys_Milliseconds(); +#endif + + ge->RunFrame(); + +#if USE_CLIENT + if( host_speeds->integer ) + time_after_game = Sys_Milliseconds(); +#endif + + if( msg_write.cursize ) { + Com_WPrintf( "Game DLL left %"PRIz" bytes " + "in multicast buffer, cleared.\n", + msg_write.cursize ); + SZ_Clear( &msg_write ); + } + +#if USE_MVD_SERVER + // save the entire world state if recording a serverdemo + SV_MvdEndFrame(); +#endif + +} + +/* +================ +SV_MasterHeartbeat + +Send a message to the master every few minutes to +let it know we are alive, and log information +================ +*/ +static void SV_MasterHeartbeat( void ) { + char buffer[MAX_PACKETLEN_DEFAULT]; + size_t len; + master_t *m; + + if( !Com_IsDedicated() ) + return; // only dedicated servers send heartbeats + + if( !sv_public->integer ) + return; // a private dedicated game + + if( svs.realtime - svs.last_heartbeat < HEARTBEAT_SECONDS*1000 ) + return; // not time to send yet + + svs.last_heartbeat = svs.realtime; + + // write the packet header + memcpy( buffer, "\xff\xff\xff\xffheartbeat\n", 14 ); + len = 14; + + // send the same string that we would give for a status OOB command + len += SV_StatusString( buffer + len ); + + // send to group master + FOR_EACH_MASTER( m ) { + if( m->adr.port ) { + Com_DPrintf( "Sending heartbeat to %s\n", + NET_AdrToString( &m->adr ) ); + NET_SendPacket( NS_SERVER, &m->adr, len, buffer ); + } + } +} + +/* +================= +SV_MasterShutdown + +Informs all masters that this server is going down +================= +*/ +static void SV_MasterShutdown( void ) { + master_t *m; + + if( !Com_IsDedicated() ) + return; // only dedicated servers send heartbeats + + if( !sv_public->integer ) + return; // a private dedicated game + + // send to group master + FOR_EACH_MASTER( m ) { + if( m->adr.port ) { + Com_DPrintf( "Sending shutdown to %s\n", + NET_AdrToString( &m->adr ) ); + OOB_PRINT( NS_SERVER, &m->adr, "shutdown" ); + } + } +} + +/* +================== +SV_Frame + +Some things like MVD client connections and command buffer +processing are run even when server is not yet initalized. + +Returns amount of extra frametime available for sleeping on IO. +================== +*/ +unsigned SV_Frame( unsigned msec ) { +#if USE_CLIENT + time_before_game = time_after_game = 0; +#endif + + // advance local server time + svs.realtime += msec; + +#if USE_MVD_CLIENT + // run connections to MVD/GTV servers + MVD_Frame(); +#endif + + // read packets from UDP clients + SV_ReadPackets(); + + if( svs.initialized ) { +#if USE_AC_SERVER + // run connection to the anticheat server + AC_Run(); +#endif + +#if USE_MVD_SERVER + // run connections from MVD/GTV clients + SV_MvdRunClients(); +#endif + + // deliver fragments and reliable messages for connecting clients + SV_SendAsyncPackets(); + } + + // move autonomous things around if enough time has passed + sv.frameresidual += msec; + if( sv.frameresidual < SV_FRAMETIME ) { + return SV_FRAMETIME - sv.frameresidual; + } + + if( svs.initialized ) { + // check timeouts + SV_CheckTimeouts(); + + // update ping based on the last known frame from all clients + SV_CalcPings(); + + // give the clients some timeslices + SV_GiveMsec(); + + // let everything in the world think and move + SV_RunGameFrame(); + + // send messages back to the UDP clients + SV_SendClientMessages(); + + // send a heartbeat to the master if needed + SV_MasterHeartbeat(); + + // clear teleport flags, etc for next frame + SV_PrepWorldFrame(); + } + + if( Com_IsDedicated() ) { + // run cmd buffer in dedicated mode + if( cmd_buffer.waitCount > 0 ) { + cmd_buffer.waitCount--; + } + } + + // decide how long to sleep next frame + sv.frameresidual -= SV_FRAMETIME; + if( sv.frameresidual < SV_FRAMETIME ) { + return SV_FRAMETIME - sv.frameresidual; + } + + // don't accumulate bogus residual + if( sv.frameresidual > 250 ) { + Com_DPrintf( "Reset residual %u\n", sv.frameresidual ); + sv.frameresidual = 100; + } + + return 0; +} + +//============================================================================ + +/* +================= +SV_UpdateUserinfo + +Ensures that name, skin and ip are properly set. +WARNING: may modify userinfo in place! +================= +*/ +void SV_UpdateUserinfo( char *userinfo ) { + char *s; + size_t len; + + if( !userinfo[0] ) { + SV_DropClient( sv_client, "empty userinfo" ); + return; + } + + if( !Info_Validate( userinfo ) ) { + SV_DropClient( sv_client, "malformed userinfo" ); + return; + } + + s = Info_ValueForKey( userinfo, "name" ); + if( COM_iswhite( s ) ) { + if( sv_client->name[0] ) { + SV_ClientCommand( sv_client, "set name \"%s\"\n", sv_client->name ); + } else { + SV_DropClient( sv_client, "malformed name" ); + } + return; + } + + s = Info_ValueForKey( userinfo, "skin" ); + len = strlen( s ); + if( !Q_ispath( s[0] ) || !Q_ispath( s[ len - 1 ] ) || strchr( s, '.' ) ) { + SV_ClientCommand( sv_client, "set skin \"male/grunt\"\n" ); + return; + } + + // force the IP key/value pair so the game can filter based on ip + s = NET_AdrToString( &sv_client->netchan->remote_address ); + if( !Info_SetValueForKey( userinfo, "ip", s ) ) { + SV_DropClient( sv_client, "oversize userinfo" ); + return; + } + + strcpy( sv_client->userinfo, userinfo ); + + SV_UserinfoChanged( sv_client ); +} + + +/* +================= +SV_UserinfoChanged + +Pull specific info from a newly changed userinfo string +into a more C freindly form. +================= +*/ +void SV_UserinfoChanged( client_t *cl ) { + char name[MAX_CLIENT_NAME]; + char *val; + size_t len; + int i; + + // call prog code to allow overrides + ge->ClientUserinfoChanged( cl->edict, cl->userinfo ); + + // name for C code + val = Info_ValueForKey( cl->userinfo, "name" ); + len = Q_strlcpy( name, val, sizeof( name ) ); + if( len >= sizeof( name ) ) { + len = sizeof( name ) - 1; + } + // mask off high bit + for( i = 0; i < len; i++ ) + name[i] &= 127; + if( cl->name[0] && strcmp( cl->name, name ) ) { + if( Com_IsDedicated() ) { + Com_Printf( "%s[%s] changed name to %s\n", cl->name, + NET_AdrToString( &cl->netchan->remote_address ), name ); + } +#if USE_MVD_CLIENT + if( sv.state == ss_broadcast ) { + MVD_GameClientNameChanged( cl->edict, name ); + } else +#endif + if( sv_show_name_changes->integer ) { + SV_BroadcastPrintf( PRINT_HIGH, "%s changed name to %s\n", + cl->name, name ); + } + } + memcpy( cl->name, name, len + 1 ); + + // rate command + val = Info_ValueForKey( cl->userinfo, "rate" ); + if( *val ) { + cl->rate = atoi( val ); + clamp( cl->rate, 100, 15000 ); + } else { + cl->rate = 5000; + } + + // never drop over the loopback + if( NET_IsLocalAddress( &cl->netchan->remote_address ) ) { + cl->rate = 0; + } + + // don't drop over LAN connections + if( sv_lan_force_rate->integer && + NET_IsLanAddress( &cl->netchan->remote_address ) ) + { + cl->rate = 0; + } + + // msg command + val = Info_ValueForKey( cl->userinfo, "msg" ); + if( *val ) { + cl->messagelevel = atoi( val ); + clamp( cl->messagelevel, PRINT_LOW, PRINT_CHAT + 1 ); + } +} + + +//============================================================================ + +#if USE_SYSCON +void SV_SetConsoleTitle( void ) { + char buffer[MAX_STRING_CHARS]; + + Q_snprintf( buffer, sizeof( buffer ), "%s (port %d%s)", + sv_hostname->string, net_port->integer, + sv_running->integer ? "" : ", down" ); + + Sys_SetConsoleTitle( buffer ); +} +#endif + +static void sv_status_limit_changed( cvar_t *self ) { + SV_RateInit( &svs.ratelimit_status, self->string ); +} +static void sv_auth_limit_changed( cvar_t *self ) { + SV_RateInit( &svs.ratelimit_auth, self->string ); +} +static void sv_rcon_limit_changed( cvar_t *self ) { + SV_RateInit( &svs.ratelimit_rcon, self->string ); +} +static void init_rate_limits( void ) { + SV_RateInit( &svs.ratelimit_status, sv_status_limit->string ); + SV_RateInit( &svs.ratelimit_auth, sv_auth_limit->string ); + SV_RateInit( &svs.ratelimit_rcon, sv_rcon_limit->string ); +} + +#if USE_SYSCON +static void sv_hostname_changed( cvar_t *self ) { + SV_SetConsoleTitle(); +} +#endif + +#if USE_ZLIB +voidpf SV_Zalloc OF(( voidpf opaque, uInt items, uInt size )) { + return SV_Malloc( items * size ); +} + +void SV_Zfree OF(( voidpf opaque, voidpf address )) { + Z_Free( address ); +} +#endif + +/* +=============== +SV_Init + +Only called at quake2.exe startup, not for each game +=============== +*/ +void SV_Init( void ) { + SV_InitOperatorCommands(); + +#if USE_MVD_SERVER + SV_MvdRegister(); +#endif + +#if USE_MVD_CLIENT + MVD_Register(); +#endif + +#if USE_AC_SERVER + AC_Register(); +#endif + + Cvar_Get( "protocol", va( "%i", PROTOCOL_VERSION_DEFAULT ), CVAR_SERVERINFO|CVAR_ROM ); + + Cvar_Get( "skill", "1", CVAR_LATCH ); + Cvar_Get( "deathmatch", "1", CVAR_SERVERINFO|CVAR_LATCH ); + Cvar_Get( "coop", "0", /*CVAR_SERVERINFO|*/CVAR_LATCH ); + Cvar_Get( "cheats", "0", CVAR_SERVERINFO|CVAR_LATCH ); + Cvar_Get( "dmflags", va( "%i", DF_INSTANT_ITEMS ), CVAR_SERVERINFO ); + Cvar_Get( "fraglimit", "0", CVAR_SERVERINFO ); + Cvar_Get( "timelimit", "0", CVAR_SERVERINFO ); + + sv_maxclients = Cvar_Get( "maxclients", "8", CVAR_SERVERINFO|CVAR_LATCH ); + sv_reserved_slots = Cvar_Get( "sv_reserved_slots", "0", CVAR_LATCH ); + sv_hostname = Cvar_Get( "hostname", "noname", CVAR_SERVERINFO|CVAR_ARCHIVE ); +#if USE_SYSCON + sv_hostname->changed = sv_hostname_changed; +#endif + sv_timeout = Cvar_Get( "timeout", "90", 0 ); + sv_zombietime = Cvar_Get( "zombietime", "2", 0 ); + sv_ghostime = Cvar_Get( "sv_ghostime", "6", 0 ); + sv_showclamp = Cvar_Get( "showclamp", "0", 0 ); + sv_enforcetime = Cvar_Get( "sv_enforcetime", "1", 0 ); + sv_allow_nodelta = Cvar_Get( "sv_allow_nodelta", "1", 0 ); +#if USE_FPS + sv_fps = Cvar_Get( "sv_fps", "10", CVAR_LATCH ); +#endif + sv_force_reconnect = Cvar_Get( "sv_force_reconnect", "", CVAR_LATCH ); + sv_show_name_changes = Cvar_Get( "sv_show_name_changes", "0", 0 ); + + sv_airaccelerate = Cvar_Get("sv_airaccelerate", "0", CVAR_LATCH); + sv_qwmod = Cvar_Get( "sv_qwmod", "0", CVAR_LATCH ); //atu QWMod + sv_public = Cvar_Get( "public", "0", CVAR_LATCH ); + sv_password = Cvar_Get( "sv_password", "", CVAR_PRIVATE ); + sv_reserved_password = Cvar_Get( "sv_reserved_password", "", CVAR_PRIVATE ); + sv_locked = Cvar_Get( "sv_locked", "0", 0 ); + sv_novis = Cvar_Get ("sv_novis", "0", 0); + sv_downloadserver = Cvar_Get( "sv_downloadserver", "", 0 ); + sv_redirect_address = Cvar_Get( "sv_redirect_address", "", 0 ); + +#ifdef _DEBUG + sv_debug = Cvar_Get( "sv_debug", "0", 0 ); + sv_pad_packets = Cvar_Get( "sv_pad_packets", "0", 0 ); +#endif + sv_lan_force_rate = Cvar_Get( "sv_lan_force_rate", "0", CVAR_LATCH ); + sv_calcpings_method = Cvar_Get( "sv_calcpings_method", "1", 0 ); + sv_changemapcmd = Cvar_Get( "sv_changemapcmd", "", 0 ); + + sv_strafejump_hack = Cvar_Get( "sv_strafejump_hack", "1", CVAR_LATCH ); + sv_waterjump_hack = Cvar_Get( "sv_waterjump_hack", "0", CVAR_LATCH ); + +#ifndef _WIN32 + sv_oldgame_hack = Cvar_Get( "sv_oldgame_hack", "0", CVAR_LATCH ); +#endif +#if USE_PACKETDUP + sv_packetdup_hack = Cvar_Get( "sv_packetdup_hack", "0", 0 ); +#endif + + sv_allow_map = Cvar_Get( "sv_allow_map", "0", 0 ); + +#if !USE_CLIENT + sv_recycle = Cvar_Get( "sv_recycle", "0", 0 ); +#endif + + sv_enhanced_setplayer = Cvar_Get( "sv_enhanced_setplayer", "0", 0 ); + + sv_iplimit = Cvar_Get( "sv_iplimit", "3", 0 ); + + sv_status_show = Cvar_Get( "sv_status_show", "2", 0 ); + + sv_status_limit = Cvar_Get( "sv_status_limit", "15", 0 ); + sv_status_limit->changed = sv_status_limit_changed; + + sv_uptime = Cvar_Get( "sv_uptime", "0", 0 ); + + sv_auth_limit = Cvar_Get( "sv_auth_limit", "1", 0 ); + sv_auth_limit->changed = sv_auth_limit_changed; + + sv_rcon_limit = Cvar_Get( "sv_rcon_limit", "1", 0 ); + sv_rcon_limit->changed = sv_rcon_limit_changed; + + Cvar_Get( "sv_features", va( "%d", SV_FEATURES ), CVAR_ROM ); + g_features = Cvar_Get( "g_features", "0", CVAR_ROM ); + + init_rate_limits(); + + // set up default pmove parameters + PmoveInit( &sv_pmp ); + +#if USE_SYSCON + SV_SetConsoleTitle(); +#endif +} + +/* +================== +SV_FinalMessage + +Used by SV_Shutdown 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. +================== +*/ +static void SV_FinalMessage( const char *message, int cmd ) { + client_t *client; + netchan_t *netchan; + int i; + + MSG_WriteByte( svc_print ); + MSG_WriteByte( PRINT_HIGH ); + MSG_WriteString( message ); + MSG_WriteByte( cmd ); + + // send it twice + // stagger the packets to crutch operating system limited buffers + for( i = 0; i < 2; i++ ) { + FOR_EACH_CLIENT( client ) { + if( client->state == cs_zombie ) { + continue; + } + netchan = client->netchan; + while( netchan->fragment_pending ) { + netchan->TransmitNextFragment( netchan ); + } + netchan->Transmit( netchan, msg_write.cursize, msg_write.data, 1 ); + } + } + + SZ_Clear( &msg_write ); + + // free any data dynamically allocated + FOR_EACH_CLIENT( client ) { + if( client->state != cs_zombie ) { + SV_CleanClient( client ); + } + SV_RemoveClient( client ); + } +} + + +/* +================ +SV_Shutdown + +Called when each game quits, from Com_Quit or Com_Error +================ +*/ +void SV_Shutdown( const char *finalmsg, killtype_t type ) { + master_t *m; + + Cvar_Set( "sv_running", "0" ); + Cvar_Set( "sv_paused", "0" ); + + if( !svs.initialized ) { +#if USE_MVD_CLIENT + MVD_Shutdown(); // make sure MVD client is down +#endif + return; + } + +#if USE_AC_SERVER + AC_Disconnect(); +#endif + +#if USE_MVD_SERVER + // shutdown MVD server + SV_MvdShutdown( type ); +#endif + + if( type == KILL_RESTART ) { + SV_FinalMessage( finalmsg, svc_reconnect ); + } else { + SV_FinalMessage( finalmsg, svc_disconnect ); + } + + SV_MasterShutdown(); + SV_ShutdownGameProgs(); + + // free current level + CM_FreeMap( &sv.cm ); + memset( &sv, 0, sizeof( sv ) ); + + // free server static data + Z_Free( svs.udp_client_pool ); + Z_Free( svs.entityStates ); +#if USE_ZLIB + deflateEnd( &svs.z ); +#endif + memset( &svs, 0, sizeof( svs ) ); + + // reset masters + FOR_EACH_MASTER( m ) { + m->last_ack = 0; + } + + // reset rate limits + init_rate_limits(); + + sv_client = NULL; + sv_player = NULL; + +#if USE_SYSCON + SV_SetConsoleTitle(); +#endif + + Z_LeakTest( TAG_SERVER ); +} + |