/* ================== Svcmd_KickNum_f Kick a user off of the server ================== */ static void Svcmd_KickNum_f( void ) { gclient_t *cl; int timeout = -1; const char *ip; char userinfo[MAX_INFO_STRING]; char sTimeout[MAX_TOKEN_CHARS]; char name[MAX_TOKEN_CHARS]; int clientNum; // make sure server is running if ( !G_Is_SV_Running() ) { G_Printf( "Server is not running.\n" ); return; } if ( trap_Argc() < 2 || trap_Argc() > 3 ) { G_Printf( "Usage: kick <client number> [timeout]\n" ); return; } if ( trap_Argc() == 3 ) { trap_Argv( 2, sTimeout, sizeof( sTimeout ) ); timeout = atoi( sTimeout ); } else { timeout = 300; } trap_Argv( 1, name, sizeof( name ) ); clientNum = atoi( name ); cl = G_GetPlayerByNum( clientNum ); if ( !cl ) { return; } if ( cl->pers.localClient ) { G_Printf( "Cannot kick host player\n" ); return; } trap_GetUserinfo( cl->ps.clientNum, userinfo, sizeof( userinfo ) ); ip = Info_ValueForKey( userinfo, "ip" ); // use engine banning system, mods may choose to use their own banlist if ( USE_ENGINE_BANLIST ) { // kick but dont ban bots, they arent that lame if ( ( g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT ) ) { timeout = 0; } trap_DropClient( cl->ps.clientNum, "player kicked", timeout ); } else { trap_DropClient( cl->ps.clientNum, "player kicked", 0 ); // kick but dont ban bots, they arent that lame if ( !( g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT ) ) { AddIPBan( ip ); } } }
/* * TVM_ClientUserInfoChanged * * called whenever the player updates a userinfo variable. * * The game can override any of the settings in place * (forcing skins or names, etc) before copying it off. */ void TVM_ClientUserinfoChanged( tvm_relay_t *relay, edict_t *ent, char *userinfo ) { gclient_t *cl; char *s; assert( ent && ent->local && ent->r.client ); cl = ent->r.client; // check for malformed or illegal info strings if( !Info_Validate( userinfo ) ) { trap_DropClient( relay, PLAYERNUM( ent ), DROP_TYPE_GENERAL, "Error: Invalid userinfo" ); return; } if( !Info_ValueForKey( userinfo, "name" ) ) { trap_DropClient( relay, PLAYERNUM( ent ), DROP_TYPE_GENERAL, "Error: No name set" ); return; } // set name, it's validated and possibly changed first Q_strncpyz( cl->pers.netname, Info_ValueForKey( userinfo, "name" ), sizeof( cl->pers.netname ) ); // fov s = Info_ValueForKey( userinfo, "fov" ); if( !s ) { cl->pers.fov = 100; } else { cl->pers.fov = atoi( s ); clamp( cl->pers.fov, 1, 140 ); } s = Info_ValueForKey( userinfo, "zoomfov" ); if( !s ) { cl->pers.zoomfov = 30; } else { cl->pers.zoomfov = atoi( s ); clamp( cl->pers.zoomfov, 1, 60 ); } // save off the userinfo in case we want to check something later Q_strncpyz( cl->pers.userinfo, userinfo, sizeof( cl->pers.userinfo ) ); }
static void Svcmd_EjectClient_f( void ) { char *reason, name[ MAX_STRING_CHARS ]; if ( trap_Argc() < 2 ) { G_Printf( "usage: eject <player|-1> <reason>\n" ); return; } trap_Argv( 1, name, sizeof( name ) ); reason = ConcatArgs( 2 ); if ( atoi( name ) == -1 ) { int i; for ( i = 0; i < level.maxclients; i++ ) { if ( level.clients[ i ].pers.connected == CON_DISCONNECTED ) { continue; } if ( level.clients[ i ].pers.localClient ) { continue; } trap_DropClient( i, reason, 0 ); } } else { gclient_t *cl = ClientForString( name ); if ( !cl ) { return; } if ( cl->pers.localClient ) { G_Printf( "eject: cannot eject local clients\n" ); return; } trap_DropClient( cl - level.clients, reason, 0 ); } }
/* =============== G_BotConnect =============== */ qboolean G_BotConnect( int clientNum, qboolean restart ) { bot_settings_t settings; char userinfo[MAX_INFO_STRING]; trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) ); Q_strncpyz( settings.characterfile, Info_ValueForKey( userinfo, "characterfile" ), sizeof(settings.characterfile) ); settings.skill = atoi( Info_ValueForKey( userinfo, "skill" ) ); Q_strncpyz( settings.team, Info_ValueForKey( userinfo, "team" ), sizeof(settings.team) ); Q_strncpyz( settings.pclass, Info_ValueForKey( userinfo, "class" ), sizeof(settings.pclass) ); if (!BotAISetupClient( clientNum, &settings )) { trap_DropClient( clientNum, "BotAISetupClient failed" ); return qfalse; } if( restart && g_gametype.integer == GT_SINGLE_PLAYER ) { g_entities[clientNum].botDelayBegin = qtrue; } else { g_entities[clientNum].botDelayBegin = qfalse; } return qtrue; }
/* ================= ClientInactivityTimer Returns qfalse if the client is dropped ================= */ qboolean ClientInactivityTimer( gclient_t *client ) { if ( !g_inactivity.integer ) { // give everyone some time, so if the operator sets g_inactivity during // gameplay, everyone isn't kicked client->inactivityTime = level.time + 60 * 1000; client->inactivityWarning = qfalse; } else if ( client->pers.cmd.forwardmove || client->pers.cmd.rightmove || client->pers.cmd.upmove || ( client->pers.cmd.wbuttons & WBUTTON_ATTACK2 ) || ( client->pers.cmd.buttons & BUTTON_ATTACK ) ) { client->inactivityTime = level.time + g_inactivity.integer * 1000; client->inactivityWarning = qfalse; } else if ( !client->pers.localClient ) { if ( level.time > client->inactivityTime ) { trap_DropClient( client - level.clients, "Dropped due to inactivity" ); return qfalse; } if ( level.time > client->inactivityTime - 10000 && !client->inactivityWarning ) { client->inactivityWarning = qtrue; trap_SendServerCommand( client - level.clients, "cp \"Ten seconds until inactivity drop!\n\"" ); } } return qtrue; }
void G_globalAction(gentity_t *ent, gentity_t *vic, globalType_t type, char *reason) { if (!G_isPlayerConnected(vic)) return; switch (type) { case GLOBAL_BAN: trap_DropClient(vic->client->ps.clientNum, va("banned by %s^7, reason: %s", (ent) ? ent->client->pers.netname : "console", (*reason) ? reason : "banned by admin")); break; case GLOBAL_DENYBUILD: vic->client->pers.denyBuild = qtrue; break; case GLOBAL_FORCESPEC: vic->client->pers.forcespec = qtrue; break; case GLOBAL_HANDICAP: vic->client->pers.handicap = qtrue; break; case GLOBAL_MUTE: vic->client->pers.muted = qtrue; break; case GLOBAL_NONE: //uh? break; } }
/* ================== Svcmd_KickNum_f Joe Kari: there was in server/sv_ccmds.c a note that mention "FIXME: move to game", so I move it to game... Additionnaly, you can now provide a reason for kicking someone. * kicknum <client number> [reasons] ================== */ static void Svcmd_KickNum_f( void ) { char arg[MAX_TOKEN_CHARS]; int clientNum; gclient_t *cl; char *reason; if ( trap_Argc() < 2 ) { G_Printf ( "Usage: kicknum <client number> [reasons]\n"); return; } trap_Argv( 1, arg, sizeof( arg ) ); clientNum = atoi( arg ); if ( clientNum >= level.maxclients || clientNum < 0 ) { Com_Printf("client not found\n"); return; } cl = &level.clients[ clientNum ] ; if ( cl->pers.connected != CON_CONNECTED ) { Com_Printf("client not found\n"); return; } if ( cl->pers.localClient ) { Com_Printf("Cannot kick host player\n"); return; } if ( trap_Argc() > 2 ) { Q_strncpyz ( arg, "was kicked: ", sizeof(arg)); reason = ConcatArgs( 2 ) ; Q_strcat( arg, sizeof(arg), reason); PushMinilogf( "KICK: %i > %s", clientNum, reason ) ; trap_DropClient( clientNum, arg ); } else { PushMinilogf( "KICK: %i", clientNum ) ; trap_DropClient( clientNum, "was kicked" ); } /* FIXME? adapt this part of the server code to the game code? cl->lastPacketTime = svs.time; // in case there is a funny zombie */ }
// *** Player Kick *** int G_Kick_v( gentity_t *ent, unsigned int dwVoteIndex, char *arg, char *arg2, qboolean fRefereeCmd ) { // Vote request (vote is being initiated) if( arg ) { int pid; if( !vote_allow_kick.integer && ent && !ent->client->sess.referee ) { G_voteDisableMessage(ent, arg); return G_INVALID; } else if( G_voteDescription(ent, fRefereeCmd, dwVoteIndex) ) { return G_INVALID; } else if( ( pid = ClientNumberFromString( ent, arg2 ) ) == -1 ) { return G_INVALID; } if( level.clients[ pid ].sess.referee ) { G_refPrintf( ent, "Can't vote to kick referees!" ); return G_INVALID; } if(G_shrubbot_permission(&g_entities[pid], SBF_IMMUNITY)) { G_refPrintf( ent, "Can't vote to kick admins!" ); return G_INVALID; } // pheno: prevent ettv slaves from being callvote kicked if( level.clients[pid].sess.ettv && ( g_ettvFlags.integer & ETTV_IMMUNITY ) ) { G_refPrintf( ent, "Can't vote to kick ettv slaves!" ); return G_INVALID; } if( !fRefereeCmd && ent ) { if( level.clients[ pid ].sess.sessionTeam != TEAM_SPECTATOR && level.clients[ pid ].sess.sessionTeam != ent->client->sess.sessionTeam ) { G_refPrintf( ent, "Can't vote to kick players on opposing team!" ); return G_INVALID; } } Com_sprintf( level.voteInfo.vote_value, VOTE_MAXSTRING, "%d", pid ); Com_sprintf( arg2, VOTE_MAXSTRING, "%s^7", level.clients[pid].pers.netname ); // Vote action (vote has passed) } else { // Kick a player //trap_SendConsoleCommand( EXEC_APPEND, va( "clientkick %d\n", atoi( level.voteInfo.vote_value ) ) ); // tjw: clientkick doesn't work in 2.60 trap_DropClient(atoi(level.voteInfo.vote_value), "You have been kicked", 120); AP( va( "cp \"%s\n^3has been kicked!\n\"", level.clients[ atoi( level.voteInfo.vote_value ) ].pers.netname ) ); } return G_OK; }
/* =============== G_BotConnect =============== */ qboolean G_BotConnect( int clientNum, qboolean restart ) { bot_settings_t settings; char userinfo[MAX_INFO_STRING]; trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) ); Q_strncpyz( settings.characterfile, Info_ValueForKey( userinfo, "characterfile" ), sizeof(settings.characterfile) ); settings.skill = atof( Info_ValueForKey( userinfo, "skill" ) ); if (!BotAISetupClient( clientNum, &settings, restart )) { trap_DropClient( clientNum, "BotAISetupClient failed" ); return qfalse; } return qtrue; }
/* ================ respawn ================ */ void respawn( gentity_t *ent ) { gentity_t *tent; //kick fragged bots from game if (IsBot(ent->client->ps.clientNum) ) { trap_DropClient( ent->client->ps.clientNum, "" ); G_EndWave(); //check if the wave was won and handle it return; } CopyToBodyQue (ent); ClientSpawn(ent); // add a teleportation effect tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_IN ); tent->s.clientNum = ent->s.clientNum; }
/* * G_CheckNumBots */ static void G_CheckNumBots( void ) { edict_t *ent; int desiredNumBots; if( level.spawnedTimeStamp + 5000 > game.realtime ) return; // check sanity of g_numbots if( g_numbots->integer < 0 ) trap_Cvar_Set( "g_numbots", "0" ); if( g_numbots->integer > gs.maxclients ) trap_Cvar_Set( "g_numbots", va( "%i", gs.maxclients ) ); if( level.gametype.numBots > gs.maxclients ) level.gametype.numBots = gs.maxclients; desiredNumBots = level.gametype.numBots ? level.gametype.numBots : g_numbots->integer; if( desiredNumBots < game.numBots ) { // kick one bot for( ent = game.edicts + gs.maxclients; PLAYERNUM( ent ) >= 0; ent-- ) { if( !ent->r.inuse || !( ent->r.svflags & SVF_FAKECLIENT ) ) continue; if( AI_GetType( ent->ai ) == AI_ISBOT ) { trap_DropClient( ent, DROP_TYPE_GENERAL, NULL ); break; } } return; } if( desiredNumBots > game.numBots ) { // add a bot if there is room for( ent = game.edicts + 1; PLAYERNUM( ent ) < gs.maxclients && game.numBots < desiredNumBots; ent++ ) { if( !ent->r.inuse && trap_GetClientState( PLAYERNUM( ent ) ) == CS_FREE ) BOT_SpawnBot( NULL ); } } }
/* * Cmd_ConsoleKick_f */ static void Cmd_ConsoleKick_f( void ) { edict_t *ent; if( trap_Cmd_Argc() != 2 ) { Com_Printf( "Usage: kick <id or name>\n" ); return; } ent = G_PlayerForText( trap_Cmd_Argv( 1 ) ); if( !ent ) { Com_Printf( "No such player\n" ); return; } trap_DropClient( ent, DROP_TYPE_NORECONNECT, "Kicked" ); }
qboolean G_CensorName(char *testname, char *userinfo, int clientNum) { char censoredName[MAX_NETNAME]; char name[MAX_NETNAME]; Q_strncpyz(name, testname, sizeof(name)); SanitizeString(name, censoredName, qtrue); if (G_CensorText(censoredName, &censorNamesDictionary)) { Q_strncpyz(testname, censoredName, sizeof(censoredName)); if (g_censorPenalty.integer & CNSRPNLTY_KICK) { trap_DropClient(clientNum, va("Name censor: Please change your name."), 0); return qtrue; } } return qfalse; }
/* =============== G_BotConnect =============== */ qboolean G_BotConnect( int clientNum, qboolean restart ) { bot_settings_t settings; char userinfo[MAX_INFO_STRING]; trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) ); Q_strncpyz( settings.personalityfile, Info_ValueForKey( userinfo, "personality" ), sizeof(settings.personalityfile) ); settings.skill = atof( Info_ValueForKey( userinfo, "skill" ) ); Q_strncpyz( settings.team, Info_ValueForKey( userinfo, "team" ), sizeof(settings.team) ); //[TABBot] settings.botType = atoi( Info_ValueForKey( userinfo, "bottype" ) ); //[/TABBot] if (!BotAISetupClient( clientNum, &settings, restart )) { trap_DropClient( clientNum, "BotAISetupClient failed" ); return qfalse; } return qtrue; }
//========================================== // BOT_RemoveBot // Remove a bot by name or all bots //========================================== void BOT_RemoveBot( const char *name ) { int i; bool freed = false; edict_t *ent; for( i = 0, ent = game.edicts + 1; i < gs.maxclients; i++, ent++ ) { if( !ent->r.inuse || AI_GetType( ent->ai ) != AI_ISBOT ) continue; if( !Q_stricmp( ent->r.client->netname, name ) || !Q_stricmp( name, "all" ) ) { trap_DropClient( ent, DROP_TYPE_GENERAL, NULL ); freed = true; } } if( !freed && Q_stricmp( name, "all" ) ) G_Printf( "BOT: %s not found\n", name ); }
void G_BotDel( int clientNum ) { gentity_t *bot = &g_entities[clientNum]; char userinfo[MAX_INFO_STRING]; const char *autoname; if ( !( bot->r.svFlags & SVF_BOT ) || !bot->botMind ) { Log::Warn( "'^7%s^7' is not a bot", bot->client->pers.netname ); return; } trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); autoname = Info_ValueForKey( userinfo, "autoname" ); if ( autoname && *autoname ) { G_BotNameUsed( BotGetEntityTeam( bot ), autoname, false ); } trap_SendServerCommand( -1, va( "print_tr %s %s", QQ( N_( "$1$^7 disconnected" ) ), Quote( bot->client->pers.netname ) ) ); trap_DropClient( clientNum, "disconnected" ); }
/* =========== ClientUserInfoChanged Called from ClientConnect when the player first connects and directly by the server system when the player updates a userinfo variable. The game can override any of the settings and call trap_SetUserinfo if desired. ============ */ char *ClientUserinfoChanged( int clientNum, qboolean forceName ) { gentity_t *ent; char *s; char model[ MAX_QPATH ]; char buffer[ MAX_QPATH ]; char filename[ MAX_QPATH ]; char oldname[ MAX_NAME_LENGTH ]; char newname[ MAX_NAME_LENGTH ]; char err[ MAX_STRING_CHARS ]; qboolean revertName = qfalse; gclient_t *client; char userinfo[ MAX_INFO_STRING ]; ent = g_entities + clientNum; client = ent->client; trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); // check for malformed or illegal info strings if( !Info_Validate(userinfo) ) { trap_SendServerCommand( ent - g_entities, "disconnect \"illegal or malformed userinfo\n\"" ); trap_DropClient( ent - g_entities, "dropped: illegal or malformed userinfo"); return "Illegal or malformed userinfo"; } // If their userinfo overflowed, tremded is in the process of disconnecting them. // If we send our own disconnect, it won't work, so just return to prevent crashes later // in this function. This check must come after the Info_Validate call. else if( !userinfo[ 0 ] ) return "Empty (overflowed) userinfo"; // stickyspec toggle s = Info_ValueForKey( userinfo, "cg_stickySpec" ); client->pers.stickySpec = atoi( s ) != 0; // set name Q_strncpyz( oldname, client->pers.netname, sizeof( oldname ) ); s = Info_ValueForKey( userinfo, "name" ); G_ClientCleanName( s, newname, sizeof( newname ) ); if( strcmp( oldname, newname ) ) { if( !forceName && client->pers.namelog->nameChangeTime && level.time - client->pers.namelog->nameChangeTime <= g_minNameChangePeriod.value * 1000 ) { trap_SendServerCommand( ent - g_entities, va( "print \"Name change spam protection (g_minNameChangePeriod = %d)\n\"", g_minNameChangePeriod.integer ) ); revertName = qtrue; } else if( !forceName && g_maxNameChanges.integer > 0 && client->pers.namelog->nameChanges >= g_maxNameChanges.integer ) { trap_SendServerCommand( ent - g_entities, va( "print \"Maximum name changes reached (g_maxNameChanges = %d)\n\"", g_maxNameChanges.integer ) ); revertName = qtrue; } else if( !forceName && client->pers.namelog->muted ) { trap_SendServerCommand( ent - g_entities, "print \"You cannot change your name while you are muted\n\"" ); revertName = qtrue; } else if( !G_admin_name_check( ent, newname, err, sizeof( err ) ) ) { trap_SendServerCommand( ent - g_entities, va( "print \"%s\n\"", err ) ); revertName = qtrue; } if( revertName ) { Q_strncpyz( client->pers.netname, *oldname ? oldname : "UnnamedPlayer", sizeof( client->pers.netname ) ); Info_SetValueForKey( userinfo, "name", oldname ); trap_SetUserinfo( clientNum, userinfo ); } else { G_CensorString( client->pers.netname, newname, sizeof( client->pers.netname ), ent ); if( !forceName && client->pers.connected == CON_CONNECTED ) { client->pers.namelog->nameChangeTime = level.time; client->pers.namelog->nameChanges++; } if( *oldname ) { G_LogPrintf( "ClientRename: %i [%s] (%s) \"%s^7\" -> \"%s^7\" \"%c%s%c^7\"\n", clientNum, client->pers.ip.str, client->pers.guid, oldname, client->pers.netname, DECOLOR_OFF, client->pers.netname, DECOLOR_ON ); } } G_namelog_update_name( client ); } if( client->pers.classSelection == PCL_NONE ) { //This looks hacky and frankly it is. The clientInfo string needs to hold different //model details to that of the spawning class or the info change will not be //registered and an axis appears instead of the player model. There is zero chance //the player can spawn with the battlesuit, hence this choice. Com_sprintf( buffer, MAX_QPATH, "%s/%s", BG_ClassConfig( PCL_HUMAN_BSUIT )->modelName, BG_ClassConfig( PCL_HUMAN_BSUIT )->skinName ); } else { Com_sprintf( buffer, MAX_QPATH, "%s/%s", BG_ClassConfig( client->pers.classSelection )->modelName, BG_ClassConfig( client->pers.classSelection )->skinName ); //model segmentation Com_sprintf( filename, sizeof( filename ), "models/players/%s/animation.cfg", BG_ClassConfig( client->pers.classSelection )->modelName ); if( G_NonSegModel( filename ) ) client->ps.persistant[ PERS_STATE ] |= PS_NONSEGMODEL; else client->ps.persistant[ PERS_STATE ] &= ~PS_NONSEGMODEL; } Q_strncpyz( model, buffer, sizeof( model ) ); // wallwalk follow s = Info_ValueForKey( userinfo, "cg_wwFollow" ); if( atoi( s ) ) client->ps.persistant[ PERS_STATE ] |= PS_WALLCLIMBINGFOLLOW; else client->ps.persistant[ PERS_STATE ] &= ~PS_WALLCLIMBINGFOLLOW; // wallwalk toggle s = Info_ValueForKey( userinfo, "cg_wwToggle" ); if( atoi( s ) ) client->ps.persistant[ PERS_STATE ] |= PS_WALLCLIMBINGTOGGLE; else client->ps.persistant[ PERS_STATE ] &= ~PS_WALLCLIMBINGTOGGLE; // always sprint s = Info_ValueForKey( userinfo, "cg_sprintToggle" ); if( atoi( s ) ) client->ps.persistant[ PERS_STATE ] |= PS_SPRINTTOGGLE; else client->ps.persistant[ PERS_STATE ] &= ~PS_SPRINTTOGGLE; // fly speed s = Info_ValueForKey( userinfo, "cg_flySpeed" ); if( *s ) client->pers.flySpeed = atoi( s ); else client->pers.flySpeed = BG_Class( PCL_NONE )->speed; // disable blueprint errors s = Info_ValueForKey( userinfo, "cg_disableBlueprintErrors" ); if( atoi( s ) ) client->pers.disableBlueprintErrors = qtrue; else client->pers.disableBlueprintErrors = qfalse; // teamInfo s = Info_ValueForKey( userinfo, "teamoverlay" ); if( atoi( s ) != 0 ) { // teamoverlay was enabled so we need an update if( client->pers.teamInfo == 0 ) client->pers.teamInfo = 1; } else client->pers.teamInfo = 0; s = Info_ValueForKey( userinfo, "cg_unlagged" ); if( !s[0] || atoi( s ) != 0 ) client->pers.useUnlagged = qtrue; else client->pers.useUnlagged = qfalse; Q_strncpyz( client->pers.voice, Info_ValueForKey( userinfo, "voice" ), sizeof( client->pers.voice ) ); // send over a subset of the userinfo keys so other clients can // print scoreboards, display models, and play custom sounds Com_sprintf( userinfo, sizeof( userinfo ), "n\\%s\\t\\%i\\model\\%s\\ig\\%16s\\v\\%s", client->pers.netname, client->pers.teamSelection, model, Com_ClientListString( &client->sess.ignoreList ), client->pers.voice ); trap_SetConfigstring( CS_PLAYERS + clientNum, userinfo ); /*G_LogPrintf( "ClientUserinfoChanged: %i %s\n", clientNum, userinfo );*/ return NULL; }
/* =========== ClientConnect Called when a player begins connecting to the server. Called again for every map change or tournement restart. The session information will be valid after exit. Return NULL if the client should be allowed, otherwise return a string with the reason for denial. Otherwise, the client will be sent the current gamestate and will eventually get to ClientBegin. firstTime will be qtrue the very first time a client connects to the server machine, but qfalse on map changes and tournement restarts. ============ */ char *ClientConnect( int clientNum, qboolean firstTime ) { char *value; char *userInfoError; gclient_t *client; char userinfo[ MAX_INFO_STRING ]; gentity_t *ent; char reason[ MAX_STRING_CHARS ] = {""}; int i; ent = &g_entities[ clientNum ]; client = &level.clients[ clientNum ]; // ignore if client already connected if( client->pers.connected != CON_DISCONNECTED ) return NULL; ent->client = client; memset( client, 0, sizeof( *client ) ); trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); value = Info_ValueForKey( userinfo, "cl_guid" ); Q_strncpyz( client->pers.guid, value, sizeof( client->pers.guid ) ); value = Info_ValueForKey( userinfo, "ip" ); // check for local client if( !strcmp( value, "localhost" ) ) client->pers.localClient = qtrue; G_AddressParse( value, &client->pers.ip ); client->pers.admin = G_admin_admin( client->pers.guid ); // check for admin ban if( G_admin_ban_check( ent, reason, sizeof( reason ) ) ) { return va( "%s", reason ); } // check for a password value = Info_ValueForKey( userinfo, "password" ); if( g_password.string[ 0 ] && Q_stricmp( g_password.string, "none" ) && strcmp( g_password.string, value ) != 0 ) return "Invalid password"; // add guid to session so we don't have to keep parsing userinfo everywhere for( i = 0; i < sizeof( client->pers.guid ) - 1 && isxdigit( client->pers.guid[ i ] ); i++ ); if( i < sizeof( client->pers.guid ) - 1 ) return "Invalid GUID"; for( i = 0; i < level.maxclients; i++ ) { if( level.clients[ i ].pers.connected == CON_DISCONNECTED ) continue; if( !Q_stricmp( client->pers.guid, level.clients[ i ].pers.guid ) ) { if( !G_ClientIsLagging( level.clients + i ) ) { trap_SendServerCommand( i, "cp \"Your GUID is not secure\"" ); return "Duplicate GUID"; } trap_DropClient( i, "Ghost" ); } } client->pers.connected = CON_CONNECTING; // read or initialize the session data if( firstTime || level.newSession ) G_InitSessionData( client, userinfo ); G_ReadSessionData( client ); // get and distribute relevent paramters G_namelog_connect( client ); userInfoError = ClientUserinfoChanged( clientNum, qfalse ); if( userInfoError != NULL ) return userInfoError; G_LogPrintf( "ClientConnect: %i [%s] (%s) \"%s^7\" \"%c%s%c^7\"\n", clientNum, client->pers.ip.str, client->pers.guid, client->pers.netname, DECOLOR_OFF, client->pers.netname, DECOLOR_ON ); // don't do the "xxx connected" messages if they were caried over from previous level if( firstTime ) trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " connected\n\"", client->pers.netname ) ); if( client->pers.admin ) G_admin_authlog( ent ); // count current clients and rank for scoreboard CalculateRanks( ); // if this is after !restart keepteams or !restart switchteams, apply said selection if ( client->sess.restartTeam != TEAM_NONE ) { G_ChangeTeam( ent, client->sess.restartTeam ); client->sess.restartTeam = TEAM_NONE; } return NULL; }
/** * Validate userinfo string * @autor: Nico */ static qboolean checkUserinfoString(int clientNum, char *userinfo) { size_t len = 0; int count = 0; int i = 0; char *ip = NULL; char parsedIp[MAX_QPATH] = { 0 }; char *name = NULL; // check for malformed or illegal info strings if (!Info_Validate(userinfo)) { // Nico, drop the client G_LogPrintf("Dropping client %d: forbidden character in userinfo\n", clientNum); trap_DropClient(clientNum, "^1You were kicked because of forbidden character in userinfo", 0); return qfalse; } // Nico, check userinfo length (from combinedfixes) len = strlen(userinfo); if (len > MAX_INFO_STRING - 44) { G_LogPrintf("Dropping client %d: oversized userinfo\n", clientNum); trap_DropClient(clientNum, "^1You were kicked because of oversized userinfo", 0); return qfalse; } // Nico, check userinfo leading backslash (from combinedfixes) if (userinfo[0] != '\\') { G_LogPrintf("Dropping client %d: malformed userinfo (missing leading backslash)\n", clientNum); trap_DropClient(clientNum, "^1You were kicked because of malformed userinfo", 0); return qfalse; } // Nico, check userinfo trailing backslash (from combinedfixes) if (len > 0 && userinfo[len - 1] == '\\') { G_LogPrintf("Dropping client %d: malformed userinfo (trailing backslash)\n", clientNum); trap_DropClient(clientNum, "^1You were kicked because of malformed userinfo", 0); return qfalse; } // Nico, make sure backslah number is even (from combinedfixes) for (i = 0; i < (int)len; ++i) { if (userinfo[i] == '\\') { count++; } } if (count % 2 != 0) { G_LogPrintf("Dropping client %d: malformed userinfo (odd number of backslash)\n", clientNum); trap_DropClient(clientNum, "^1You were kicked because of malformed userinfo", 0); return qfalse; } // Nico, make sure client ip is not empty or malformed (from combinedfixes) ip = Info_ValueForKey(userinfo, "ip"); if (!strcmp(ip, "") || !getParsedIp(ip, parsedIp)) { G_LogPrintf("Dropping client %d: malformed userinfo (empty or malformed ip)\n", clientNum); trap_DropClient(clientNum, "^1You were kicked because of malformed userinfo", 0); return qfalse; } // Nico, make sure client name is not empty (from combinedfixes) name = Info_ValueForKey(userinfo, "name"); if (!strcmp(name, "")) { G_LogPrintf("Dropping client %d: malformed userinfo (empty name)\n", clientNum); trap_DropClient(clientNum, "^1You were kicked because of malformed userinfo", 0); return qfalse; } // Nico, one ip in userinfo (from ETpub) count = 0; if (len > 4) { for (i = 0; userinfo[i + 3]; ++i) { if (userinfo[i] == '\\' && userinfo[i + 1] == 'i' && userinfo[i + 2] == 'p' && userinfo[i + 3] == '\\') { count++; } } } if (count > 1) { G_LogPrintf("Dropping client %d: malformed userinfo (too many IP fields)\n", clientNum); trap_DropClient(clientNum, "^1You were kicked because of malformed userinfo", 0); return qfalse; } // Nico, one cl_guid in userinfo (from ETpub) count = 0; if (len > 9) { for (i = 0; userinfo[i + 8]; ++i) { if (userinfo[i] == '\\' && userinfo[i + 1] == 'c' && userinfo[i + 2] == 'l' && userinfo[i + 3] == '_' && userinfo[i + 4] == 'g' && userinfo[i + 5] == 'u' && userinfo[i + 6] == 'i' && userinfo[i + 7] == 'd' && userinfo[i + 8] == '\\') { count++; } } } if (count > 1) { G_LogPrintf("Dropping client %d: malformed userinfo (too many cl_guid fields)\n", clientNum); trap_DropClient(clientNum, "^1You were kicked because of malformed userinfo", 0); return qfalse; } // Nico, one name in userinfo (from ETpub) count = 0; if (len > 6) { for (i = 0; userinfo[i + 5]; ++i) { if (userinfo[i] == '\\' && userinfo[i + 1] == 'n' && userinfo[i + 2] == 'a' && userinfo[i + 3] == 'm' && userinfo[i + 4] == 'e' && userinfo[i + 5] == '\\') { count++; } } } if (count > 1) { G_LogPrintf("Dropping client %d: malformed userinfo (too many name fields)\n", clientNum); trap_DropClient(clientNum, "^1You were kicked because of malformed userinfo", 0); return qfalse; } return qtrue; }
/* ============== ClientThink This will be called once for each client frame, which will usually be a couple times for each server frame on fast clients. If "g_synchronousClients 1" is set, this will be called exactly once for each server frame, which makes for smooth demo recording. ============== */ void ClientThink_real(gentity_t * ent) { gclient_t *client; pmove_t pm; int oldEventSequence; int msec; usercmd_t *ucmd; int bJumping = 0; client = ent->client; // don't think if the client is not yet connected (and thus not yet spawned in) if (client->pers.connected != CON_CONNECTED) { return; } // mark the time, so the connection sprite can be removed ucmd = &ent->client->pers.cmd; // sanity check the command time to prevent speedup cheating if (ucmd->serverTime > level.time + 200) { ucmd->serverTime = level.time + 200; } if (ucmd->serverTime < level.time - 1000) { ucmd->serverTime = level.time - 1000; } client->lastUpdateFrame = level.framenum; msec = ucmd->serverTime - client->ps.commandTime; // following others may result in bad times, but we still want // to check for follow toggles if (msec < 1 && client->sess.spectatorState != SPECTATOR_FOLLOW) { return; } if (msec > 200) { msec = 200; } if (pmove_msec.integer < 8) { trap_Cvar_Set("pmove_msec", "8"); } else if (pmove_msec.integer > 33) { trap_Cvar_Set("pmove_msec", "33"); } if (pmove_fixed.integer || client->pers.pmoveFixed) { ucmd->serverTime = ((ucmd->serverTime + pmove_msec.integer - 1) / pmove_msec.integer) * pmove_msec.integer; } // // check for exiting intermission // if (level.intermissiontime) { ClientIntermissionThink(client); return; } // spectators don't do much if (client->sess.sessionTeam == TEAM_SPECTATOR) { if (client->sess.spectatorState == SPECTATOR_SCOREBOARD) { return; } SpectatorThink(ent, ucmd); return; } // check for inactivity timer, but never drop the local client of a non-dedicated server if (!ClientInactivityTimer(client)) { return; } // clear the rewards if time if (level.time > client->rewardTime) { client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP); } if (client->noclip) { client->ps.pm_type = PM_NOCLIP; } else if (client->ps.stats[STAT_HEALTH] <= 0) { client->ps.pm_type = PM_DEAD; } else { client->ps.pm_type = PM_NORMAL; } client->ps.gravity = g_gravity.value; // set speed client->ps.speed = g_speed.value; // set up for pmove oldEventSequence = client->ps.eventSequence; memset(&pm, 0, sizeof(pm)); if (ent->flags & FL_FORCE_GESTURE) { ent->flags &= ~FL_FORCE_GESTURE; ent->client->pers.cmd.buttons |= BUTTON_GESTURE; } //Elder: 3rb Code moved to bg_pmove.c (resides in PM_Weapon) pm.ps = &client->ps; pm.cmd = *ucmd; if (pm.ps->pm_type == PM_DEAD) { pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; } else if (ent->r.svFlags & SVF_BOT) { pm.tracemask = MASK_PLAYERSOLID | CONTENTS_BOTCLIP; } else { pm.tracemask = MASK_PLAYERSOLID; } // JBravo: fixing telefragging and shit during spawnig. (Thanks NiceAss! :) if ((g_gametype.integer == GT_TEAMPLAY || g_gametype.integer == GT_TEAM) && ((ent->client->ps.stats[STAT_RQ3] & RQ3_PLAYERSOLID) != RQ3_PLAYERSOLID) && !level.lights_camera_action) { UnstickPlayer(ent); } if ((g_gametype.integer == GT_TEAMPLAY || g_gametype.integer == GT_TEAM) && ((ent->client->ps.stats[STAT_RQ3] & RQ3_PLAYERSOLID) != RQ3_PLAYERSOLID)) { pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; } pm.trace = trap_Trace; pm.pointcontents = trap_PointContents; pm.debugLevel = g_debugMove.integer; pm.noFootsteps = (g_dmflags.integer & DF_NO_FOOTSTEPS) > 0; pm.pmove_fixed = pmove_fixed.integer | client->pers.pmoveFixed; pm.pmove_msec = pmove_msec.integer; VectorCopy(client->ps.origin, client->oldOrigin); // JBravo: setting lca in pm if appropriate if (g_RQ3_lca.integer == 1) pm.lca = qtrue; else pm.lca = qfalse; pm.predict = qtrue; Pmove(&pm); if ((pm.cmd.upmove > 10) && (pm.waterlevel == 0) && ent->s.groundEntityNum != ENTITYNUM_NONE && pm.ps->groundEntityNum == ENTITYNUM_NONE) bJumping = 1; // save results of pmove if (ent->client->ps.eventSequence != oldEventSequence) { ent->eventTime = level.time; } BG_PlayerStateToEntityState(&ent->client->ps, &ent->s, qtrue); SendPendingPredictableEvents(&ent->client->ps); if (!(ent->client->ps.eFlags & EF_FIRING)) { client->fireHeld = qfalse; // for grapple } // use the snapped origin for linking so it matches client predicted versions VectorCopy(ent->s.pos.trBase, ent->r.currentOrigin); VectorCopy(pm.mins, ent->r.mins); VectorCopy(pm.maxs, ent->r.maxs); ent->waterlevel = pm.waterlevel; ent->watertype = pm.watertype; // execute client events ClientEvents(ent, oldEventSequence); // link entity now, after any personal teleporters have been used // JBravo: this call reactivates gibbed players. if (!ent->client->gibbed) trap_LinkEntity(ent); if (!ent->client->noclip) { G_TouchTriggers(ent); } // NOTE: now copy the exact origin over otherwise clients can be snapped into solid VectorCopy(ent->client->ps.origin, ent->r.currentOrigin); //test for solid areas in the AAS file BotTestAAS(ent->r.currentOrigin); // touch other objects ClientImpacts(ent, &pm); //Elder: someone added if (bJumping) JumpKick(ent); // save results of triggers and client events if (ent->client->ps.eventSequence != oldEventSequence) { ent->eventTime = level.time; } // swap and latch button actions client->oldbuttons = client->buttons; client->buttons = ucmd->buttons; client->latched_buttons |= client->buttons & ~client->oldbuttons; // check for respawning // JBravo: Lets make dead players into spectators. if (client->ps.stats[STAT_HEALTH] <= 0) { // wait for the attack button to be pressed if (level.time > client->respawnTime) { // forcerespawn is to prevent users from waiting out powerups if (g_forcerespawn.integer > 0 && (level.time - client->respawnTime) > g_forcerespawn.integer * 1000 && g_gametype.integer != GT_TEAMPLAY && g_gametype.integer != GT_CTF) { respawn(ent); return; } if ((g_gametype.integer == GT_TEAMPLAY || g_gametype.integer == GT_CTF) && level.time > client->respawnTime) { MakeSpectator(ent); } // pressing attack or use is the normal respawn method // JBravo: make'em spactate if (ucmd->buttons & (BUTTON_ATTACK | BUTTON_USE_HOLDABLE)) { if (g_gametype.integer == GT_TEAMPLAY || g_gametype.integer == GT_CTF) { MakeSpectator(ent); } else { respawn(ent); } } } return; } // JBravo: Idle sounds if (g_RQ3_ppl_idletime.integer) { if (ucmd->forwardmove == 0 && ucmd->rightmove == 0) { if (client->idletime) { if (level.time >= client->idletime + (g_RQ3_ppl_idletime.integer * 1000)) { if (g_gametype.integer >= GT_TEAM && g_RQ3_idleaction.integer == 1 && (ent->client->sess.sessionTeam == TEAM_RED || ent->client->sess.sessionTeam == TEAM_BLUE)) { trap_SendServerCommand( -1, va("print \"Removing %s^7 from his team for excessive Idling\n\"", ent->client->pers.netname)); trap_SendServerCommand(ent - g_entities, "stuff team none\n"); } else if (g_gametype.integer >= GT_TEAM && g_RQ3_idleaction.integer == 2 && (ent->client->sess.sessionTeam == TEAM_RED || ent->client->sess.sessionTeam == TEAM_BLUE)) { trap_SendServerCommand( -1, va("print \"Kicking %s^7 for excessive Idling\n\"", ent->client->pers.netname)); trap_DropClient(ent - g_entities, "Dropped due to excessive Idling"); } else G_TempEntity(ent->r.currentOrigin, EV_INSANESOUND); client->idletime = 0; } } else { client->idletime = level.time; } } else { client->idletime = 0; } } // perform once-a-second actions ClientTimerActions(ent, msec); }
/* =========== ClientConnect Called when a player begins connecting to the server. Called again for every map change or tournement restart. The session information will be valid after exit. Return nullptr if the client should be allowed, otherwise return a string with the reason for denial. Otherwise, the client will be sent the current gamestate and will eventually get to ClientBegin. firstTime will be true the very first time a client connects to the server machine, but false on map changes and tournement restarts. ============ */ const char *ClientConnect( int clientNum, bool firstTime ) { const char *value; const char *userInfoError; gclient_t *client; char userinfo[ MAX_INFO_STRING ]; char pubkey[ RSA_STRING_LENGTH ]; gentity_t *ent; char reason[ MAX_STRING_CHARS ] = { "" }; int i; const char *country; ent = &g_entities[ clientNum ]; client = &level.clients[ clientNum ]; // ignore if client already connected if ( client->pers.connected != CON_DISCONNECTED ) { return nullptr; } ent->client = client; memset( client, 0, sizeof( *client ) ); trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); value = Info_ValueForKey( userinfo, "ip" ); // check for local client if ( !strcmp( value, "localhost" ) ) { client->pers.localClient = true; } G_AddressParse( value, &client->pers.ip ); trap_GetPlayerPubkey( clientNum, pubkey, sizeof( pubkey ) ); if ( strlen( pubkey ) != RSA_STRING_LENGTH - 1 ) { return "Invalid pubkey key"; } trap_GenFingerprint( pubkey, sizeof( pubkey ), client->pers.guid, sizeof( client->pers.guid ) ); client->pers.admin = G_admin_admin( client->pers.guid ); client->pers.pubkey_authenticated = false; if ( client->pers.admin ) { Com_GMTime( &client->pers.admin->lastSeen ); } // check for admin ban if ( G_admin_ban_check( ent, reason, sizeof( reason ) ) ) { return va( "%s", reason ); // reason is local } // check for a password value = Info_ValueForKey( userinfo, "password" ); if ( g_password.string[ 0 ] && Q_stricmp( g_password.string, "none" ) && strcmp( g_password.string, value ) != 0 ) { return "Invalid password"; } // if a player reconnects quickly after a disconnect, the client disconnect may never be called, thus flag can get lost in the ether if ( ent->inuse ) { G_LogPrintf( "Forcing disconnect on active client: %i", (int)( ent - g_entities ) ); // so lets just fix up anything that should happen on a disconnect ClientDisconnect( ent-g_entities ); } for ( i = 0; i < level.maxclients; i++ ) { if ( level.clients[ i ].pers.connected == CON_DISCONNECTED ) { continue; } if ( !( g_entities[i].r.svFlags & SVF_BOT ) && !Q_stricmp( client->pers.guid, level.clients[ i ].pers.guid ) ) { if ( !G_ClientIsLagging( level.clients + i ) ) { trap_SendServerCommand( i, "cp \"Your GUID is not secure\"" ); return "Duplicate GUID"; } trap_DropClient( i, "Ghost" ); } } client->pers.connected = CON_CONNECTING; // read or initialize the session data if ( firstTime ) { G_InitSessionData( client, userinfo ); } G_ReadSessionData( client ); // get and distribute relevent paramters G_namelog_connect( client ); userInfoError = ClientUserinfoChanged( clientNum, false ); if ( userInfoError != nullptr ) { return userInfoError; } G_LogPrintf( "ClientConnect: %i [%s] (%s) \"%s^7\" \"%s^7\"", clientNum, client->pers.ip.str[0] ? client->pers.ip.str : "127.0.0.1", client->pers.guid, client->pers.netname, client->pers.netname ); country = Info_ValueForKey( userinfo, "geoip" ); Q_strncpyz( client->pers.country, country, sizeof( client->pers.country ) ); G_SendClientPmoveParams(clientNum); // don't do the "xxx connected" messages if they were caried over from previous level if ( firstTime ) { if ( g_geoip.integer && country && *country ) { trap_SendServerCommand( -1, va( "print_tr %s %s %s", QQ( N_("$1$^7 connected from $2$") ), Quote( client->pers.netname ), Quote( country ) ) ); } else { trap_SendServerCommand( -1, va( "print_tr %s %s", QQ( N_("$1$^7 connected") ), Quote( client->pers.netname ) ) ); } } // count current clients and rank for scoreboard CalculateRanks(); // if this is after !restart keepteams or !restart switchteams, apply said selection if ( client->sess.restartTeam != TEAM_NONE ) { G_ChangeTeam( ent, client->sess.restartTeam ); client->sess.restartTeam = TEAM_NONE; } return nullptr; }
/* =========== ClientUserInfoChanged Called from ClientConnect when the player first connects and directly by the server system when the player updates a userinfo variable. The game can override any of the settings and call trap_SetUserinfo if desired. ============ */ void ClientUserinfoChanged(int clientNum) { gentity_t *ent; char *s; char oldname[MAX_STRING_CHARS]; char userinfo[MAX_INFO_STRING]; gclient_t *client; char *ip = NULL; // Nico, used to store client ip char *rate = NULL; // Nico, used to store client rate char *snaps = NULL; // Nico, used to store client snaps char *name = NULL; // Nico, used to store client name char oldAuthToken[MAX_QPATH]; // Nico, used to see if auth token was changed ent = g_entities + clientNum; client = ent->client; client->ps.clientNum = clientNum; // Nico, flood protection if (ClientIsFlooding(ent)) { G_LogPrintf("Dropping client %d: flooded userinfo\n", clientNum); trap_DropClient(clientNum, "^1You were kicked because of flooded userinfo", 0); return; } trap_GetUserinfo(clientNum, userinfo, sizeof (userinfo)); // Nico, perform security checks on userinfo string if (!checkUserinfoString(clientNum, userinfo)) { return; } if (g_developer.integer || *g_log.string || g_dedicated.integer) { G_Printf("Userinfo: %s\n", userinfo); } // check for local client ip = Info_ValueForKey(userinfo, "ip"); Q_strncpyz(client->pers.ip, ip, IP_MAX_LENGTH); if (ip && !strcmp(ip, "localhost")) { client->pers.localClient = qtrue; level.fLocalHost = qtrue; client->sess.referee = RL_REFEREE; } // Nico, store rate and snaps rate = Info_ValueForKey(userinfo, "rate"); client->pers.rate = atoi(rate); snaps = Info_ValueForKey(userinfo, "snaps"); client->pers.snaps = atoi(snaps); // Nico, backup old auth token Q_strncpyz(oldAuthToken, client->pers.authToken, sizeof (oldAuthToken)); s = Info_ValueForKey(userinfo, "cg_uinfo"); sscanf(s, "%i %i %i %i %s %i %i %i %i %i %i %i %i %i", &client->pers.clientFlags, &client->pers.clientTimeNudge, &client->pers.clientMaxPackets, // Nico, max FPS &client->pers.maxFPS, // Nico, auth Token (char *)&client->pers.authToken, // Nico, load view angles on load &client->pers.loadViewAngles, // Nico, load weapon on load &client->pers.loadWeapon, // Nico, load position when player dies &client->pers.autoLoad, // Nico, cgaz &client->pers.cgaz, // Nico, hideme &client->pers.hideme, // Nico, client auto demo record setting &client->pers.autoDemo, // Nico, automatically load checkpoints &client->pers.autoLoadCheckpoints, // Nico, persistant specLock (int *)&client->sess.specLocked, // Nico, keep all demos &client->pers.keepAllDemos ); // Nico, check if auth token was changed if (oldAuthToken[0] != '\0' && Q_stricmp(oldAuthToken, client->pers.authToken) && client->sess.logged) { // Nico, auth token was changed => logout player if he was logged in CP("cp \"You are no longer logged in!\n\""); G_LogPrintf("ClientUserinfoChanged: authToken changed for client %d, forcing logout\n", clientNum); ent->client->sess.logged = qfalse; } client->pers.autoActivate = (client->pers.clientFlags & CGF_AUTOACTIVATE) ? PICKUP_TOUCH : PICKUP_ACTIVATE; client->pers.predictItemPickup = ((client->pers.clientFlags & CGF_PREDICTITEMS) != 0); if (client->pers.clientFlags & CGF_AUTORELOAD) { client->pers.bAutoReloadAux = qtrue; client->pmext.bAutoReload = qtrue; } else { client->pers.bAutoReloadAux = qfalse; client->pmext.bAutoReload = qfalse; } // Nico, pmove_fixed client->pers.pmoveFixed = client->pers.clientFlags & CGF_PMOVEFIXED; // Nico, autologin client->pers.autoLogin = client->pers.clientFlags & CGF_AUTOLOGIN; // // Nico, name handling // // Backup old name Q_strncpyz(oldname, client->pers.netname, sizeof (oldname)); // Get new name from userinfo string name = Info_ValueForKey(userinfo, "name"); // Clean the new name ClientCleanName(name, client->pers.netname, sizeof (client->pers.netname)); // Check it's valid if (CheckName(client->pers.netname) != qtrue) { // Invalid name, restore old name Q_strncpyz(client->pers.netname, oldname, sizeof (client->pers.netname)); Info_SetValueForKey(userinfo, "name", oldname); trap_SetUserinfo(clientNum, userinfo); CPx(clientNum, "print \"^1Invalid name, name change refused\n\""); G_LogPrintf("Client %d name change refused\n", clientNum); } else { // Name is valid // Now, check if name was changed or not if (client->pers.connected == CON_CONNECTED && strcmp(oldname, client->pers.netname) != 0) { // Name was changed // Now, check name changes limit if (g_maxNameChanges.integer > -1 && client->pers.nameChanges >= g_maxNameChanges.integer) { // Nico, limit reached, forbid name change Q_strncpyz(client->pers.netname, oldname, sizeof (client->pers.netname)); Info_SetValueForKey(userinfo, "name", oldname); trap_SetUserinfo(clientNum, userinfo); CPx(clientNum, "print \"^1You had too many namechanges\n\""); G_LogPrintf("Client %d name change refused\n", clientNum); return; } client->pers.nameChanges++; trap_SendServerCommand(-1, va("print \"%s" S_COLOR_WHITE " renamed to %s\n\"", oldname, client->pers.netname)); } } // // Nico, end of name handling // client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; // To communicate it to cgame client->ps.stats[STAT_PLAYER_CLASS] = client->sess.playerType; // Gordon: Not needed any more as it's in clientinfo? // send over a subset of the userinfo keys so other clients can // print scoreboards, display models, and play custom sounds s = va("n\\%s\\t\\%i\\c\\%i\\w\\%i\\lw\\%i\\sw\\%i\\mu\\%i\\ref\\%i\\pm\\%i\\l\\%i\\h\\%i\\cc\\%i", client->pers.netname, client->sess.sessionTeam, client->sess.playerType, client->sess.playerWeapon, client->sess.latchPlayerWeapon, client->sess.latchPlayerWeapon2, client->sess.muted ? 1 : 0, client->sess.referee, client->pers.pmoveFixed ? 1 : 0, // Nico, pmove_fixed client->sess.logged ? 1 : 0, // Nico, login status client->pers.hideme, // Nico, hideme client->sess.countryCode// Nico, country code (GeoIP) ); trap_GetConfigstring(CS_PLAYERS + clientNum, oldname, sizeof (oldname)); trap_SetConfigstring(CS_PLAYERS + clientNum, s); if (!Q_stricmp(oldname, s)) { return; } G_LogPrintf("ClientUserinfoChanged: %i %s\n", clientNum, s); G_DPrintf("ClientUserinfoChanged: %i :: %s\n", clientNum, s); }
void Svcmd_BanUser_f( void ) { char str[MAX_TOKEN_CHARS]; char userInfo[MAX_TOKEN_CHARS]; idFilter_t id; int playerNum; char *ip; if ( trap_Argc() < 2 ) { G_Printf("Usage: banUser <client ID> <reason for banning>\n"); return; } trap_Argv( 1, str, sizeof( str ) ); playerNum = atoi(str); if ( playerNum > MAX_CLIENTS || playerNum < 0 || !g_entities[playerNum].client ) { G_Printf("Error: Player ID wasn't valid.\n"); return; } trap_GetUserinfo( playerNum, userInfo, sizeof( userInfo ) ); if ( !userInfo[0] ) return; //get unique Ban ID id.playerID = atoul( Info_ValueForKey( userInfo, "sv_securityCode" ) ); //Get player name and clean it of color tags Q_strncpyz( id.playerName, Q_CleanStr(Info_ValueForKey( userInfo, "name" )), sizeof( id.playerName ) ); //get ban reason trap_Argv( 2, id.banReason, sizeof( id.banReason ) ); if ( !id.banReason[0] ) Q_strncpyz( id.banReason, "No reason given.", sizeof( id.banReason ) ); AddID( &id ); ip = g_entities[playerNum].client->pers.ip; UpdateIDBans(); //Scooter's filter list if( Q_stricmp( ip, "localhost" ) //localhost && Q_strncmp( ip, "10.", 3 ) //class A && Q_strncmp( ip, "172.16.", 7 ) //class B && Q_strncmp( ip, "192.168.", 8 ) //class C && Q_strncmp( ip, "127.", 4 ) //loopback && Q_strncmp( ip, "169.254.", 8 ) //link-local ) { AddIP( ip ); G_Printf( "User: %s ( %i - %s ) ^7was successfully banned.\n", Info_ValueForKey( userInfo, "name" ), playerNum, ip ); } trap_DropClient( playerNum, "Banned from the server" ); G_Printf( "User: %s ( %i ) ^7was successfully banned.\n", id.playerName, playerNum ); }
/* =========== ClientUserInfoChanged Called from ClientConnect when the player first connects and directly by the server system when the player updates a userinfo variable. The game can override any of the settings and call trap_SetUserinfo if desired. ============ */ void ClientUserinfoChanged( int clientNum ) { gentity_t *ent; int teamTask, teamLeader; char *s; char model[MAX_QPATH]; char headModel[MAX_QPATH]; char oldname[MAX_STRING_CHARS]; gclient_t *client; char c1[MAX_INFO_STRING]; char c2[MAX_INFO_STRING]; char userinfo[MAX_INFO_STRING]; ent = g_entities + clientNum; client = ent->client; trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); // check for malformed or illegal info strings if ( !Info_Validate(userinfo) ) { strcpy (userinfo, "\\name\\badinfo"); // don't keep those clients and userinfo trap_DropClient(clientNum, "Invalid userinfo"); } // check for local client s = Info_ValueForKey( userinfo, "ip" ); if ( !strcmp( s, "localhost" ) ) { client->pers.localClient = qtrue; } // check the item prediction s = Info_ValueForKey( userinfo, "cg_predictItems" ); if ( !atoi( s ) ) { client->pers.predictItemPickup = qfalse; } else { client->pers.predictItemPickup = qtrue; } // set name Q_strncpyz ( oldname, client->pers.netname, sizeof( oldname ) ); s = Info_ValueForKey (userinfo, "name"); ClientCleanName( s, client->pers.netname, sizeof(client->pers.netname) ); if ( client->sess.sessionTeam == TEAM_SPECTATOR ) { if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) { Q_strncpyz( client->pers.netname, "scoreboard", sizeof(client->pers.netname) ); } } if ( client->pers.connected == CON_CONNECTED ) { if ( strcmp( oldname, client->pers.netname ) ) { trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " renamed to %s\n\"", oldname, client->pers.netname) ); } } // set max health #ifdef MISSIONPACK if (client->ps.powerups[PW_GUARD]) { client->pers.maxHealth = 200; } else { client->pers.maxHealth = ClientHandicap( client ); } #else client->pers.maxHealth = ClientHandicap( client ); #endif client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; // set model if( g_gametype.integer >= GT_TEAM ) { Q_strncpyz( model, Info_ValueForKey (userinfo, "team_model"), sizeof( model ) ); Q_strncpyz( headModel, Info_ValueForKey (userinfo, "team_headmodel"), sizeof( headModel ) ); } else { Q_strncpyz( model, Info_ValueForKey (userinfo, "model"), sizeof( model ) ); Q_strncpyz( headModel, Info_ValueForKey (userinfo, "headmodel"), sizeof( headModel ) ); } /* NOTE: all client side now // team switch( team ) { case TEAM_RED: ForceClientSkin(client, model, "red"); // ForceClientSkin(client, headModel, "red"); break; case TEAM_BLUE: ForceClientSkin(client, model, "blue"); // ForceClientSkin(client, headModel, "blue"); break; } // don't ever use a default skin in teamplay, it would just waste memory // however bots will always join a team but they spawn in as spectator if ( g_gametype.integer >= GT_TEAM && team == TEAM_SPECTATOR) { ForceClientSkin(client, model, "red"); // ForceClientSkin(client, headModel, "red"); } */ #ifdef MISSIONPACK if (g_gametype.integer >= GT_TEAM) { client->pers.teamInfo = qtrue; } else { s = Info_ValueForKey( userinfo, "teamoverlay" ); if ( ! *s || atoi( s ) != 0 ) { client->pers.teamInfo = qtrue; } else { client->pers.teamInfo = qfalse; } } #else // teamInfo s = Info_ValueForKey( userinfo, "teamoverlay" ); if ( ! *s || atoi( s ) != 0 ) { client->pers.teamInfo = qtrue; } else { client->pers.teamInfo = qfalse; } #endif /* s = Info_ValueForKey( userinfo, "cg_pmove_fixed" ); if ( !*s || atoi( s ) == 0 ) { client->pers.pmoveFixed = qfalse; } else { client->pers.pmoveFixed = qtrue; } */ // team task (0 = none, 1 = offence, 2 = defence) teamTask = atoi(Info_ValueForKey(userinfo, "teamtask")); // team Leader (1 = leader, 0 is normal player) teamLeader = client->sess.teamLeader; // colors strcpy(c1, Info_ValueForKey( userinfo, "color1" )); strcpy(c2, Info_ValueForKey( userinfo, "color2" )); // send over a subset of the userinfo keys so other clients can // print scoreboards, display models, and play custom sounds if (ent->r.svFlags & SVF_BOT) { s = va("n\\%s\\t\\%i\\model\\%s\\hmodel\\%s\\c1\\%s\\c2\\%s\\hc\\%i\\w\\%i\\l\\%i\\skill\\%s\\tt\\%d\\tl\\%d", client->pers.netname, client->sess.sessionTeam, model, headModel, c1, c2, client->pers.maxHealth, client->sess.wins, client->sess.losses, Info_ValueForKey( userinfo, "skill" ), teamTask, teamLeader ); } else { s = va("n\\%s\\t\\%i\\model\\%s\\hmodel\\%s\\c1\\%s\\c2\\%s\\hc\\%i\\w\\%i\\l\\%i\\tt\\%d\\tl\\%d", client->pers.netname, client->sess.sessionTeam, model, headModel, c1, c2, client->pers.maxHealth, client->sess.wins, client->sess.losses, teamTask, teamLeader); } trap_SetConfigstring( CS_PLAYERS+clientNum, s ); // this is not the userinfo, more like the configstring actually G_LogPrintf( "ClientUserinfoChanged: %i %s\n", clientNum, s ); }
static void Svcmd_Kick_f( void ) { gclient_t *cl; int i; int timeout = -1; char sTimeout[MAX_TOKEN_CHARS]; char name[MAX_TOKEN_CHARS]; // make sure server is running if ( !G_Is_SV_Running() ) { G_Printf( "Server is not running.\n" ); return; } if ( trap_Argc() < 2 || trap_Argc() > 3 ) { G_Printf ("Usage: kick <player name> [timeout]\n"); return; } if( trap_Argc() == 3 ) { trap_Argv( 2, sTimeout, sizeof( sTimeout ) ); timeout = atoi( sTimeout ); } else { timeout = 300; } trap_Argv(1, name, sizeof(name)); cl = G_GetPlayerByName( name );//ClientForString( name ); if ( !cl ) { if ( !Q_stricmp(name, "all") ) { for (i = 0, cl = level.clients; i < level.numConnectedClients; i++, cl++) { // dont kick localclients ... if ( cl->pers.localClient ) { continue; } if ( timeout != -1 ) { char *ip; char userinfo[MAX_INFO_STRING]; trap_GetUserinfo( cl->ps.clientNum, userinfo, sizeof( userinfo ) ); ip = Info_ValueForKey (userinfo, "ip"); // use engine banning system, mods may choose to use their own banlist if (USE_ENGINE_BANLIST) { // kick but dont ban bots, they arent that lame if ( (g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) { timeout = 0; } trap_DropClient(cl->ps.clientNum, "player kicked", timeout); } else { trap_DropClient(cl->ps.clientNum, "player kicked", 0); // kick but dont ban bots, they arent that lame if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) AddIPBan( ip ); } } else { trap_DropClient(cl->ps.clientNum, "player kicked", 0); } } } #ifndef NO_BOT_SUPPORT else if ( !Q_stricmp(name, "allbots") ) { for (i = 0, cl = level.clients; i < level.numConnectedClients; i++, cl++) { if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) { continue; } // kick but dont ban bots, they arent that lame trap_DropClient(cl->ps.clientNum, "player kicked", 0); } } #endif return; } else { // dont kick localclients ... if ( cl->pers.localClient ) { G_Printf("Cannot kick host player\n"); return; } if ( timeout != -1 ) { char *ip; char userinfo[MAX_INFO_STRING]; trap_GetUserinfo( cl->ps.clientNum, userinfo, sizeof( userinfo ) ); ip = Info_ValueForKey (userinfo, "ip"); // use engine banning system, mods may choose to use their own banlist if (USE_ENGINE_BANLIST) { // kick but dont ban bots, they arent that lame if ( (g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) { timeout = 0; } trap_DropClient(cl->ps.clientNum, "player kicked", timeout); } else { trap_DropClient(cl->ps.clientNum, "player kicked", 0); // kick but dont ban bots, they arent that lame if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) AddIPBan( ip ); } } else { trap_DropClient(cl->ps.clientNum, "player kicked", 0); } } }
/* * ClientUserinfoChanged * called whenever the player updates a userinfo variable. * * The game can override any of the settings in place * (forcing skins or names, etc) before copying it off. */ void ClientUserinfoChanged( edict_t *ent, char *userinfo ) { char *s; char oldname[MAX_INFO_VALUE]; gclient_t *cl; int rgbcolor, i; assert( ent && ent->r.client ); assert( userinfo && Info_Validate( userinfo ) ); // check for malformed or illegal info strings if( !Info_Validate( userinfo ) ) { trap_DropClient( ent, DROP_TYPE_GENERAL, "Error: Invalid userinfo" ); return; } cl = ent->r.client; // ip s = Info_ValueForKey( userinfo, "ip" ); if( !s ) { trap_DropClient( ent, DROP_TYPE_GENERAL, "Error: Server didn't provide client IP" ); return; } Q_strncpyz( cl->ip, s, sizeof( cl->ip ) ); // socket s = Info_ValueForKey( userinfo, "socket" ); if( !s ) { trap_DropClient( ent, DROP_TYPE_GENERAL, "Error: Server didn't provide client socket" ); return; } Q_strncpyz( cl->socket, s, sizeof( cl->socket ) ); // color s = Info_ValueForKey( userinfo, "color" ); if( s ) rgbcolor = COM_ReadColorRGBString( s ); else rgbcolor = -1; if( rgbcolor != -1 ) { rgbcolor = COM_ValidatePlayerColor( rgbcolor ); Vector4Set( cl->color, COLOR_R( rgbcolor ), COLOR_G( rgbcolor ), COLOR_B( rgbcolor ), 255 ); } else { Vector4Set( cl->color, 255, 255, 255, 255 ); } // set name, it's validated and possibly changed first Q_strncpyz( oldname, cl->netname, sizeof( oldname ) ); G_SetName( ent, Info_ValueForKey( userinfo, "name" ) ); if( oldname[0] && Q_stricmp( oldname, cl->netname ) && !cl->isTV && !CheckFlood( ent, false ) ) G_PrintMsg( NULL, "%s%s is now known as %s%s\n", oldname, S_COLOR_WHITE, cl->netname, S_COLOR_WHITE ); if( !Info_SetValueForKey( userinfo, "name", cl->netname ) ) { trap_DropClient( ent, DROP_TYPE_GENERAL, "Error: Couldn't set userinfo (name)" ); return; } // clan tag G_SetClan( ent, Info_ValueForKey( userinfo, "clan" ) ); // handedness s = Info_ValueForKey( userinfo, "hand" ); if( !s ) cl->hand = 2; else cl->hand = bound( atoi( s ), 0, 2 ); // handicap s = Info_ValueForKey( userinfo, "handicap" ); if( s ) { i = atoi( s ); if( i > 90 || i < 0 ) { G_PrintMsg( ent, "Handicap must be defined in the [0-90] range.\n" ); cl->handicap = 0; } else { cl->handicap = i; } } s = Info_ValueForKey( userinfo, "cg_movementStyle" ); if( s ) { i = bound( atoi( s ), 0, GS_MAXBUNNIES - 1 ); if( trap_GetClientState( PLAYERNUM(ent) ) < CS_SPAWNED ) { if( i != cl->movestyle ) cl->movestyle = cl->movestyle_latched = i; } else if( cl->movestyle_latched != cl->movestyle ) { G_PrintMsg( ent, "A movement style change is already in progress. Please wait.\n" ); } else if( i != cl->movestyle_latched ) { cl->movestyle_latched = i; if( cl->movestyle_latched != cl->movestyle ) { edict_t *switcher; switcher = G_Spawn(); switcher->think = think_MoveTypeSwitcher; switcher->nextThink = level.time + 10000; switcher->s.ownerNum = ENTNUM( ent ); G_PrintMsg( ent, "Movement style will change in 10 seconds.\n" ); } } } // update the movement features depending on the movestyle if( !G_ISGHOSTING( ent ) && g_allow_bunny->integer ) { if( cl->movestyle == GS_CLASSICBUNNY ) cl->ps.pmove.stats[PM_STAT_FEATURES] &= ~PMFEAT_FWDBUNNY; else cl->ps.pmove.stats[PM_STAT_FEATURES] |= PMFEAT_FWDBUNNY; } s = Info_ValueForKey( userinfo, "cg_noAutohop" ); if( s && s[0] ) { if( atoi( s ) != 0 ) cl->ps.pmove.stats[PM_STAT_FEATURES] &= ~PMFEAT_CONTINOUSJUMP; else cl->ps.pmove.stats[PM_STAT_FEATURES] |= PMFEAT_CONTINOUSJUMP; } #ifdef UCMDTIMENUDGE s = Info_ValueForKey( userinfo, "cl_ucmdTimeNudge" ); if( !s ) { cl->ucmdTimeNudge = 0; } else { cl->ucmdTimeNudge = atoi( s ); clamp( cl->ucmdTimeNudge, -MAX_UCMD_TIMENUDGE, MAX_UCMD_TIMENUDGE ); } #endif // mm session // TODO: remove the key after storing it to gclient_t ! s = Info_ValueForKey( userinfo, "cl_mm_session" ); cl->mm_session = ( s == NULL ) ? 0 : atoi( s ); s = Info_ValueForKey( userinfo, "mmflags" ); cl->mmflags = ( s == NULL ) ? 0 : strtoul( s, NULL, 10 ); // tv if( cl->isTV ) { s = Info_ValueForKey( userinfo, "tv_port" ); cl->tv.port = s ? atoi( s ) : 0; s = Info_ValueForKey( userinfo, "tv_port6" ); cl->tv.port6 = s ? atoi( s ) : 0; s = Info_ValueForKey( userinfo, "max_cl" ); cl->tv.maxclients = s ? atoi( s ) : 0; s = Info_ValueForKey( userinfo, "num_cl" ); cl->tv.numclients = s ? atoi( s ) : 0; s = Info_ValueForKey( userinfo, "chan" ); cl->tv.channel = s ? atoi( s ) : 0; } if( !G_ISGHOSTING( ent ) && trap_GetClientState( PLAYERNUM( ent ) ) >= CS_SPAWNED ) G_Client_AssignTeamSkin( ent, userinfo ); // save off the userinfo in case we want to check something later Q_strncpyz( cl->userinfo, userinfo, sizeof( cl->userinfo ) ); G_UpdatePlayerInfoString( PLAYERNUM( ent ) ); G_UpdateMMPlayerInfoString( PLAYERNUM( ent ) ); G_Gametype_ScoreEvent( cl, "userinfochanged", oldname ); }
/** Called from ClientConnect when the player first connects and directly by the server system when the player updates a userinfo variable. The game can override any of the settings and call trap_SetUserinfo if desired. */ void ClientUserinfoChanged(int clientNum) { gentity_t *ent; int teamTask, teamLeader, team, health; char *s; char oldname[MAX_STRING_CHARS]; gclient_t *client; char userinfo[MAX_INFO_STRING]; char *nameError; ent = g_entities + clientNum; client = ent->client; trap_GetUserinfo(clientNum, userinfo, sizeof(userinfo)); // check for malformed or illegal info strings if (!Info_Validate(userinfo)) { strcpy (userinfo, "\\name\\badinfo"); // don't keep those clients and userinfo trap_DropClient(clientNum, "Invalid userinfo"); } // check for local client s = Info_ValueForKey(userinfo, "ip"); if (!strcmp(s, "localhost")) { client->pers.localClient = qtrue; } // check the item prediction s = Info_ValueForKey(userinfo, "cg_predictItems"); if (!atoi(s)) { client->pers.predictItemPickup = qfalse; } else { client->pers.predictItemPickup = qtrue; } // set name Q_strncpyz(oldname, client->pers.netname, sizeof(oldname)); s = Info_ValueForKey(userinfo, "name"); nameError = ClientCleanName(s, client->pers.netname, sizeof client->pers.netname); if (client->pers.connected == CON_CONNECTED && strcmp(oldname, s)) { if (client->pers.muted) { ClientPrint(ent, "You cannot change your name while you are muted."); } else if (nameError) { ClientPrint(ent, "%s", nameError); } else { G_LogPrintf("%s ^7renamed to %s\n", oldname, client->pers.netname); } if (nameError || client->pers.muted) { Q_strncpyz(client->pers.netname, oldname, sizeof client->pers.netname); } } if (client->sess.sessionTeam == TEAM_SPECTATOR) { if (client->sess.spectatorState == SPECTATOR_SCOREBOARD) { Q_strncpyz(client->pers.netname, "scoreboard", sizeof(client->pers.netname)); } } // set max health health = atoi(Info_ValueForKey(userinfo, "handicap")); client->pers.maxHealth = health; if (client->pers.maxHealth < 1 || client->pers.maxHealth > 100) { client->pers.maxHealth = 100; } client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; // bots set their team a few frames later if (g_gametype.integer >= GT_TEAM && g_entities[clientNum].r.svFlags & SVF_BOT) { s = Info_ValueForKey(userinfo, "team"); if (!Q_stricmp(s, "red") || !Q_stricmp(s, "r")) { team = TEAM_RED; } else if (!Q_stricmp(s, "blue") || !Q_stricmp(s, "b")) { team = TEAM_BLUE; } else { // pick the team with the least number of players team = PickTeam(clientNum); } } else { team = client->sess.sessionTeam; } // team task (0 = none, 1 = offence, 2 = defence) teamTask = atoi(Info_ValueForKey(userinfo, "teamtask")); // team Leader (1 = leader, 0 is normal player) teamLeader = client->sess.teamLeader; // send over a subset of the userinfo keys so other clients can // print scoreboards, display models, and play custom sounds if (ent->r.svFlags & SVF_BOT) { s = va("n\\%s\\t\\%i\\hc\\%i\\w\\%i\\l\\%i\\skill\\%s\\tt\\%d\\tl\\%d", client->pers.netname, team, client->pers.maxHealth, client->sess.wins, client->sess.losses, Info_ValueForKey(userinfo, "skill"), teamTask, teamLeader); } else { s = va("n\\%s\\t\\%i\\s\\%d\\r\\%i\\hc\\%i\\w\\%i\\l\\%i\\tt\\%d\\tl\\%d", client->pers.netname, team, client->sess.specOnly, client->pers.ready, client->pers.maxHealth, client->sess.wins, client->sess.losses, teamTask, teamLeader); } trap_SetConfigstring(CS_PLAYERS+clientNum, s); // this is not the userinfo, more like the configstring actually G_LogPrintf("ClientUserinfoChanged: %i %s\n", clientNum, s); }