/* =========== ClientBotConnect Cut-down version of ClientConnect. Doesn't do things not relevant to bots (which are local GUIDless clients). ============ */ const char *ClientBotConnect( int clientNum, bool firstTime, team_t team ) { const char *userInfoError; gclient_t *client; char userinfo[ MAX_INFO_STRING ]; gentity_t *ent; ent = &g_entities[ clientNum ]; client = &level.clients[ clientNum ]; ent->client = client; memset( client, 0, sizeof( *client ) ); trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); client->pers.localClient = true; G_AddressParse( "localhost", &client->pers.ip ); Q_strncpyz( client->pers.guid, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", sizeof( client->pers.guid ) ); client->pers.admin = nullptr; client->pers.pubkey_authenticated = true; client->pers.connected = CON_CONNECTING; // read or initialize the session data if ( firstTime ) { G_InitSessionData( client, userinfo ); } G_ReadSessionData( client ); // get and distribute relevant parameters G_namelog_connect( client ); userInfoError = ClientUserinfoChanged( clientNum, false ); if ( userInfoError != nullptr ) { return userInfoError; } ent->r.svFlags |= SVF_BOT; // can happen during reconnection if ( !ent->botMind ) { G_BotSetDefaults( clientNum, team, client->sess.botSkill, client->sess.botTree ); } G_LogPrintf( "ClientConnect: %i [%s] (%s) \"%s^7\" \"%s^7\" [BOT]", clientNum, client->pers.ip.str[0] ? client->pers.ip.str : "127.0.0.1", client->pers.guid, client->pers.netname, client->pers.netname ); // don't do the "xxx connected" messages if they were caried over from previous level if ( firstTime ) { 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(); return nullptr; }
/* =========== 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; }
/* =========== 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; }