/* ===================== 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 -- SV_FinalCommand() will handle that ===================== */ void SV_DropClient( client_t *drop, const char *reason ) { if ( drop->state == clientState_t::CS_ZOMBIE ) { return; // already dropped } Log::Debug( "Going to CS_ZOMBIE for %s", drop->name ); drop->state = clientState_t::CS_ZOMBIE; // become free in a few seconds // call the prog function for removing a client // this will remove the body, among other things gvm.GameClientDisconnect( drop - svs.clients ); if ( SV_IsBot(drop) ) { SV_BotFreeClient( drop - svs.clients ); } else { // tell everyone why they got dropped // Gordon: we want this displayed elsewhere now SV_SendServerCommand( nullptr, "print %s\"^* \"%s\"\n\"", Cmd_QuoteString( drop->name ), Cmd_QuoteString( reason ) ); // add the disconnect command SV_SendServerCommand( drop, "disconnect %s\n", Cmd_QuoteString( reason ) ); } // nuke user info SV_SetUserinfo( drop - svs.clients, "" ); SV_FreeClient( drop ); // if this was the last client on the server, send a heartbeat // to the master so it is known the server is empty // send a heartbeat now so the master will get up to date info // if there is already a slot for this IP address, reuse it int i; for ( i = 0; i < sv_maxclients->integer; i++ ) { if ( svs.clients[ i ].state >= clientState_t::CS_CONNECTED ) { break; } } if ( i == sv_maxclients->integer ) { SV_Heartbeat_f(); } }
/* ================ SV_Shutdown Called when each game quits, before Sys_Quit or Sys_Error ================ */ void SV_Shutdown( const char *finalmsg ) { if ( !com_sv_running || !com_sv_running->integer ) { return; } PrintBanner(_( "Server Shutdown" )) NET_LeaveMulticast6(); if ( svs.clients && !com_errorEntered ) { SV_FinalCommand( va( "print %s", Cmd_QuoteString( finalmsg ) ), qtrue ); } SV_RemoveOperatorCommands(); SV_MasterShutdown(); SV_ShutdownGameProgs(); // free current level SV_ClearServer(); // free server static data if ( svs.clients ) { int index; for ( index = 0; index < sv_maxclients->integer; index++ ) { SV_FreeClient( &svs.clients[ index ] ); } //Z_Free( svs.clients ); free( svs.clients ); // RF, avoid trying to allocate large chunk on a fragmented zone } memset( &svs, 0, sizeof( svs ) ); svs.serverLoad = -1; Cvar_Set( "sv_running", "0" ); #ifndef DEDICATED NET_Config( qtrue ); #endif Com_Printf( "---------------------------\n" ); // disconnect any local clients CL_Disconnect( qfalse ); }
/* ================ SV_Shutdown Called when each game quits, before Sys_Quit or Sys_Error ================ */ void SV_Shutdown( const char *finalmsg ) { if ( !com_sv_running || !com_sv_running->integer ) { return; } PrintBanner( "Server Shutdown" ) NET_LeaveMulticast6(); if ( svs.clients ) { SV_FinalCommand( va( "print %s", Cmd_QuoteString( finalmsg ) ), true ); } SV_RemoveOperatorCommands(); SV_MasterShutdown(); SV_ShutdownGameProgs(); // free current level SV_ClearServer(); // free server static data if ( svs.clients ) { int index; for ( index = 0; index < sv_maxclients->integer; index++ ) { SV_FreeClient( &svs.clients[ index ] ); } free( svs.clients ); } memset( &svs, 0, sizeof( svs ) ); svs.serverLoad = -1; Cvar_Set( "sv_running", "0" ); #ifndef BUILD_SERVER NET_Config( true ); #endif Com_Printf( "---------------------------\n" ); // disconnect any local clients CL_Disconnect( false ); }
/* ============ Cvar_WriteVariables Appends lines containing "set variable value" for all variables with the archive flag set that are not in a transient state. ============ */ void Cvar_WriteVariables( fileHandle_t f ) { cvar_t *var; char buffer[ 1024 ]; for ( var = cvar_vars; var; var = var->next ) { if ( var->flags & CVAR_ARCHIVE ) { if( var->transient ) continue; // write the latched value, even if it hasn't taken effect yet Com_sprintf( buffer, sizeof( buffer ), "seta %s %s%s\n", var->name, Cmd_QuoteString( var->latchedString ? var->latchedString : var->string ), ( var->flags & CVAR_UNSAFE ) ? " unsafe" : "" ); FS_Printf( f, "%s", buffer ); } } }
/* =================== CL_GetServerCommand Set up argc/argv for the given command =================== */ qboolean CL_GetServerCommand( int serverCommandNumber ) { const char *s; char *cmd; static char bigConfigString[ BIG_INFO_STRING ]; int argc; // if we have irretrievably lost a reliable command, drop the connection if ( serverCommandNumber <= clc.serverCommandSequence - MAX_RELIABLE_COMMANDS ) { // when a demo record was started after the client got a whole bunch of // reliable commands then the client never got those first reliable commands if ( clc.demoplaying ) { return qfalse; } Com_Error( ERR_DROP, "CL_GetServerCommand: a reliable command was cycled out" ); } if ( serverCommandNumber > clc.serverCommandSequence ) { Com_Error( ERR_DROP, "CL_GetServerCommand: requested a command not received" ); } s = clc.serverCommands[ serverCommandNumber & ( MAX_RELIABLE_COMMANDS - 1 ) ]; clc.lastExecutedServerCommand = serverCommandNumber; if ( cl_showServerCommands->integer ) { // NERVE - SMF Com_Printf( "serverCommand: %i : %s\n", serverCommandNumber, s ); } rescan: Cmd_TokenizeString( s ); cmd = Cmd_Argv( 0 ); argc = Cmd_Argc(); if ( !strcmp( cmd, "disconnect" ) ) { // NERVE - SMF - allow server to indicate why they were disconnected if ( argc >= 2 ) { Com_Error( ERR_SERVERDISCONNECT, "Server Disconnected – %s", Cmd_Argv( 1 ) ); } else { Com_Error( ERR_SERVERDISCONNECT, "Server disconnected" ); } } if ( !strcmp( cmd, "bcs0" ) ) { Com_sprintf( bigConfigString, BIG_INFO_STRING, "cs %s %s", Cmd_Argv( 1 ), Cmd_QuoteString( Cmd_Argv( 2 ) ) ); return qfalse; } if ( !strcmp( cmd, "bcs1" ) ) { s = Cmd_QuoteString( Cmd_Argv( 2 ) ); if ( strlen( bigConfigString ) + strlen( s ) >= BIG_INFO_STRING ) { Com_Error( ERR_DROP, "bcs exceeded BIG_INFO_STRING" ); } strcat( bigConfigString, s ); return qfalse; } if ( !strcmp( cmd, "bcs2" ) ) { s = Cmd_QuoteString( Cmd_Argv( 2 ) ); if ( strlen( bigConfigString ) + strlen( s ) + 1 >= BIG_INFO_STRING ) { Com_Error( ERR_DROP, "bcs exceeded BIG_INFO_STRING" ); } strcat( bigConfigString, s ); strcat( bigConfigString, "\"" ); s = bigConfigString; goto rescan; } if ( !strcmp( cmd, "cs" ) ) { CL_ConfigstringModified(); // reparse the string, because CL_ConfigstringModified may have done another Cmd_TokenizeString() Cmd_TokenizeString( s ); return qtrue; } if ( !strcmp( cmd, "map_restart" ) ) { // clear notify lines and outgoing commands before passing // the restart to the cgame Con_ClearNotify(); memset( cl.cmds, 0, sizeof( cl.cmds ) ); return qtrue; } if ( !strcmp( cmd, "popup" ) ) { // direct server to client popup request, bypassing cgame // trap_UI_Popup(Cmd_Argv(1)); // if ( cls.state == CA_ACTIVE && !clc.demoplaying ) { // VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_CLIPBOARD); // Menus_OpenByName(Cmd_Argv(1)); // } return qfalse; } #ifdef USE_CRYPTO if ( cl_pubkeyID->integer && !strcmp( cmd, "pubkey_request" ) ) { char buffer[ MAX_STRING_CHARS ] = "pubkey "; mpz_get_str( buffer + 7, 16, public_key.n ); CL_AddReliableCommand( buffer ); return qfalse; } if ( cl_pubkeyID->integer && !strcmp( cmd, "pubkey_decrypt" ) ) { char buffer[ MAX_STRING_CHARS ] = "pubkey_identify "; unsigned int msg_len = MAX_STRING_CHARS - 16; mpz_t message; if ( argc == 1 ) { Com_Printf("%s", _( "^3Server sent a pubkey_decrypt command, but sent nothing to decrypt!\n" )); return qfalse; } mpz_init_set_str( message, Cmd_Argv( 1 ), 16 ); if ( rsa_decrypt( &private_key, &msg_len, ( unsigned char * ) buffer + 16, message ) ) { nettle_mpz_set_str_256_u( message, msg_len, ( unsigned char * ) buffer + 16 ); mpz_get_str( buffer + 16, 16, message ); CL_AddReliableCommand( buffer ); } mpz_clear( message ); return qfalse; } #endif // we may want to put a "connect to other server" command here // cgame can now act on the command return qtrue; }
void SV_UpdateConfigStrings() { int len, i, index; client_t *client; int maxChunkSize = MAX_STRING_CHARS - 64; for ( index = 0; index < MAX_CONFIGSTRINGS; index++ ) { if ( !sv.configstringsmodified[ index ] ) { continue; } sv.configstringsmodified[ index ] = false; // send it to all the clients if we aren't // spawning a new server if ( sv.state == SS_GAME || sv.restarting ) { // send the data to all relevent clients for ( i = 0, client = svs.clients; i < sv_maxclients->integer; i++, client++ ) { if ( client->state < CS_PRIMED ) { continue; } // do not always send server info to all clients if ( index == CS_SERVERINFO && client->gentity && ( client->gentity->r.svFlags & SVF_NOSERVERINFO ) ) { continue; } len = strlen( sv.configstrings[ index ] ); if ( len >= maxChunkSize ) { int sent = 0; int remaining = len; const char *cmd; char buf[ MAX_STRING_CHARS ]; while ( remaining > 0 ) { if ( sent == 0 ) { cmd = "bcs0"; } else if ( remaining < maxChunkSize ) { cmd = "bcs2"; } else { cmd = "bcs1"; } Q_strncpyz( buf, &sv.configstrings[ index ][ sent ], maxChunkSize ); SV_SendServerCommand( client, "%s %i %s\n", cmd, index, Cmd_QuoteString( buf ) ); sent += ( maxChunkSize - 1 ); remaining -= ( maxChunkSize - 1 ); } } else { // standard cs, just send it SV_SendServerCommand( client, "cs %i %s\n", index, Cmd_QuoteString( sv.configstrings[ index ] ) ); } } } } }
/* ============ Cmd_QuoteStringBuffer Cmd_QuoteString for VM usage ============ */ void Cmd_QuoteStringBuffer( const char *in, char *buffer, int size ) { Q_strncpyz( buffer, Cmd_QuoteString( in ), size ); }
/* =================== CL_HandleServerCommand CL_GetServerCommand =================== */ bool CL_HandleServerCommand(Str::StringRef text, std::string& newText) { static char bigConfigString[ BIG_INFO_STRING ]; Cmd::Args args(text); if (args.Argc() == 0) { return false; } auto cmd = args.Argv(0); int argc = args.Argc(); if (cmd == "disconnect") { // NERVE - SMF - allow server to indicate why they were disconnected if (argc >= 2) { Com_Error(errorParm_t::ERR_SERVERDISCONNECT, "Server disconnected: %s", args.Argv(1).c_str()); } else { Com_Error(errorParm_t::ERR_SERVERDISCONNECT, "Server disconnected"); } } // bcs0 to bcs2 are used by the server to send info strings that are bigger than the size of a packet. // See also SV_UpdateConfigStrings // bcs0 starts a new big config string // bcs1 continues it // bcs2 finishes it and feeds it back as a new command sent by the server (bcs0 makes it a cs command) if (cmd == "bcs0") { if (argc >= 3) { Com_sprintf(bigConfigString, BIG_INFO_STRING, "cs %s %s", args.Argv(1).c_str(), args.EscapedArgs(2).c_str()); } return false; } if (cmd == "bcs1") { if (argc >= 3) { const char* s = Cmd_QuoteString( args[2].c_str() ); if (strlen(bigConfigString) + strlen(s) >= BIG_INFO_STRING) { Com_Error(errorParm_t::ERR_DROP, "bcs exceeded BIG_INFO_STRING"); } Q_strcat(bigConfigString, sizeof(bigConfigString), s); } return false; } if (cmd == "bcs2") { if (argc >= 3) { const char* s = Cmd_QuoteString( args[2].c_str() ); if (strlen(bigConfigString) + strlen(s) + 1 >= BIG_INFO_STRING) { Com_Error(errorParm_t::ERR_DROP, "bcs exceeded BIG_INFO_STRING"); } Q_strcat(bigConfigString, sizeof(bigConfigString), s); Q_strcat(bigConfigString, sizeof(bigConfigString), "\""); newText = bigConfigString; return CL_HandleServerCommand(bigConfigString, newText); } return false; } if (cmd == "cs") { CL_ConfigstringModified(args); return true; } if (cmd == "map_restart") { // clear outgoing commands before passing // the restart to the cgame memset(cl.cmds, 0, sizeof(cl.cmds)); return true; } if (cmd == "popup") { // direct server to client popup request, bypassing cgame if (cls.state == connstate_t::CA_ACTIVE && !clc.demoplaying && argc >=1) { // TODO: Pass to the cgame } return false; } if (cmd == "pubkey_decrypt") { char buffer[ MAX_STRING_CHARS ] = "pubkey_identify "; NettleLength msg_len = MAX_STRING_CHARS - 16; mpz_t message; if (argc == 1) { Log::Notice("^3Server sent a pubkey_decrypt command, but sent nothing to decrypt!\n"); return false; } mpz_init_set_str(message, args.Argv(1).c_str(), 16); if (rsa_decrypt(&private_key, &msg_len, (unsigned char *) buffer + 16, message)) { nettle_mpz_set_str_256_u(message, msg_len, (unsigned char *) buffer + 16); mpz_get_str(buffer + 16, 16, message); CL_AddReliableCommand(buffer); } mpz_clear(message); return false; } return true; }