/* ================== SV_ForceCvar_f_helper Called internally by SV_ForceCvar_f. ================== */ static void SV_ForceCvar_f_helper( client_t *cl ) { int oldInfoLen; int newInfoLen; qboolean touchedUserinfo = qfalse; // Who knows what would happen if we called the VM with a GAME_CLIENT_USERINFO_CHANGED // when this client wasn't connected. if (cl->state < CS_CONNECTED) { return; } // First remove all keys; there might exist more than one in the userinfo. oldInfoLen = strlen(cl->userinfo); while (qtrue) { Info_RemoveKey(cl->userinfo, Cmd_Argv(2)); newInfoLen = strlen(cl->userinfo); if (oldInfoLen == newInfoLen) { break; } // userinfo wasn't modified. oldInfoLen = newInfoLen; touchedUserinfo = qtrue; } if (strlen(Cmd_Argv(3)) > 0) { if (strlen(Cmd_Argv(2)) + strlen(Cmd_Argv(3)) + 2 + newInfoLen >= MAX_INFO_STRING) { SV_DropClient(cl, "userinfo string length exceeded"); return; } Info_SetValueForKey(cl->userinfo, Cmd_Argv(2), Cmd_Argv(3)); touchedUserinfo = qtrue; } if (touchedUserinfo) { SV_UserinfoChanged(cl); VM_Call(gvm, GAME_CLIENT_USERINFO_CHANGED, cl - svs.clients); } }
/* * SV_UserinfoCommand_f */ static void SV_UserinfoCommand_f( client_t *client ) { char *info; unsigned int time; info = Cmd_Argv( 1 ); if( !Info_Validate( info ) ) { SV_DropClient( client, DROP_TYPE_GENERAL, "%s", "Error: Invalid userinfo" ); return; } time = Sys_Milliseconds(); if( client->userinfoLatchTimeout > time ) { Q_strncpyz( client->userinfoLatched, info, sizeof( client->userinfo ) ); } else { Q_strncpyz( client->userinfo, info, sizeof( client->userinfo ) ); client->userinfoLatched[0] = '\0'; client->userinfoLatchTimeout = time + USERINFO_UPDATE_COOLDOWN_MSEC; SV_UserinfoChanged( client ); } }
/* ================== SV_UpdateUserinfo_f ================== */ static void SV_UpdateUserinfo_f( client_t *cl ) { char info[MAX_INFO_STRING]; char *arg = Cmd_Argv(1); // Stop random empty /userinfo calls without hurting anything if (!arg || !*arg) { return; } Q_strncpyz( cl->userinfo, arg, sizeof(cl->userinfo) ); if (cl->lastUserInfoChange > svs.time) { cl->lastUserInfoCount++; if (cl->lastUserInfoCount >= INFO_CHANGE_MAX_COUNT) { SV_SendServerCommand(cl, "print \"Warning: Too many info changes, last info ignored\n\"\n"); return; } } else { cl->lastUserInfoCount = 0; cl->lastUserInfoChange = svs.time + INFO_CHANGE_MIN_INTERVAL; } SV_UserinfoChanged( cl ); // call prog code to allow overrides VM_Call( gvm, GAME_CLIENT_USERINFO_CHANGED, cl - svs.clients ); // get the name out of the game and set it in the engine SV_GetConfigstring(CS_PLAYERS + (cl - svs.clients), info, sizeof(info)); Info_SetValueForKey(cl->userinfo, "name", Info_ValueForKey(info, "n")); Q_strncpyz(cl->name, Info_ValueForKey(info, "n"), sizeof(cl->name)); }
/* ================== SV_UpdateUserinfo_f ================== */ static void SV_UpdateUserinfo_f( client_t *cl ) { char *arg = Cmd_Argv(1); // Stop random empty /userinfo calls without hurting anything if( !arg || !*arg ) return; Q_strncpyz( cl->userinfo, arg, sizeof(cl->userinfo) ); #ifdef FINAL_BUILD if (cl->lastUserInfoChange > svs.time) { cl->lastUserInfoCount++; if (cl->lastUserInfoCount >= INFO_CHANGE_MAX_COUNT) { // SV_SendServerCommand(cl, "print \"Warning: Too many info changes, last info ignored\n\"\n"); SV_SendServerCommand(cl, "print \"@@@TOO_MANY_INFO\n\"\n"); return; } } else #endif { cl->lastUserInfoCount = 0; cl->lastUserInfoChange = svs.time + INFO_CHANGE_MIN_INTERVAL; } SV_UserinfoChanged( cl ); // call prog code to allow overrides GVM_ClientUserinfoChanged( cl - svs.clients ); }
/* ================== SV_ForceName_f Sets a user's name ================== */ static void SV_ForceName_f( void ) { client_t *cl; const char *name; // make sure server is running if ( !com_sv_running->integer ) { Com_Printf( "Server is not running.\n" ); return; } if ( Cmd_Argc() >= 2 ) { cl = SV_GetPlayerByNum(); if ( !cl ) { return; } if ( Cmd_Argc() == 2 ) { Com_Printf ("forcename: %s: %s\n",Cmd_Argv(1),cl->name); return; } else { name = Cmd_ArgsFrom(2); cl->forcename = 2; Q_strncpyz( cl->name, name, sizeof(cl->name) ); SV_UserinfoChanged( cl ); VM_Call( gvm, GAME_CLIENT_USERINFO_CHANGED, cl - svs.clients ); Com_Printf ("forcename: %s: %s\n",Cmd_Argv(1),cl->name); return; } } Com_Printf ("Usage: forcename <client number> [<name>]\n"); return; }
/* ================== SV_UpdateUserinfo_f ================== */ static void SV_UpdateUserinfo_f( client_t *cl ) { Q_strncpyz( cl->userinfo, Cmd_Argv(1), sizeof(cl->userinfo) ); SV_UserinfoChanged( cl ); // call prog code to allow overrides VM_Call( gvm, GAME_CLIENT_USERINFO_CHANGED, cl - svs.clients ); }
/* ================== SV_UpdateUserinfo_f ================== */ static void SV_UpdateUserinfo_f( client_t *cl ) { Q_strncpyz( cl->userinfo, Cmd_Argv(1), sizeof(cl->userinfo) ); #ifdef FINAL_BUILD if (cl->lastUserInfoChange > svs.time) { cl->lastUserInfoCount++; if (cl->lastUserInfoCount >= INFO_CHANGE_MAX_COUNT) { // SV_SendServerCommand(cl, "print \"Warning: Too many info changes, last info ignored\n\"\n"); SV_SendServerCommand(cl, "print \"@@@TOO_MANY_INFO\n\"\n"); return; } } else #endif { cl->lastUserInfoCount = 0; cl->lastUserInfoChange = svs.time + INFO_CHANGE_MIN_INTERVAL; } SV_UserinfoChanged( cl ); // call prog code to allow overrides VM_Call( gvm, GAME_CLIENT_USERINFO_CHANGED, cl - svs.clients ); }
/* ================== SV_UpdateUserinfo_f ================== */ static void SV_UpdateUserinfo_f( client_t *cl ) { Q_strncpyz( cl->userinfo, Cmd_Argv(1), sizeof(cl->userinfo) ); SV_UserinfoChanged( cl ); // call prog code to allow overrides ge->ClientUserinfoChanged( cl - svs.clients ); }
/* ================== SV_UpdateUserinfo_f ================== */ static void SV_UpdateUserinfo_f( client_t *cl, const Cmd::Args& args ) { if (args.Argc() < 2) { return; } Q_strncpyz(cl->userinfo, args.Argv(1).c_str(), sizeof(cl->userinfo)); // FIXME QUOTING INFO SV_UserinfoChanged( cl ); // call prog code to allow overrides gvm.GameClientUserInfoChanged( cl - svs.clients ); }
/* ================== SV_UpdateUserinfo_f ================== */ static void SV_UpdateUserinfo_f( client_t *cl ) { #if 0 /* TODO */ if( cl->lastUserInfoChange > svs.time && cl->lastUserInfoCount > 5 ) { SV_SendServerCommand( cl, "print \"@@@TOO_MANY_INFO\n\"" ); return; } #endif Q_strncpyz( cl->userinfo, Cmd_Argv(1), sizeof(cl->userinfo) ); SV_UserinfoChanged( cl ); // call prog code to allow overrides VM_Call( gvm, GAME_CLIENT_USERINFO_CHANGED, cl - svs.clients ); }
/* * SV_UserinfoCommand_f */ static void SV_UserinfoCommand_f( client_t *client ) { char *info; info = Cmd_Argv( 1 ); if( !Info_Validate( info ) ) { SV_DropClient( client, DROP_TYPE_GENERAL, "Error: Invalid userinfo" ); return; } Q_strncpyz( client->userinfo, info, sizeof( client->userinfo ) ); SV_UserinfoChanged( client ); }
/* ================== SV_UpdateUserinfo_f ================== */ void SV_UpdateUserinfo_f( client_t *cl ) { if ( (sv_floodProtect->integer) && (cl->state >= CS_ACTIVE) && (svs.time < cl->nextReliableUserTime) ) { Q_strncpyz( cl->userinfobuffer, Cmd_Argv(1), sizeof(cl->userinfobuffer) ); SV_SendServerCommand(cl, "print \"^7Command ^1delayed^7 due to sv_floodprotect.\""); return; } cl->userinfobuffer[0]=0; cl->nextReliableUserTime = svs.time + 5000; Q_strncpyz( cl->userinfo, Cmd_Argv(1), sizeof(cl->userinfo) ); SV_UserinfoChanged( cl ); // call prog code to allow overrides VM_Call( gvm, GAME_CLIENT_USERINFO_CHANGED, cl - svs.clients ); }
/* * SV_UserinfoCommand_f */ static void SV_UserinfoCommand_f( client_t *client ) { char *info; // prevent userinfo cmd flood if( client->userinfoUpTimeout > Sys_Milliseconds() ) return; client->userinfoUpTimeout = Sys_Milliseconds() + USERINFO_UPDATE_COOLDOWN_MSEC; info = Cmd_Argv( 1 ); if( !Info_Validate( info ) ) { SV_DropClient( client, DROP_TYPE_GENERAL, "%s", "Error: Invalid userinfo" ); return; } Q_strncpyz( client->userinfo, info, sizeof( client->userinfo ) ); SV_UserinfoChanged( client ); }
/* ================== SV_ForceCvar_f_helper Called internally by SV_ForceCvar_f ================== */ static void SV_ForceCvar_f_helper(client_t *cl) { char *val; int len = -1; if (strlen(Cmd_Argv(3)) > 0) { val = Info_ValueForKey(cl->userinfo, Cmd_Argv(2)); if (val[0]) len = strlen(Cmd_Argv(3)) - strlen(val) + strlen(cl->userinfo); else len = strlen(Cmd_Argv(2)) + strlen(Cmd_Argv(3)) + 2 + strlen(cl->userinfo); } if (len >= MAX_INFO_STRING) SV_DropClient(cl, "userinfo string length exceeded"); else { // In the case where Cmd_Argv(3) is "", the Info_SetValueForKey() call will // actually just call Info_RemoveKey(). Info_SetValueForKey(cl->userinfo, Cmd_Argv(2), Cmd_Argv(3)); SV_UserinfoChanged(cl); // call prog code to allow overrides VM_Call(gvm, GAME_CLIENT_USERINFO_CHANGED, cl - svs.clients); } }
/* ================== SV_DirectConnect A "connect" OOB command has been received ================== */ void SV_DirectConnect( netadr_t from ) { char userinfo[MAX_INFO_STRING]; int i; client_t *cl, *newcl; MAC_STATIC client_t temp; sharedEntity_t *ent; int clientNum; int version; int qport; int challenge; char *password; int startIndex; char *denied; int count; char *ip; #ifdef _XBOX bool reconnect = false; #endif Com_DPrintf ("SVC_DirectConnect ()\n"); Q_strncpyz( userinfo, Cmd_Argv(1), sizeof(userinfo) ); version = atoi( Info_ValueForKey( userinfo, "protocol" ) ); if ( version != PROTOCOL_VERSION ) { NET_OutOfBandPrint( NS_SERVER, from, "print\nServer uses protocol version %i.\n", PROTOCOL_VERSION ); Com_DPrintf (" rejected connect from version %i\n", version); return; } challenge = atoi( Info_ValueForKey( userinfo, "challenge" ) ); qport = atoi( Info_ValueForKey( userinfo, "qport" ) ); // quick reject for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { /* This was preventing sv_reconnectlimit from working. It seems like commenting this out has solved the problem. HOwever, if there is a future problem then it could be this. if ( cl->state == CS_FREE ) { continue; } */ if ( NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) && ( cl->netchan.qport == qport || from.port == cl->netchan.remoteAddress.port ) ) { if (( svs.time - cl->lastConnectTime) < (sv_reconnectlimit->integer * 1000)) { NET_OutOfBandPrint( NS_SERVER, from, "print\nReconnect rejected : too soon\n" ); Com_DPrintf ("%s:reconnect rejected : too soon\n", NET_AdrToString (from)); return; } break; } } // don't let "ip" overflow userinfo string if ( NET_IsLocalAddress (from) ) ip = "localhost"; else ip = (char *)NET_AdrToString( from ); if( ( strlen( ip ) + strlen( userinfo ) + 4 ) >= MAX_INFO_STRING ) { NET_OutOfBandPrint( NS_SERVER, from, "print\nUserinfo string length exceeded. " "Try removing setu cvars from your config.\n" ); return; } Info_SetValueForKey( userinfo, "ip", ip ); // see if the challenge is valid (LAN clients don't need to challenge) if ( !NET_IsLocalAddress (from) ) { int ping; for (i=0 ; i<MAX_CHALLENGES ; i++) { if (NET_CompareAdr(from, svs.challenges[i].adr)) { if ( challenge == svs.challenges[i].challenge ) { break; // good } } } if (i == MAX_CHALLENGES) { NET_OutOfBandPrint( NS_SERVER, from, "print\nNo or bad challenge for address.\n" ); return; } ping = svs.time - svs.challenges[i].pingTime; Com_Printf( SE_GetString("MP_SVGAME", "CLIENT_CONN_WITH_PING"), i, ping);//"Client %i connecting with %i challenge ping\n", i, ping ); svs.challenges[i].connected = qtrue; // never reject a LAN client based on ping if ( !Sys_IsLANAddress( from ) ) { if ( sv_minPing->value && ping < sv_minPing->value ) { // don't let them keep trying until they get a big delay NET_OutOfBandPrint( NS_SERVER, from, va("print\n%s\n", SE_GetString("MP_SVGAME", "SERVER_FOR_HIGH_PING")));//Server is for high pings only\n" ); Com_DPrintf (SE_GetString("MP_SVGAME", "CLIENT_REJECTED_LOW_PING"), i);//"Client %i rejected on a too low ping\n", i); // reset the address otherwise their ping will keep increasing // with each connect message and they'd eventually be able to connect svs.challenges[i].adr.port = 0; return; } if ( sv_maxPing->value && ping > sv_maxPing->value ) { NET_OutOfBandPrint( NS_SERVER, from, va("print\n%s\n", SE_GetString("MP_SVGAME", "SERVER_FOR_LOW_PING")));//Server is for low pings only\n" ); Com_DPrintf (SE_GetString("MP_SVGAME", "CLIENT_REJECTED_HIGH_PING"), i);//"Client %i rejected on a too high ping\n", i); return; } } } else { // force the "ip" info key to "localhost" Info_SetValueForKey( userinfo, "ip", "localhost" ); } newcl = &temp; Com_Memset (newcl, 0, sizeof(client_t)); // if there is already a slot for this ip, reuse it for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { if ( cl->state == CS_FREE ) { continue; } if ( NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) && ( cl->netchan.qport == qport || from.port == cl->netchan.remoteAddress.port ) ) { Com_Printf ("%s:reconnect\n", NET_AdrToString (from)); newcl = cl; #ifdef _XBOX reconnect = true; #endif // VVFIXME - both SOF2 and Wolf remove this call, claiming it blows away the user's info // disconnect the client from the game first so any flags the // player might have are dropped VM_Call( gvm, GAME_CLIENT_DISCONNECT, newcl - svs.clients ); // goto gotnewcl; } } // find a client slot // if "sv_privateClients" is set > 0, then that number // of client slots will be reserved for connections that // have "password" set to the value of "sv_privatePassword" // Info requests will report the maxclients as if the private // slots didn't exist, to prevent people from trying to connect // to a full server. // This is to allow us to reserve a couple slots here on our // servers so we can play without having to kick people. // check for privateClient password password = Info_ValueForKey( userinfo, "password" ); if ( !strcmp( password, sv_privatePassword->string ) ) { startIndex = 0; } else { // skip past the reserved slots startIndex = sv_privateClients->integer; } newcl = NULL; for ( i = startIndex; i < sv_maxclients->integer ; i++ ) { cl = &svs.clients[i]; if (cl->state == CS_FREE) { newcl = cl; break; } } if ( !newcl ) { if ( NET_IsLocalAddress( from ) ) { count = 0; for ( i = startIndex; i < sv_maxclients->integer ; i++ ) { cl = &svs.clients[i]; if (cl->netchan.remoteAddress.type == NA_BOT) { count++; } } // if they're all bots if (count >= sv_maxclients->integer - startIndex) { SV_DropClient(&svs.clients[sv_maxclients->integer - 1], "only bots on server"); newcl = &svs.clients[sv_maxclients->integer - 1]; } else { Com_Error( ERR_FATAL, "server is full on local connect\n" ); return; } } else { const char *SV_GetStringEdString(char *refSection, char *refName); NET_OutOfBandPrint( NS_SERVER, from, va("print\n%s\n", SV_GetStringEdString("MP_SVGAME","SERVER_IS_FULL"))); Com_DPrintf ("Rejected a connection.\n"); return; } } // we got a newcl, so reset the reliableSequence and reliableAcknowledge cl->reliableAcknowledge = 0; cl->reliableSequence = 0; gotnewcl: // build a new connection // accept the new client // this is the only place a client_t is ever initialized *newcl = temp; clientNum = newcl - svs.clients; ent = SV_GentityNum( clientNum ); newcl->gentity = ent; // save the challenge newcl->challenge = challenge; // save the address Netchan_Setup (NS_SERVER, &newcl->netchan , from, qport); // save the userinfo Q_strncpyz( newcl->userinfo, userinfo, sizeof(newcl->userinfo) ); // get the game a chance to reject this connection or modify the userinfo denied = (char *)VM_Call( gvm, GAME_CLIENT_CONNECT, clientNum, qtrue, qfalse ); // firstTime = qtrue if ( denied ) { // we can't just use VM_ArgPtr, because that is only valid inside a VM_Call denied = (char *)VM_ExplicitArgPtr( gvm, (int)denied ); NET_OutOfBandPrint( NS_SERVER, from, "print\n%s\n", denied ); Com_DPrintf ("Game rejected a connection: %s.\n", denied); return; } SV_UserinfoChanged( newcl ); // send the connect packet to the client NET_OutOfBandPrint( NS_SERVER, from, "connectResponse" ); Com_DPrintf( "Going from CS_FREE to CS_CONNECTED for %s\n", newcl->name ); newcl->state = CS_CONNECTED; newcl->nextSnapshotTime = svs.time; newcl->lastPacketTime = svs.time; newcl->lastConnectTime = svs.time; // when we receive the first packet from the client, we will // notice that it is from a different serverid and that the // gamestate message was not just sent, forcing a retransmit newcl->gamestateMessageNum = -1; newcl->lastUserInfoChange = 0; //reset the delay newcl->lastUserInfoCount = 0; //reset the count // if this was the first client on the server, or the last client // the server can hold, send a heartbeat to the master. count = 0; for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { if ( svs.clients[i].state >= CS_CONNECTED ) { count++; } } if ( count == 1 || count == sv_maxclients->integer ) { SV_Heartbeat_f(); } }
void SV_DirectConnect( netadr_t from ) { char userinfo[MAX_INFO_STRING]; int i; client_t *cl, *newcl; client_t temp; sharedEntity_t *ent; int clientNum; int qport; int challenge; char *password; int startIndex; intptr_t denied; int count; Com_DPrintf ("SVC_DirectConnect ()\n"); Q_strncpyz( userinfo, Cmd_Argv(1), sizeof(userinfo) ); if ( Q_stricmp( Info_ValueForKey( userinfo, "protocol" ), PROTOCOL_VERSION ) ) { NET_OutOfBandPrint( NS_SERVER, from, "print\nServer uses protocol version "PROTOCOL_VERSION".\n" ); Com_DPrintf (" rejected connect from version %s\n", Info_ValueForKey( userinfo, "protocol" )); return; } challenge = atoi( Info_ValueForKey( userinfo, "challenge" ) ); qport = atoi( Info_ValueForKey( userinfo, "qport" ) ); // quick reject for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { if ( cl->state == CS_FREE ) { continue; } if ( NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) && ( cl->netchan.qport == qport || from.port == cl->netchan.remoteAddress.port ) ) { if (( svs.time - cl->lastConnectTime) < (sv_reconnectlimit->integer * 1000)) { Com_DPrintf ("%s:reconnect rejected : too soon\n", NET_AdrToString (from)); return; } break; } } // see if the challenge is valid (LAN clients don't need to challenge) if ( !NET_IsLocalAddress (from) ) { int ping; for (i=0 ; i<MAX_CHALLENGES ; i++) { if (NET_CompareAdr(from, svs.challenges[i].adr)) { if ( challenge == svs.challenges[i].challenge ) { break; // good } } } if (i == MAX_CHALLENGES) { NET_OutOfBandPrint( NS_SERVER, from, "print\nNo or bad challenge for address.\n" ); return; } // force the IP key/value pair so the game can filter based on ip Info_SetValueForKey( userinfo, "ip", NET_AdrToString( from ) ); ping = svs.time - svs.challenges[i].pingTime; Com_Printf( "Client %i connecting with %i challenge ping\n", i, ping ); svs.challenges[i].connected = qtrue; // never reject a LAN client based on ping if ( !Sys_IsLANAddress( from ) ) { if ( sv_minPing->value && ping < sv_minPing->value ) { // don't let them keep trying until they get a big delay NET_OutOfBandPrint( NS_SERVER, from, "print\nServer is for high pings only\n" ); Com_DPrintf ("Client %i rejected on a too low ping\n", i); // reset the address otherwise their ping will keep increasing // with each connect message and they'd eventually be able to connect svs.challenges[i].adr.port = 0; return; } if ( sv_maxPing->value && ping > sv_maxPing->value ) { NET_OutOfBandPrint( NS_SERVER, from, "print\nServer is for low pings only\n" ); Com_DPrintf ("Client %i rejected on a too high ping\n", i); return; } } } else { // force the "ip" info key to "localhost" Info_SetValueForKey( userinfo, "ip", "localhost" ); } newcl = &temp; Com_Memset (newcl, 0, sizeof(client_t)); // if there is already a slot for this ip, reuse it for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { if ( cl->state == CS_FREE ) { continue; } if ( NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) && ( cl->netchan.qport == qport || from.port == cl->netchan.remoteAddress.port ) ) { Com_Printf ("%s:reconnect\n", NET_AdrToString (from)); newcl = cl; // this doesn't work because it nukes the players userinfo // // disconnect the client from the game first so any flags the // // player might have are dropped // VM_Call( gvm, GAME_CLIENT_DISCONNECT, newcl - svs.clients ); // goto gotnewcl; } } // find a client slot // if "sv_privateClients" is set > 0, then that number // of client slots will be reserved for connections that // have "password" set to the value of "sv_privatePassword" // Info requests will report the maxclients as if the private // slots didn't exist, to prevent people from trying to connect // to a full server. // This is to allow us to reserve a couple slots here on our // servers so we can play without having to kick people. // check for privateClient password password = Info_ValueForKey( userinfo, "password" ); if ( !strcmp( password, sv_privatePassword->string ) ) { startIndex = 0; } else { // skip past the reserved slots startIndex = sv_privateClients->integer; } newcl = NULL; for ( i = startIndex; i < MAX_PLAYERS ; i++ ) { cl = &svs.clients[i]; if (cl->state == CS_FREE) { newcl = cl; break; } } if ( !newcl ) { if ( NET_IsLocalAddress( from ) ) { count = 0; for ( i = startIndex; i < sv_maxclients->integer ; i++ ) { cl = &svs.clients[i]; if (cl->netchan.remoteAddress.type == NA_BOT) { count++; } } // if they're all bots if (count >= sv_maxclients->integer - startIndex) { SV_DropClient(&svs.clients[sv_maxclients->integer - 1], "only bots on server"); newcl = &svs.clients[sv_maxclients->integer - 1]; } else { Com_Error( ERR_FATAL, "server is full on local connect\n" ); return; } } else { NET_OutOfBandPrint( NS_SERVER, from, "print\nServer is full.\n" ); Com_DPrintf ("Rejected a connection.\n"); return; } } // we got a newcl, so reset the reliableSequence and reliableAcknowledge cl->reliableAcknowledge = 0; cl->reliableSequence = 0; gotnewcl: // build a new connection // accept the new client // this is the only place a client_t is ever initialized *newcl = temp; clientNum = newcl - svs.clients; ent = SV_GentityNum( clientNum ); newcl->gentity = ent; // save the challenge newcl->challenge = challenge; // save the address Netchan_Setup (NS_SERVER, &newcl->netchan , from, qport); // init the netchan queue newcl->netchan_end_queue = &newcl->netchan_start_queue; // save the userinfo Q_strncpyz( newcl->userinfo, userinfo, sizeof(newcl->userinfo) ); // get the game a chance to reject this connection or modify the userinfo denied = VM_Call( gvm, GAME_CLIENT_CONNECT, clientNum, qtrue, qfalse ); // firstTime = qtrue if ( denied ) { // we can't just use VM_ArgPtr, because that is only valid inside a VM_Call char *str = VM_ExplicitArgPtr( gvm, denied ); NET_OutOfBandPrint( NS_SERVER, from, "error\n%s\n", str ); Com_DPrintf ("Game rejected a connection: %s.\n", str); return; } SV_UserinfoChanged( newcl ); // send the connect packet to the client NET_OutOfBandPrint( NS_SERVER, from, "connectResponse" ); Com_DPrintf( "Going from CS_FREE to CS_CONNECTED for %s\n", newcl->name ); newcl->state = CS_CONNECTED; newcl->nextSnapshotTime = svs.time; newcl->lastPacketTime = svs.time; newcl->lastConnectTime = svs.time; // when we receive the first packet from the client, we will // notice that it is from a different serverid and that the // gamestate message was not just sent, forcing a retransmit newcl->gamestateMessageNum = -1; // if this was the first client on the server, or the last client // the server can hold, send a heartbeat to the master. count = 0; for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { if ( svs.clients[i].state >= CS_CONNECTED ) { count++; } } if ( count == 1 || count == sv_maxclients->integer ) { SV_Heartbeat_f(); } }
/* ================== SV_DirectConnect A "connect" OOB command has been received ================== */ void SV_DirectConnect( netadr_t from, const Cmd::Args& args ) { char userinfo[ MAX_INFO_STRING ]; int i; client_t *cl, *newcl; client_t temp; sharedEntity_t *ent; int clientNum; int version; int qport; int challenge; const char *password; int startIndex; bool denied; char reason[ MAX_STRING_CHARS ]; int count; const char *ip; #ifdef HAVE_GEOIP const char *country = nullptr; #endif if ( args.Argc() < 2 ) { return; } Log::Debug( "SVC_DirectConnect ()" ); Q_strncpyz( userinfo, args.Argv(1).c_str(), sizeof( userinfo ) ); // DHM - Nerve :: Update Server allows any protocol to connect // NOTE TTimo: but we might need to store the protocol around for potential non http/ftp clients version = atoi( Info_ValueForKey( userinfo, "protocol" ) ); if ( version != PROTOCOL_VERSION ) { NET_OutOfBandPrint( netsrc_t::NS_SERVER, from, "print\nServer uses protocol version %i (yours is %i).", PROTOCOL_VERSION, version ); Log::Debug( " rejected connect from version %i", version ); return; } challenge = atoi( Info_ValueForKey( userinfo, "challenge" ) ); qport = atoi( Info_ValueForKey( userinfo, "qport" ) ); // quick reject for ( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) { // DHM - Nerve :: This check was allowing clients to reconnect after zombietime(2 secs) //if ( cl->state == CS_FREE ) { //continue; //} if ( NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) && ( cl->netchan.qport == qport || from.port == cl->netchan.remoteAddress.port ) ) { if ( ( svs.time - cl->lastConnectTime ) < ( sv_reconnectlimit->integer * 1000 ) ) { Log::Debug( "%s: reconnect rejected: too soon", NET_AdrToString( from ) ); return; } break; } } if ( NET_IsLocalAddress( from ) ) { ip = "localhost"; } else { ip = NET_AdrToString( from ); } if ( ( strlen( ip ) + strlen( userinfo ) + 4 ) >= MAX_INFO_STRING ) { NET_OutOfBandPrint( netsrc_t::NS_SERVER, from, "print\nUserinfo string length exceeded. " "Try removing setu cvars from your config." ); return; } Info_SetValueForKey( userinfo, "ip", ip, false ); // see if the challenge is valid (local clients don't need to challenge) if ( !NET_IsLocalAddress( from ) ) { int ping; for ( i = 0; i < MAX_CHALLENGES; i++ ) { if ( NET_CompareAdr( from, svs.challenges[ i ].adr ) ) { if ( challenge == svs.challenges[ i ].challenge ) { break; // good } } } if ( i == MAX_CHALLENGES ) { NET_OutOfBandPrint( netsrc_t::NS_SERVER, from, "print\n[err_dialog]No or bad challenge for address." ); return; } // force the IP address key/value pair, so the game can filter based on it Info_SetValueForKey( userinfo, "ip", NET_AdrToString( from ), false ); if ( svs.challenges[ i ].firstPing == 0 ) { ping = svs.time - svs.challenges[ i ].pingTime; svs.challenges[ i ].firstPing = ping; } else { ping = svs.challenges[ i ].firstPing; } #ifdef HAVE_GEOIP country = NET_GeoIP_Country( &from ); if ( country ) { Log::Notice( "Client %i connecting from %s with %i challenge ping\n", i, country, ping ); } else { Log::Notice( "Client %i connecting from somewhere unknown with %i challenge ping\n", i, ping ); } #else Log::Notice( "Client %i connecting with %i challenge ping\n", i, ping ); #endif svs.challenges[ i ].connected = true; // never reject a LAN client based on ping if ( !Sys_IsLANAddress( from ) ) { if ( sv_minPing->value && ping < sv_minPing->value ) { NET_OutOfBandPrint( netsrc_t::NS_SERVER, from, "print\n[err_dialog]Server is for high pings only" ); Log::Debug( "Client %i rejected on a too low ping", i ); return; } if ( sv_maxPing->value && ping > sv_maxPing->value ) { NET_OutOfBandPrint( netsrc_t::NS_SERVER, from, "print\n[err_dialog]Server is for low pings only" ); Log::Debug( "Client %i rejected on a too high ping: %i", i, ping ); return; } } } else { // force the "ip" info key to "localhost" Info_SetValueForKey( userinfo, "ip", "localhost", false ); } newcl = &temp; memset( newcl, 0, sizeof( client_t ) ); // if there is already a slot for this IP address, reuse it for ( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) { if ( cl->state == clientState_t::CS_FREE ) { continue; } if ( NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) && ( cl->netchan.qport == qport || from.port == cl->netchan.remoteAddress.port ) ) { Log::Notice( "%s:reconnect\n", NET_AdrToString( from ) ); newcl = cl; // this doesn't work because it nukes the players userinfo // // disconnect the client from the game first so any flags the // // player might have are dropped // VM_Call( gvm, GAME_CLIENT_DISCONNECT, newcl - svs.clients ); // goto gotnewcl; } } // find a client slot // if "sv_privateClients" is set > 0, then that number // of client slots will be reserved for connections that // have "password" set to the value of "sv_privatePassword" // Info requests will report the maxclients as if the private // slots didn't exist, to prevent people from trying to connect // to a full server. // This is to allow us to reserve a couple slots here on our // servers so we can play without having to kick people. // check for privateClient password password = Info_ValueForKey( userinfo, "password" ); if ( !strcmp( password, sv_privatePassword->string ) ) { startIndex = 0; } else { // skip past the reserved slots startIndex = sv_privateClients->integer; } newcl = nullptr; for ( i = startIndex; i < sv_maxclients->integer; i++ ) { cl = &svs.clients[ i ]; if ( cl->state == clientState_t::CS_FREE ) { newcl = cl; break; } } if ( !newcl ) { if ( NET_IsLocalAddress( from ) ) { count = 0; for ( i = startIndex; i < sv_maxclients->integer; i++ ) { cl = &svs.clients[ i ]; if ( SV_IsBot(cl) ) { count++; } } // if they're all bots if ( count >= sv_maxclients->integer - startIndex ) { SV_DropClient( &svs.clients[ sv_maxclients->integer - 1 ], "only bots on server" ); newcl = &svs.clients[ sv_maxclients->integer - 1 ]; } else { Com_Error( errorParm_t::ERR_FATAL, "server is full on local connect" ); } } else { NET_OutOfBandPrint( netsrc_t::NS_SERVER, from, "print\n%s", sv_fullmsg->string ); Log::Debug( "Rejected a connection." ); return; } } // we got a newcl, so reset the reliableSequence and reliableAcknowledge cl->reliableAcknowledge = 0; cl->reliableSequence = 0; gotnewcl: // build a new connection // accept the new client // this is the only place a client_t is ever initialized *newcl = std::move(temp); clientNum = newcl - svs.clients; ent = SV_GentityNum( clientNum ); newcl->gentity = ent; ent->r.svFlags = 0; #ifdef HAVE_GEOIP if ( country ) { Info_SetValueForKey( userinfo, "geoip", country, false ); } #endif // save the challenge newcl->challenge = challenge; // save the address Netchan_Setup( netsrc_t::NS_SERVER, &newcl->netchan, from, qport ); // init the netchan queue // Save the pubkey Q_strncpyz( newcl->pubkey, Info_ValueForKey( userinfo, "pubkey" ), sizeof( newcl->pubkey ) ); Info_RemoveKey( userinfo, "pubkey", false ); // save the userinfo Q_strncpyz( newcl->userinfo, userinfo, sizeof( newcl->userinfo ) ); // get the game a chance to reject this connection or modify the userinfo denied = gvm.GameClientConnect( reason, sizeof( reason ), clientNum, true, false ); // firstTime = true if ( denied ) { NET_OutOfBandPrint( netsrc_t::NS_SERVER, from, "print\n[err_dialog]%s", reason ); Log::Debug( "Game rejected a connection: %s.", reason ); return; } SV_UserinfoChanged( newcl ); // DHM - Nerve :: Clear out firstPing now that client is connected svs.challenges[ i ].firstPing = 0; // send the connect packet to the client NET_OutOfBandPrint( netsrc_t::NS_SERVER, from, "connectResponse" ); Log::Debug( "Going from CS_FREE to CS_CONNECTED for %s", newcl->name ); newcl->state = clientState_t::CS_CONNECTED; newcl->nextSnapshotTime = svs.time; newcl->lastPacketTime = svs.time; newcl->lastConnectTime = svs.time; // when we receive the first packet from the client, we will // notice that it is from a different serverid and that the // gamestate message was not just sent, forcing a retransmit newcl->gamestateMessageNum = -1; // if this was the first client on the server, or the last client // the server can hold, send a heartbeat to the master. count = 0; for ( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) { if ( svs.clients[ i ].state >= clientState_t::CS_CONNECTED ) { count++; } } if ( count == 1 || count == sv_maxclients->integer ) { SV_Heartbeat_f(); } }
/** * @brief The current net_message is parsed for the given client */ void SV_ExecuteClientMessage (client_t * cl, int cmd, struct dbuffer *msg) { if (cmd == -1) return; switch (cmd) { default: Com_Printf("SV_ExecuteClientMessage: unknown command char '%d'\n", cmd); SV_DropClient(cl, "Unknown command\n"); return; case clc_nop: break; case clc_ack: cl->lastmessage = svs.realtime; break; case clc_userinfo: NET_ReadString(msg, cl->userinfo, sizeof(cl->userinfo)); Com_DPrintf(DEBUG_SERVER, "userinfo from client: %s\n", cl->userinfo); SV_UserinfoChanged(cl); break; case clc_stringcmd: { char str[1024]; NET_ReadString(msg, str, sizeof(str)); Com_DPrintf(DEBUG_SERVER, "stringcmd from client: %s\n", str); SV_ExecuteUserCommand(cl, str); if (cl->state == cs_free) return; /* disconnect command */ break; } case clc_action: /* client actions are handled by the game module */ sv->messageBuffer = msg; TH_MutexLock(svs.serverMutex); svs.ge->ClientAction(cl->player); TH_MutexUnlock(svs.serverMutex); sv->messageBuffer = NULL; break; case clc_endround: /* player wants to end round */ sv->messageBuffer = msg; TH_MutexLock(svs.serverMutex); svs.ge->ClientEndRound(cl->player); TH_MutexUnlock(svs.serverMutex); sv->messageBuffer = NULL; break; case clc_teaminfo: /* player sends team info */ /* actors spawn accordingly */ sv->messageBuffer = msg; TH_MutexLock(svs.serverMutex); svs.ge->ClientTeamInfo(cl->player); TH_MutexUnlock(svs.serverMutex); sv->messageBuffer = NULL; SV_SetClientState(cl, cs_spawned); break; case clc_initactorstates: /* player sends team info */ /* actors spawn accordingly */ sv->messageBuffer = msg; TH_MutexLock(svs.serverMutex); svs.ge->ClientInitActorStates(cl->player); TH_MutexUnlock(svs.serverMutex); sv->messageBuffer = NULL; break; } }
void SV_DirectConnect( netadr_t from ) { char userinfo[MAX_INFO_STRING]; int i; client_t *cl, *newcl; client_t temp; sharedEntity_t *ent; int clientNum; int version; int qport; int challenge; char *password; int startIndex; intptr_t denied; int count; char *ip; const char *stringEd; #ifdef LEGACY_PROTOCOL qboolean compat = qfalse; #endif Com_DPrintf ("SVC_DirectConnect ()\n"); // Check whether this client is banned. if(SV_IsBanned(&from, qfalse)) { NET_OutOfBandPrint(NS_SERVER, from, "print\nYou are banned from this server.\n"); return; } Q_strncpyz( userinfo, Cmd_Argv(1), sizeof(userinfo) ); version = atoi(Info_ValueForKey(userinfo, "protocol")); #ifdef LEGACY_PROTOCOL if(version > 0 && com_legacyprotocol->integer == version) compat = qtrue; else #endif { if(version != com_protocol->integer) { NET_OutOfBandPrint(NS_SERVER, from, "print\nServer uses protocol version %i " "(yours is %i).\n", com_protocol->integer, version); Com_DPrintf(" rejected connect from version %i\n", version); return; } } challenge = atoi( Info_ValueForKey( userinfo, "challenge" ) ); qport = atoi( Info_ValueForKey( userinfo, "qport" ) ); // quick reject for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { if ( cl->state == CS_FREE ) { continue; } if ( NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) && ( cl->netchan.qport == qport || from.port == cl->netchan.remoteAddress.port ) ) { if (( svs.time - cl->lastConnectTime) < (sv_reconnectlimit->integer * 1000)) { Com_DPrintf ("%s:reconnect rejected : too soon\n", NET_AdrToString (from)); return; } break; } } // don't let "ip" overflow userinfo string if ( NET_IsLocalAddress (from) ) ip = "localhost"; else ip = (char *)NET_AdrToString( from ); if( ( strlen( ip ) + strlen( userinfo ) + 4 ) >= MAX_INFO_STRING ) { NET_OutOfBandPrint( NS_SERVER, from, "print\nUserinfo string length exceeded. " "Try removing setu cvars from your config.\n" ); return; } Info_SetValueForKey( userinfo, "ip", ip ); // see if the challenge is valid (LAN clients don't need to challenge) if (!NET_IsLocalAddress(from)) { int ping; challenge_t *challengeptr; for (i=0; i<MAX_CHALLENGES; i++) { if (NET_CompareAdr(from, svs.challenges[i].adr)) { if(challenge == svs.challenges[i].challenge) break; } } if (i == MAX_CHALLENGES) { NET_OutOfBandPrint( NS_SERVER, from, "print\nNo or bad challenge for your address.\n" ); return; } challengeptr = &svs.challenges[i]; if(challengeptr->wasrefused) { // Return silently, so that error messages written by the server keep being displayed. return; } ping = svs.time - challengeptr->pingTime; // never reject a LAN client based on ping if ( !Sys_IsLANAddress( from ) ) { if ( sv_minPing->value && ping < sv_minPing->value ) { NET_OutOfBandPrint( NS_SERVER, from, "print\n%s", SV_StringEdString("SERVER_FOR_HIGH_PING") ); stringEd = SV_GetString("CLIENT_REJECTED_LOW_PING"); Com_DPrintf (stringEd, i); challengeptr->wasrefused = qtrue; return; } if ( sv_maxPing->value && ping > sv_maxPing->value ) { NET_OutOfBandPrint( NS_SERVER, from, "print\n%s", SV_StringEdString("SERVER_FOR_LOW_PING") ); stringEd = SV_GetString("CLIENT_REJECTED_LOW_PING"); Com_DPrintf (stringEd, i); challengeptr->wasrefused = qtrue; return; } } stringEd = SV_GetString("CLIENT_CONN_WITH_PING"); Com_Printf(stringEd, i, ping); challengeptr->connected = qtrue; } newcl = &temp; Com_Memset (newcl, 0, sizeof(client_t)); // if there is already a slot for this ip, reuse it for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { if ( cl->state == CS_FREE ) { continue; } if ( NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) && ( cl->netchan.qport == qport || from.port == cl->netchan.remoteAddress.port ) ) { Com_Printf ("%s:reconnect\n", NET_AdrToString (from)); newcl = cl; // this doesn't work because it nukes the players userinfo // // disconnect the client from the game first so any flags the // // player might have are dropped // VM_Call( gvm, GAME_CLIENT_DISCONNECT, newcl - svs.clients ); // goto gotnewcl; } } // find a client slot // if "sv_privateClients" is set > 0, then that number // of client slots will be reserved for connections that // have "password" set to the value of "sv_privatePassword" // Info requests will report the maxclients as if the private // slots didn't exist, to prevent people from trying to connect // to a full server. // This is to allow us to reserve a couple slots here on our // servers so we can play without having to kick people. // check for privateClient password password = Info_ValueForKey( userinfo, "password" ); if ( !strcmp( password, sv_privatePassword->string ) ) { startIndex = 0; } else { // skip past the reserved slots startIndex = sv_privateClients->integer; } newcl = NULL; for ( i = startIndex; i < sv_maxclients->integer ; i++ ) { cl = &svs.clients[i]; if (cl->state == CS_FREE) { newcl = cl; break; } } if ( !newcl ) { if ( NET_IsLocalAddress( from ) ) { count = 0; for ( i = startIndex; i < sv_maxclients->integer ; i++ ) { cl = &svs.clients[i]; if (cl->netchan.remoteAddress.type == NA_BOT) { count++; } } // if they're all bots if (count >= sv_maxclients->integer - startIndex) { SV_DropClient(&svs.clients[sv_maxclients->integer - 1], "only bots on server"); newcl = &svs.clients[sv_maxclients->integer - 1]; } else { Com_Error( ERR_FATAL, "server is full on local connect" ); return; } } else { NET_OutOfBandPrint( NS_SERVER, from, "print\n%s\n", SV_StringEdString("SERVER_IS_FULL") ); Com_DPrintf ("Rejected a connection.\n"); return; } } // we got a newcl, so reset the reliableSequence and reliableAcknowledge cl->reliableAcknowledge = 0; cl->reliableSequence = 0; gotnewcl: // build a new connection // accept the new client // this is the only place a client_t is ever initialized *newcl = temp; clientNum = newcl - svs.clients; ent = SV_GentityNum( clientNum ); newcl->gentity = ent; // save the challenge newcl->challenge = challenge; // save the address #ifdef LEGACY_PROTOCOL newcl->compat = compat; Netchan_Setup(NS_SERVER, &newcl->netchan, from, qport, challenge, compat); #else Netchan_Setup(NS_SERVER, &newcl->netchan, from, qport, challenge, qfalse); #endif // init the netchan queue newcl->netchan_end_queue = &newcl->netchan_start_queue; // save the userinfo Q_strncpyz( newcl->userinfo, userinfo, sizeof(newcl->userinfo) ); // get the game a chance to reject this connection or modify the userinfo denied = VM_Call( gvm, GAME_CLIENT_CONNECT, clientNum, qtrue, qfalse ); // firstTime = qtrue if ( denied ) { // we can't just use VM_ArgPtr, because that is only valid inside a VM_Call char *str = VM_ExplicitArgPtr( gvm, denied ); NET_OutOfBandPrint( NS_SERVER, from, "print\n%s\n", str ); Com_DPrintf ("Game rejected a connection: %s.\n", str); return; } SV_UserinfoChanged( newcl ); // send the connect packet to the client NET_OutOfBandPrint(NS_SERVER, from, "connectResponse %d", challenge); Com_DPrintf( "Going from CS_FREE to CS_CONNECTED for %s\n", newcl->name ); newcl->state = CS_CONNECTED; newcl->lastSnapshotTime = 0; newcl->lastPacketTime = svs.time; newcl->lastConnectTime = svs.time; // when we receive the first packet from the client, we will // notice that it is from a different serverid and that the // gamestate message was not just sent, forcing a retransmit newcl->gamestateMessageNum = -1; // if this was the first client on the server, or the last client // the server can hold, send a heartbeat to the master. count = 0; for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { if ( svs.clients[i].state >= CS_CONNECTED ) { count++; } } if ( count == 1 || count == sv_maxclients->integer ) { SV_Heartbeat_f(); } }
/* * ================== * SVC_DirectConnect * * A connection request that did not come from the master * ================== */ void SVC_DirectConnect(void) { char userinfo[MAX_INFO_STRING]; netadr_t adr; int i; client_t *cl, *newcl; client_t temp; edict_t *ent; int edictnum; int version; int qport; int challenge; int previousclients; // rich: connection limit per IP adr = net_from; Com_DPrintf("SVC_DirectConnect ()\n"); version = atoi(Cmd_Argv(1)); if (version != PROTOCOL_VERSION) { Netchan_OutOfBandPrint(NS_SERVER, adr, "print\nServer is version %4.2f.\n", VERSION); Com_DPrintf(" rejected connect from version %i\n", version); return; } qport = atoi(Cmd_Argv(2)); challenge = atoi(Cmd_Argv(3)); // r1ch: limit connections from a single IP previousclients = 0; for (i = 0, cl = svs.clients; i < (int)maxclients->value; i++, cl++) { if (cl->state == cs_free) { continue; } if (NET_CompareBaseAdr(adr, cl->netchan.remote_address)) { // r1ch: zombies are less dangerous if (cl->state == cs_zombie) { previousclients++; } else { previousclients += 2; } } } if (previousclients >= (int)sv_iplimit->value * 2) { Netchan_OutOfBandPrint(NS_SERVER, adr, "print\nToo many connections from your host.\n"); Com_DPrintf(" too many connections\n"); return; } // end r1ch fix strncpy(userinfo, Cmd_Argv(4), sizeof(userinfo) - 1); userinfo[sizeof(userinfo) - 1] = 0; // force the IP key/value pair so the game can filter based on ip Info_SetValueForKey(userinfo, "ip", NET_AdrToString(net_from)); // attractloop servers are ONLY for local clients if (sv.attractloop) { if (!NET_IsLocalAddress(adr)) { Com_Printf("Remote connect in attract loop. Ignored.\n"); Netchan_OutOfBandPrint(NS_SERVER, adr, "print\nConnection refused.\n"); return; } } // see if the challenge is valid if (!NET_IsLocalAddress(adr)) { for (i = 0; i < MAX_CHALLENGES; i++) { if (NET_CompareBaseAdr(net_from, svs.challenges[i].adr)) { if (challenge == svs.challenges[i].challenge) { break; // good } Netchan_OutOfBandPrint(NS_SERVER, adr, "print\nBad challenge.\n"); return; } } if (i == MAX_CHALLENGES) { Netchan_OutOfBandPrint(NS_SERVER, adr, "print\nNo challenge for address.\n"); return; } } newcl = &temp; memset(newcl, 0, sizeof(client_t)); // if there is already a slot for this ip, reuse it for (i = 0, cl = svs.clients; i < maxclients->value; i++, cl++) { if (cl->state == cs_free) { continue; } if (NET_CompareBaseAdr(adr, cl->netchan.remote_address) && ((cl->netchan.qport == qport) || (adr.port == cl->netchan.remote_address.port))) { if (!NET_IsLocalAddress(adr) && ((svs.realtime - cl->lastconnect) < ((int)sv_reconnect_limit->value * 1000))) { Com_DPrintf("%s:reconnect rejected : too soon\n", NET_AdrToString(adr)); return; } // r1ch: !! fix nasty bug where non-disconnected clients (from dropped disconnect // packets) could be overwritten! if (cl->state != cs_zombie) { Com_DPrintf(" client already found\n"); // If we legitly get here, spoofed udp isn't possible (passed challenge) and client addr/port combo // is exactly the same, so we can assume its really a dropped/crashed client. i hope... Com_Printf("Dropping %s, ghost reconnect\n", cl->name); SV_DropClient(cl); } // end r1ch fix Com_Printf("%s:reconnect\n", NET_AdrToString(adr)); SV_CleanClient(cl); // r1ch: clean up last client data newcl = cl; goto gotnewcl; } } // find a client slot newcl = NULL; for (i = 0, cl = svs.clients; i < maxclients->value; i++, cl++) { if (cl->state == cs_free) { newcl = cl; break; } } if (!newcl) { Netchan_OutOfBandPrint(NS_SERVER, adr, "print\nServer is full.\n"); Com_DPrintf("Rejected a connection.\n"); return; } gotnewcl: // build a new connection // accept the new client // this is the only place a client_t is ever initialized *newcl = temp; sv_client = newcl; edictnum = (newcl - svs.clients) + 1; ent = EDICT_NUM(edictnum); newcl->edict = ent; newcl->challenge = challenge; // save challenge for checksumming // get the game a chance to reject this connection or modify the userinfo if (!(ge->ClientConnect(ent, userinfo))) { if (*Info_ValueForKey(userinfo, "rejmsg")) { Netchan_OutOfBandPrint(NS_SERVER, adr, "print\n%s\nConnection refused.\n", Info_ValueForKey(userinfo, "rejmsg")); } else { Netchan_OutOfBandPrint(NS_SERVER, adr, "print\nConnection refused.\n"); } Com_DPrintf("Game rejected a connection.\n"); return; } // parse some info from the info strings strncpy(newcl->userinfo, userinfo, sizeof(newcl->userinfo) - 1); SV_UserinfoChanged(newcl); // send the connect packet to the client Netchan_OutOfBandPrint(NS_SERVER, adr, "client_connect"); Netchan_Setup(NS_SERVER, &newcl->netchan, adr, qport); newcl->state = cs_connected; SZ_Init(&newcl->datagram, newcl->datagram_buf, sizeof(newcl->datagram_buf)); newcl->datagram.allowoverflow = true; newcl->lastmessage = svs.realtime; // don't timeout newcl->lastconnect = svs.realtime; }
/* ================== SV_DirectConnect A "connect" OOB command has been received ================== */ void SV_DirectConnect( netadr_t from ) { char userinfo[MAX_INFO_STRING]; int i; client_t *cl, *newcl; client_t temp; sharedEntity_t *ent; int clientNum; int version; int qport; int challenge; char *password; int startIndex; char *denied; int count; char *ip; Com_DPrintf ("SVC_DirectConnect ()\n"); // Check whether this client is banned. if ( SV_IsBanned( &from, qfalse ) ) { NET_OutOfBandPrint( NS_SERVER, from, "print\nYou are banned from this server.\n" ); Com_DPrintf( " rejected connect from %s (banned)\n", NET_AdrToString(from) ); return; } Q_strncpyz( userinfo, Cmd_Argv(1), sizeof(userinfo) ); version = atoi( Info_ValueForKey( userinfo, "protocol" ) ); if ( version != PROTOCOL_VERSION ) { NET_OutOfBandPrint( NS_SERVER, from, "print\nServer uses protocol version %i (yours is %i).\n", PROTOCOL_VERSION, version ); Com_DPrintf (" rejected connect from version %i\n", version); return; } challenge = atoi( Info_ValueForKey( userinfo, "challenge" ) ); qport = atoi( Info_ValueForKey( userinfo, "qport" ) ); // quick reject for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { /* This was preventing sv_reconnectlimit from working. It seems like commenting this out has solved the problem. HOwever, if there is a future problem then it could be this. if ( cl->state == CS_FREE ) { continue; } */ if ( NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) && ( cl->netchan.qport == qport || from.port == cl->netchan.remoteAddress.port ) ) { if (( svs.time - cl->lastConnectTime) < (sv_reconnectlimit->integer * 1000)) { NET_OutOfBandPrint( NS_SERVER, from, "print\nReconnect rejected : too soon\n" ); Com_DPrintf ("%s:reconnect rejected : too soon\n", NET_AdrToString (from)); return; } break; } } // don't let "ip" overflow userinfo string if ( NET_IsLocalAddress (from) ) ip = "localhost"; else ip = (char *)NET_AdrToString( from ); if( ( strlen( ip ) + strlen( userinfo ) + 4 ) >= MAX_INFO_STRING ) { NET_OutOfBandPrint( NS_SERVER, from, "print\nUserinfo string length exceeded. " "Try removing setu cvars from your config.\n" ); return; } Info_SetValueForKey( userinfo, "ip", ip ); // see if the challenge is valid (localhost clients don't need to challenge) if (!NET_IsLocalAddress(from)) { // Verify the received challenge against the expected challenge if (!SV_VerifyChallenge(challenge, from)) { NET_OutOfBandPrint( NS_SERVER, from, "print\nIncorrect challenge for your address.\n" ); return; } } newcl = &temp; Com_Memset (newcl, 0, sizeof(client_t)); // if there is already a slot for this ip, reuse it for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { if ( cl->state == CS_FREE ) { continue; } if ( NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) && ( cl->netchan.qport == qport || from.port == cl->netchan.remoteAddress.port ) ) { Com_Printf ("%s:reconnect\n", NET_AdrToString (from)); newcl = cl; // VVFIXME - both SOF2 and Wolf remove this call, claiming it blows away the user's info // disconnect the client from the game first so any flags the // player might have are dropped GVM_ClientDisconnect( newcl - svs.clients ); // goto gotnewcl; } } // find a client slot // if "sv_privateClients" is set > 0, then that number // of client slots will be reserved for connections that // have "password" set to the value of "sv_privatePassword" // Info requests will report the maxclients as if the private // slots didn't exist, to prevent people from trying to connect // to a full server. // This is to allow us to reserve a couple slots here on our // servers so we can play without having to kick people. // check for privateClient password password = Info_ValueForKey( userinfo, "password" ); if ( !strcmp( password, sv_privatePassword->string ) ) { startIndex = 0; } else { // skip past the reserved slots startIndex = sv_privateClients->integer; } newcl = NULL; for ( i = startIndex; i < sv_maxclients->integer ; i++ ) { cl = &svs.clients[i]; if (cl->state == CS_FREE) { newcl = cl; break; } } if ( !newcl ) { if ( NET_IsLocalAddress( from ) ) { count = 0; for ( i = startIndex; i < sv_maxclients->integer ; i++ ) { cl = &svs.clients[i]; if (cl->netchan.remoteAddress.type == NA_BOT) { count++; } } // if they're all bots if (count >= sv_maxclients->integer - startIndex) { SV_DropClient(&svs.clients[sv_maxclients->integer - 1], "only bots on server"); newcl = &svs.clients[sv_maxclients->integer - 1]; } else { Com_Error( ERR_FATAL, "server is full on local connect\n" ); return; } } else { const char *SV_GetStringEdString(char *refSection, char *refName); NET_OutOfBandPrint( NS_SERVER, from, va("print\n%s\n", SV_GetStringEdString("MP_SVGAME","SERVER_IS_FULL"))); Com_DPrintf ("Rejected a connection.\n"); return; } } // we got a newcl, so reset the reliableSequence and reliableAcknowledge cl->reliableAcknowledge = 0; cl->reliableSequence = 0; gotnewcl: // build a new connection // accept the new client // this is the only place a client_t is ever initialized *newcl = temp; clientNum = newcl - svs.clients; ent = SV_GentityNum( clientNum ); newcl->gentity = ent; // save the challenge newcl->challenge = challenge; // save the address Netchan_Setup (NS_SERVER, &newcl->netchan , from, qport); // save the userinfo Q_strncpyz( newcl->userinfo, userinfo, sizeof(newcl->userinfo) ); // get the game a chance to reject this connection or modify the userinfo denied = GVM_ClientConnect( clientNum, qtrue, qfalse ); // firstTime = qtrue if ( denied ) { NET_OutOfBandPrint( NS_SERVER, from, "print\n%s\n", denied ); Com_DPrintf ("Game rejected a connection: %s.\n", denied); return; } SV_UserinfoChanged( newcl ); // send the connect packet to the client NET_OutOfBandPrint( NS_SERVER, from, "connectResponse" ); Com_DPrintf( "Going from CS_FREE to CS_CONNECTED for %s\n", newcl->name ); newcl->state = CS_CONNECTED; newcl->nextSnapshotTime = svs.time; newcl->lastPacketTime = svs.time; newcl->lastConnectTime = svs.time; // when we receive the first packet from the client, we will // notice that it is from a different serverid and that the // gamestate message was not just sent, forcing a retransmit newcl->gamestateMessageNum = -1; newcl->lastUserInfoChange = 0; //reset the delay newcl->lastUserInfoCount = 0; //reset the count // if this was the first client on the server, or the last client // the server can hold, send a heartbeat to the master. count = 0; for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { if ( svs.clients[i].state >= CS_CONNECTED ) { count++; } } if ( count == 1 || count == sv_maxclients->integer ) { SV_Heartbeat_f(); } }
/* ================== SV_DirectConnect A "connect" OOB command has been received ================== */ void SV_DirectConnect( netadr_t from, const Cmd::Args& args ) { if ( args.Argc() < 2 ) { return; } Log::Debug( "SVC_DirectConnect ()" ); InfoMap userinfo = InfoStringToMap(args.Argv(1)); // DHM - Nerve :: Update Server allows any protocol to connect // NOTE TTimo: but we might need to store the protocol around for potential non http/ftp clients int version = atoi( userinfo["protocol"].c_str() ); if ( version != PROTOCOL_VERSION ) { Net::OutOfBandPrint( netsrc_t::NS_SERVER, from, "print\nServer uses protocol version %i (yours is %i).", PROTOCOL_VERSION, version ); Log::Debug( " rejected connect from version %i", version ); return; } int qport = atoi( userinfo["qport"].c_str() ); auto clients_begin = svs.clients; auto clients_end = clients_begin + sv_maxclients->integer; client_t* reconnecting = std::find_if(clients_begin, clients_end, [&from, qport](const client_t& client) { return NET_CompareBaseAdr( from, client.netchan.remoteAddress ) && ( client.netchan.qport == qport || from.port == client.netchan.remoteAddress.port ); } ); if ( reconnecting != clients_end && svs.time - reconnecting->lastConnectTime < sv_reconnectlimit->integer * 1000 ) { Log::Debug( "%s: reconnect rejected: too soon", NET_AdrToString( from ) ); return; } if ( NET_IsLocalAddress( from ) ) { userinfo["ip"] = "loopback"; } else { // see if the challenge is valid (local clients don't need to challenge) Challenge::Duration ping_duration; if ( !ChallengeManager::MatchString( from, userinfo["challenge"], &ping_duration ) ) { Net::OutOfBandPrint( netsrc_t::NS_SERVER, from, "print\n[err_dialog]No or bad challenge for address." ); return; } userinfo["ip"] = NET_AdrToString( from ); } client_t *new_client = nullptr; // if there is already a slot for this IP address, reuse it if ( reconnecting != clients_end ) { Log::Notice( "%s:reconnect\n", NET_AdrToString( from ) ); new_client = reconnecting; } else { // find a client slot // if "sv_privateClients" is set > 0, then that number // of client slots will be reserved for connections that // have "password" set to the value of "sv_privatePassword" // Info requests will report the maxclients as if the private // slots didn't exist, to prevent people from trying to connect // to a full server. // This is to allow us to reserve a couple slots here on our // servers so we can play without having to kick people. // check for privateClient password int startIndex = 0; if ( userinfo["password"] != sv_privatePassword->string ) { // skip past the reserved slots startIndex = sv_privateClients->integer; } new_client = std::find_if(clients_begin, clients_end, [](const client_t& client) { return client.state == clientState_t::CS_FREE; }); if ( new_client == clients_end ) { if ( NET_IsLocalAddress( from ) ) { int count = std::count_if(clients_begin+startIndex, clients_end, [](const client_t& client) { return SV_IsBot(&client); } ); // if they're all bots if ( count >= sv_maxclients->integer - startIndex ) { SV_DropClient( &svs.clients[ sv_maxclients->integer - 1 ], "only bots on server" ); new_client = &svs.clients[ sv_maxclients->integer - 1 ]; } else { Com_Error( errorParm_t::ERR_FATAL, "server is full on local connect" ); } } else { Net::OutOfBandPrint( netsrc_t::NS_SERVER, from, "print\n%s", sv_fullmsg->string ); Log::Debug( "Rejected a connection." ); return; } } } // build a new connection // accept the new client // this is the only place a client_t is ever initialized memset( new_client, 0, sizeof( client_t ) ); int clientNum = new_client - svs.clients; #ifdef HAVE_GEOIP const char * country = NET_GeoIP_Country( &from ); if ( country ) { Log::Notice( "Client %i connecting from %s\n", clientNum, country ); userinfo["geoip"] = country; } else { Log::Notice( "Client %i connecting from somewhere unknown\n", clientNum ); } #else Log::Notice( "Client %i connecting\n", clientNum ); #endif new_client->gentity = SV_GentityNum( clientNum ); new_client->gentity->r.svFlags = 0; // save the address Netchan_Setup( netsrc_t::NS_SERVER, &new_client->netchan, from, qport ); // init the netchan queue // Save the pubkey Q_strncpyz( new_client->pubkey, userinfo["pubkey"].c_str(), sizeof( new_client->pubkey ) ); userinfo.erase("pubkey"); // save the userinfo Q_strncpyz( new_client->userinfo, InfoMapToString(userinfo).c_str(), sizeof( new_client->userinfo ) ); // get the game a chance to reject this connection or modify the userinfo char reason[ MAX_STRING_CHARS ]; if ( gvm.GameClientConnect( reason, sizeof( reason ), clientNum, true, false ) ) { Net::OutOfBandPrint( netsrc_t::NS_SERVER, from, "print\n[err_dialog]%s", reason ); Log::Debug( "Game rejected a connection: %s.", reason ); return; } SV_UserinfoChanged( new_client ); // send the connect packet to the client Net::OutOfBandPrint( netsrc_t::NS_SERVER, from, "connectResponse" ); Log::Debug( "Going from CS_FREE to CS_CONNECTED for %s", new_client->name ); new_client->state = clientState_t::CS_CONNECTED; new_client->nextSnapshotTime = svs.time; new_client->lastPacketTime = svs.time; new_client->lastConnectTime = svs.time; // when we receive the first packet from the client, we will // notice that it is from a different serverid and that the // gamestate message was not just sent, forcing a retransmit new_client->gamestateMessageNum = -1; // if this was the first client on the server, or the last client // the server can hold, send a heartbeat to the master. int count = std::count_if(clients_begin, clients_end, [](const client_t& client) { return client.state >= clientState_t::CS_CONNECTED; }); if ( count == 1 || count == sv_maxclients->integer ) { SV_Heartbeat_f(); } }
/* * A connection request that did not come from the master */ void SVC_DirectConnect(void) { char userinfo[MAX_INFO_STRING]; netadr_t adr; int i; client_t *cl, *newcl; client_t temp; edict_t *ent; int edictnum; int version; int qport; int challenge; adr = net_from; Com_DPrintf("SVC_DirectConnect ()\n"); version = (int)strtol(Cmd_Argv(1), (char **)NULL, 10); if (version != PROTOCOL_VERSION) { Netchan_OutOfBandPrint(NS_SERVER, adr, "print\nServer is version %s.\n", YQ2VERSION); Com_DPrintf(" rejected connect from version %i\n", version); return; } qport = (int)strtol(Cmd_Argv(2), (char **)NULL, 10); challenge = (int)strtol(Cmd_Argv(3), (char **)NULL, 10); Q_strlcpy(userinfo, Cmd_Argv(4), sizeof(userinfo)); /* force the IP key/value pair so the game can filter based on ip */ Info_SetValueForKey(userinfo, "ip", NET_AdrToString(net_from)); /* attractloop servers are ONLY for local clients */ if (sv.attractloop) { if (!NET_IsLocalAddress(adr)) { Com_Printf("Remote connect in attract loop. Ignored.\n"); Netchan_OutOfBandPrint(NS_SERVER, adr, "print\nConnection refused.\n"); return; } } /* see if the challenge is valid */ if (!NET_IsLocalAddress(adr)) { for (i = 0; i < MAX_CHALLENGES; i++) { if (NET_CompareBaseAdr(net_from, svs.challenges[i].adr)) { if (challenge == svs.challenges[i].challenge) { break; /* good */ } Netchan_OutOfBandPrint(NS_SERVER, adr, "print\nBad challenge.\n"); return; } } if (i == MAX_CHALLENGES) { Netchan_OutOfBandPrint(NS_SERVER, adr, "print\nNo challenge for address.\n"); return; } } newcl = &temp; memset(newcl, 0, sizeof(client_t)); /* if there is already a slot for this ip, reuse it */ for (i = 0, cl = svs.clients; i < maxclients->value; i++, cl++) { if (cl->state < cs_connected) { continue; } if (NET_CompareBaseAdr(adr, cl->netchan.remote_address) && ((cl->netchan.qport == qport) || (adr.port == cl->netchan.remote_address.port))) { if (!NET_IsLocalAddress(adr)) { Com_DPrintf("%s:reconnect rejected : too soon\n", NET_AdrToString(adr)); return; } Com_Printf("%s:reconnect\n", NET_AdrToString(adr)); newcl = cl; goto gotnewcl; } } /* find a client slot */ newcl = NULL; for (i = 0, cl = svs.clients; i < maxclients->value; i++, cl++) { if (cl->state == cs_free) { newcl = cl; break; } } if (!newcl) { Netchan_OutOfBandPrint(NS_SERVER, adr, "print\nServer is full.\n"); Com_DPrintf("Rejected a connection.\n"); return; } gotnewcl: /* build a new connection accept the new client this is the only place a client_t is ever initialized */ *newcl = temp; sv_client = newcl; edictnum = (newcl - svs.clients) + 1; ent = EDICT_NUM(edictnum); newcl->edict = ent; newcl->challenge = challenge; /* save challenge for checksumming */ /* get the game a chance to reject this connection or modify the userinfo */ if (!(ge->ClientConnect(ent, userinfo))) { if (*Info_ValueForKey(userinfo, "rejmsg")) { Netchan_OutOfBandPrint(NS_SERVER, adr, "print\n%s\nConnection refused.\n", Info_ValueForKey(userinfo, "rejmsg")); } else { Netchan_OutOfBandPrint(NS_SERVER, adr, "print\nConnection refused.\n"); } Com_DPrintf("Game rejected a connection.\n"); return; } /* parse some info from the info strings */ Q_strlcpy(newcl->userinfo, userinfo, sizeof(newcl->userinfo)); SV_UserinfoChanged(newcl); /* send the connect packet to the client */ Netchan_OutOfBandPrint(NS_SERVER, adr, "client_connect"); Netchan_Setup(NS_SERVER, &newcl->netchan, adr, qport); newcl->state = cs_connected; SZ_Init(&newcl->datagram, newcl->datagram_buf, sizeof(newcl->datagram_buf)); newcl->datagram.allowoverflow = true; newcl->lastmessage = svs.realtime; /* don't timeout */ newcl->lastconnect = svs.realtime; }
/* * SV_ClientConnect * accept the new client * this is the only place a client_t is ever initialized */ qboolean SV_ClientConnect( const socket_t *socket, const netadr_t *address, client_t *client, char *userinfo, int game_port, int challenge, qboolean fakeClient, qboolean tvClient, unsigned int ticket_id, int session_id ) { int i; edict_t *ent; int edictnum; edictnum = ( client - svs.clients ) + 1; ent = EDICT_NUM( edictnum ); // give mm a chance to reject if the server is locked ready for mm // must be called before ge->ClientConnect // ch : rly ignore fakeClient and tvClient here? session_id = SV_MM_ClientConnect( address, userinfo, ticket_id, session_id ); if( !session_id ) return qfalse; // we need to set local sessions to userinfo ourselves if( session_id < 0 ) Info_SetValueForKey( userinfo, "cl_mm_session", va("%d", session_id) ); // get the game a chance to reject this connection or modify the userinfo if( !ge->ClientConnect( ent, userinfo, fakeClient, tvClient ) ) return qfalse; // the connection is accepted, set up the client slot memset( client, 0, sizeof( *client ) ); client->edict = ent; client->challenge = challenge; // save challenge for checksumming client->tvclient = tvClient; client->mm_session = session_id; client->mm_ticket = ticket_id; if( socket ) { switch( socket->type ) { #ifdef TCP_ALLOW_CONNECT case SOCKET_TCP: client->reliable = qtrue; client->individual_socket = qtrue; client->socket = *socket; break; #endif case SOCKET_UDP: case SOCKET_LOOPBACK: client->reliable = qfalse; client->individual_socket = qfalse; client->socket.open = qfalse; break; default: assert( qfalse ); } } else { assert( fakeClient ); client->reliable = qfalse; client->individual_socket = qfalse; client->socket.open = qfalse; } SV_ClientResetCommandBuffers( client ); // reset timeouts client->lastPacketReceivedTime = svs.realtime; client->lastconnect = svs.realtime; // init the connection client->state = CS_CONNECTING; if( fakeClient ) { client->netchan.remoteAddress.type = NA_NOTRANSMIT; // fake-clients can't transmit // TODO: if mm_debug_reportbots Info_SetValueForKey( userinfo, "cl_mm_session", va("%d", client->mm_session) ); } else { if( client->individual_socket ) { Netchan_Setup( &client->netchan, &client->socket, address, game_port ); } else { Netchan_Setup( &client->netchan, socket, address, game_port ); } } // create default rating for the client and current gametype ge->AddDefaultRating( ent, NULL ); // parse some info from the info strings Q_strncpyz( client->userinfo, userinfo, sizeof( client->userinfo ) ); SV_UserinfoChanged( client ); // generate session id for( i = 0; i < sizeof( svs.clients[0].session ) - 1; i++ ) { const unsigned char symbols[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; client->session[i] = symbols[rand() % (sizeof( symbols )-1)]; } client->session[i] = '\0'; return qtrue; }
/* ================== cDirectConnect A connection request that did not come from the master ================== */ static void cDirectConnect(int argc, char **argv) { int i; netadr_t adr = net_from; Com_DPrintf("SVC_DirectConnect()\n"); int version = atoi(argv[1]); if (version != PROTOCOL_VERSION) { Netchan_OutOfBandPrint(NS_SERVER, adr, "print\nServer is version %4.2f.\n", VERSION); Com_DPrintf(" rejected connect from version %d\n", version); return; } int port = atoi(argv[2]); int challenge = atoi(argv[3]); // get userinfo char userinfo[MAX_INFO_STRING]; appStrncpyz(userinfo, argv[4], sizeof(userinfo)); // force the IP key/value pair to be in userinfo (for game-side IP filters) Info_SetValueForKey(userinfo, "ip", NET_AdrToString(&net_from)); // attractloop servers are ONLY for local clients if (sv.attractloop) { if (!NET_IsLocalAddress(&adr)) { appPrintf("Remote connect in attract loop. Ignored.\n"); Netchan_OutOfBandPrint(NS_SERVER, adr, "print\nConnection refused.\n"); return; } } // see if the challenge is valid if (!NET_IsLocalAddress(&adr)) { for (i = 0; i < MAX_CHALLENGES; i++) if (NET_CompareBaseAdr(&net_from, &svs.challenges[i].adr)) { if (challenge == svs.challenges[i].challenge) break; // good Netchan_OutOfBandPrint(NS_SERVER, adr, "print\nBad challenge.\n"); return; } if (i == MAX_CHALLENGES) { Netchan_OutOfBandPrint(NS_SERVER, adr, "print\nNo challenge for address.\n"); return; } } client_t temp, *cl; memset(&temp, 0, sizeof(client_t)); client_t *newcl = NULL; // if there is already a slot for this ip, reuse it for (i = 0, cl = svs.clients; i < sv_maxclients->integer; i++,cl++) { if (cl->state == cs_free) continue; if (NET_CompareBaseAdr(&adr, &cl->netchan.remote_address) && (cl->netchan.port == port || adr.port == cl->netchan.remote_address.port)) { if (!NET_IsLocalAddress(&adr) && (svs.realtime - cl->lastconnect) < (sv_reconnect_limit->integer * 1000)) { Com_DPrintf("%s:reconnect rejected : too soon\n", NET_AdrToString(&adr)); return; } appPrintf("%s:reconnect\n", NET_AdrToString(&adr)); newcl = cl; break; } } // find a client slot if (!newcl) { for (i = 0, cl = svs.clients; i < sv_maxclients->integer; i++,cl++) if (cl->state == cs_free) { newcl = cl; break; } } if (!newcl) { Netchan_OutOfBandPrint(NS_SERVER, adr, "print\nServer is full.\n"); Com_DPrintf("Rejected a connection.\n"); return; } // build a new connection // accept the new client // this is the only place a client_t is ever initialized *newcl = temp; sv_client = newcl; int edictnum = (newcl-svs.clients)+1; edict_t *ent = EDICT_NUM(edictnum); newcl->edict = ent; newcl->challenge = challenge; // save challenge for checksumming // get the game a chance to reject this connection or modify the userinfo qboolean result; guardGame(ge.ClientConnect); result = ge->ClientConnect(ent, userinfo); unguardGame; if (!result) { if (*Info_ValueForKey(userinfo, "rejmsg")) Netchan_OutOfBandPrint(NS_SERVER, adr, "print\n%s\nConnection refused.\n", Info_ValueForKey(userinfo, "rejmsg")); else Netchan_OutOfBandPrint(NS_SERVER, adr, "print\nConnection refused.\n" ); Com_DPrintf("Game rejected a connection.\n"); return; } // parse some info from the info strings newcl->Userinfo = userinfo; SV_UserinfoChanged(newcl); // check if client trying to connect with a new protocol newcl->newprotocol = !strcmp(argv [5], NEW_PROTOCOL_ID) && sv_extProtocol->integer; // send the connect packet to the client if (newcl->newprotocol) { int ver = atoi(argv[6]); if (ver != NEW_PROTOCOL_VERSION) { //?? we may also perform extended protocol checking by adding "[cl_]extprotocol" to userinfo Com_DPrintf("Client used extended protocol %d != "STR(NEW_PROTOCOL_VERSION)"\n", ver); if (ver < NEW_PROTOCOL_VERSION) Netchan_OutOfBandPrint(NS_SERVER, adr, "print\n" S_YELLOW"Server provides newer version of extended protocol\nPlease upgrade your client\n" ); newcl->newprotocol = false; } } if (newcl->newprotocol) { Com_DPrintf("Connecting client using extended protocol\n"); Netchan_OutOfBandPrint(NS_SERVER, adr, "client_connect "NEW_PROTOCOL_ID" "STR(NEW_PROTOCOL_VERSION)); } else Netchan_OutOfBandPrint(NS_SERVER, adr, "client_connect"); newcl->netchan.Setup(NS_SERVER, adr, port); newcl->maxPacketSize = cl->newprotocol ? MAX_MSGLEN : MAX_MSGLEN_OLD; newcl->state = cs_connected; newcl->datagram.Init(newcl->datagram_buf, sizeof(newcl->datagram_buf)); newcl->datagram.allowoverflow = true; newcl->lastmessage = svs.realtime; // don't timeout newcl->lastconnect = svs.realtime; }
/** * @brief A connection request that did not come from the master * @sa CL_ConnectionlessPacket */ static void SVC_DirectConnect (struct net_stream *stream) { char userinfo[MAX_INFO_STRING]; client_t *cl; player_t *player; int playernum; int version; bool connected; char buf[256]; const char *peername = NET_StreamPeerToName(stream, buf, sizeof(buf), false); Com_DPrintf(DEBUG_SERVER, "SVC_DirectConnect()\n"); if (sv->started || sv->spawned) { Com_Printf("rejected connect because match is already running\n"); NET_OOB_Printf(stream, "print\nGame has started already.\n"); return; } version = atoi(Cmd_Argv(1)); if (version != PROTOCOL_VERSION) { Com_Printf("rejected connect from version %i - %s\n", version, peername); NET_OOB_Printf(stream, "print\nServer is version %s.\n", UFO_VERSION); return; } Q_strncpyz(userinfo, Cmd_Argv(2), sizeof(userinfo)); if (userinfo[0] == '\0') { /* catch empty userinfo */ Com_Printf("Empty userinfo from %s\n", peername); NET_OOB_Printf(stream, "print\nConnection refused.\n"); return; } if (strchr(userinfo, '\xFF')) { /* catch end of message in string exploit */ Com_Printf("Illegal userinfo contained xFF from %s\n", peername); NET_OOB_Printf(stream, "print\nConnection refused.\n"); return; } if (strlen(Info_ValueForKey(userinfo, "ip"))) { /* catch spoofed ips */ Com_Printf("Illegal userinfo contained ip from %s\n", peername); NET_OOB_Printf(stream, "print\nConnection refused.\n"); return; } /* force the IP key/value pair so the game can filter based on ip */ Info_SetValueForKey(userinfo, sizeof(userinfo), "ip", peername); /* find a client slot */ cl = NULL; while ((cl = SV_GetNextClient(cl)) != NULL) if (cl->state == cs_free) break; if (cl == NULL) { NET_OOB_Printf(stream, "print\nServer is full.\n"); Com_Printf("Rejected a connection - server is full.\n"); return; } /* build a new connection - accept the new client * this is the only place a client_t is ever initialized */ OBJZERO(*cl); playernum = cl - SV_GetClient(0); player = PLAYER_NUM(playernum); cl->player = player; cl->player->num = playernum; { const ScopedMutex scopedMutex(svs.serverMutex); connected = svs.ge->ClientConnect(player, userinfo, sizeof(userinfo)); } /* get the game a chance to reject this connection or modify the userinfo */ if (!connected) { const char *rejmsg = Info_ValueForKey(userinfo, "rejmsg"); if (rejmsg[0] != '\0') { NET_OOB_Printf(stream, "print\n%s\nConnection refused.\n", rejmsg); Com_Printf("Game rejected a connection from %s. Reason: %s\n", peername, rejmsg); } else { NET_OOB_Printf(stream, "print\nConnection refused.\n"); Com_Printf("Game rejected a connection from %s.\n", peername); } return; } /* new player */ cl->player->inuse = true; cl->lastmessage = svs.realtime; /* parse some info from the info strings */ strncpy(cl->userinfo, userinfo, sizeof(cl->userinfo) - 1); SV_UserinfoChanged(cl); /* send the connect packet to the client */ if (sv_http_downloadserver->string[0]) NET_OOB_Printf(stream, "client_connect dlserver=%s", sv_http_downloadserver->string); else NET_OOB_Printf(stream, "client_connect"); SV_SetClientState(cl, cs_connected); Q_strncpyz(cl->peername, peername, sizeof(cl->peername)); cl->stream = stream; NET_StreamSetData(stream, cl); }
/* * sv_clientconnect_done * callback for clientconnect POST request */ static void sv_mm_clientconnect_done( stat_query_t *query, qboolean success, void *customp ) { stat_query_section_t *root, *ratings_section; int session_id, isession_id; client_t *cl; edict_t *ent; /* * ch : JSON API * { * id: [int], // 0 on error, > 0 on logged-in user, < 0 for "anonymous" user * login: [string], // login-name for user on success * ratings: [ * { gametype: [string], rating: [float]: deviation: [float] } * .. * ] * } */ /* * since we are now generating the local session in SV_MM_ClientConnect, we should * check if session_id < 0 just in case so that we wont try to regenerate local * session or do anything stupid like that * (currently we dont even tell MM about these so ignore) */ session_id = (int)customp; isession_id = 0; cl = SV_MM_ClientForSession( session_id ); if( cl == NULL ) { // or figure out the validation anyway from the received session-id? Com_Printf("SV_MM_ClientConnect: Couldnt find client with session-id %d\n", session_id ); return; } if( !success ) Com_Printf( "SV_MM_ClientConnect: Error\n" ); else { root = sq_api->GetRoot( query ); if( query == NULL ) { Com_Printf("SV_MM_ParseResponse: Failed to parse data\n"); } else { isession_id = (int)sq_api->GetNumber( root, "id" ); if( isession_id == 0 ) { Com_Printf( "SV_MM_ClientConnect: Client not logged in\n"); } else if( isession_id != session_id ) { Com_Printf( "SV_MM_ClientConnect: Session-id doesnt match %d -> %d\n", isession_id, session_id ); isession_id = 0; } else { const char *login = sq_api->GetString( root, "login" ); ratings_section = sq_api->GetSection( root, "ratings" ); Q_strncpyz( cl->mm_login, login, sizeof( cl->mm_login ) ); if( !Info_SetValueForKey( cl->userinfo, "cl_mm_login", login ) ) { Com_Printf( "Failed to set infokey cl_mm_login for player %s\n", login ); } if( ge != NULL && ratings_section != NULL ) { int idx = 0; stat_query_section_t *element = sq_api->GetArraySection( ratings_section, idx++ ); ent = EDICT_NUM( (cl - svs.clients) + 1 ); while( element != NULL ) { ge->AddRating( ent, sq_api->GetString( element, "gametype" ), sq_api->GetNumber( element, "rating" ), sq_api->GetNumber( element, "deviation" ) ); element = sq_api->GetArraySection( ratings_section, idx++ ); } } SV_UserinfoChanged( cl ); } } } // unable to validate client, either kick him out or force local session if( isession_id == 0 ) { if( sv_mm_loginonly->integer ) { SV_DropClient( cl, DROP_TYPE_GENERAL, "Error: This server requires login. Create account at http://www.warsow.net/" ); return; } // TODO: check that session_id >= 0 isession_id = SV_MM_GenerateLocalSession(); Com_Printf("SV_MM_ClientConnect: Forcing local_session %d on client %s\n", isession_id, cl->name ); cl->mm_session = isession_id; // TODO: reflect this to the userinfo Info_SetValueForKey( cl->userinfo, "cl_mm_session", va("%d", isession_id ) ); // We should also notify MM about the new local session id? // Or another option would be that MM doesnt track local sessions at all, // it just emits the results straight away. // resend scc query // cl->socket->address } Com_Printf("SV_MM_ClientConnect: %s with session id %d\n", cl->name, cl->mm_session ); }
/* ================== SV_DirectConnect A "connect" OOB command has been received ================== */ void SV_DirectConnect( netadr_t from ) { char userinfo[MAX_INFO_STRING]; int i; client_t *cl, *newcl; MAC_STATIC client_t temp; gentity_t *ent; int clientNum; int version; int qport; int challenge; char *denied; Com_DPrintf ("SVC_DirectConnect ()\n"); Q_strncpyz( userinfo, Cmd_Argv(1), sizeof(userinfo) ); version = atoi( Info_ValueForKey( userinfo, "protocol" ) ); if ( version != PROTOCOL_VERSION ) { NET_OutOfBandPrint( NS_SERVER, from, "print\nServer uses protocol version %i.\n", PROTOCOL_VERSION ); Com_DPrintf (" rejected connect from version %i\n", version); return; } qport = atoi( Info_ValueForKey( userinfo, "qport" ) ); challenge = atoi( Info_ValueForKey( userinfo, "challenge" ) ); // see if the challenge is valid (local clients don't need to challenge) if ( !NET_IsLocalAddress (from) ) { NET_OutOfBandPrint( NS_SERVER, from, "print\nNo challenge for address.\n" ); return; } else { // force the "ip" info key to "localhost" Info_SetValueForKey( userinfo, "ip", "localhost" ); } newcl = &temp; memset (newcl, 0, sizeof(client_t)); // if there is already a slot for this ip, reuse it for (i=0,cl=svs.clients ; i < 1 ; i++,cl++) { if ( cl->state == CS_FREE ) { continue; } if ( NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) && ( cl->netchan.qport == qport || from.port == cl->netchan.remoteAddress.port ) ) { if (( sv.time - cl->lastConnectTime) < (sv_reconnectlimit->integer * 1000)) { Com_DPrintf ("%s:reconnect rejected : too soon\n", NET_AdrToString (from)); return; } Com_Printf ("%s:reconnect\n", NET_AdrToString (from)); newcl = cl; goto gotnewcl; } } newcl = NULL; for ( i = 0; i < 1 ; i++ ) { cl = &svs.clients[i]; if (cl->state == CS_FREE) { newcl = cl; break; } } if ( !newcl ) { NET_OutOfBandPrint( NS_SERVER, from, "print\nServer is full.\n" ); Com_DPrintf ("Rejected a connection.\n"); return; } gotnewcl: // build a new connection // accept the new client // this is the only place a client_t is ever initialized *newcl = temp; clientNum = newcl - svs.clients; ent = SV_GentityNum( clientNum ); newcl->gentity = ent; // save the address Netchan_Setup (NS_SERVER, &newcl->netchan , from, qport); // save the userinfo Q_strncpyz( newcl->userinfo, userinfo, sizeof(newcl->userinfo) ); // get the game a chance to reject this connection or modify the userinfo denied = ge->ClientConnect( clientNum, qtrue, eSavedGameJustLoaded ); // firstTime = qtrue if ( denied ) { NET_OutOfBandPrint( NS_SERVER, from, "print\n%s\n", denied ); Com_DPrintf ("Game rejected a connection: %s.\n", denied); return; } SV_UserinfoChanged( newcl ); // send the connect packet to the client NET_OutOfBandPrint( NS_SERVER, from, "connectResponse" ); newcl->state = CS_CONNECTED; newcl->nextSnapshotTime = sv.time; newcl->lastPacketTime = sv.time; newcl->lastConnectTime = sv.time; // when we receive the first packet from the client, we will // notice that it is from a different serverid and that the // gamestate message was not just sent, forcing a retransmit newcl->gamestateMessageNum = -1; }
/* =================== SV_ExecuteClientMessage The current net_message is parsed for the given client =================== */ void SV_ExecuteClientMessage(client_t *cl){ int c; char *s; usercmd_t nullcmd; usercmd_t oldest, oldcmd, newcmd; int net_drop; int stringCmdCount; int checksum, calculatedChecksum; int checksumIndex; qboolean move_issued; int lastframe; sv_client = cl; sv_player = sv_client->edict; // only allow one move command move_issued = false; stringCmdCount = 0; while(1){ if(net_message.readcount > net_message.cursize){ Com_Printf("SV_ReadClientMessage: badread\n"); SV_DropClient(cl); return; } c = MSG_ReadByte(&net_message); if(c == -1) break; switch(c){ default: Com_Printf("SV_ReadClientMessage: unknown command char\n"); SV_DropClient(cl); return; case clc_nop: break; case clc_userinfo: strncpy(cl->userinfo, MSG_ReadString(&net_message), sizeof(cl->userinfo) - 1); SV_UserinfoChanged(cl); break; case clc_move: if(move_issued) return; // someone is trying to cheat... move_issued = true; checksumIndex = net_message.readcount; checksum = MSG_ReadByte(&net_message); lastframe = MSG_ReadLong(&net_message); if(lastframe != cl->lastframe){ cl->lastframe = lastframe; if(cl->lastframe > 0){ cl->frame_latency[cl->lastframe&(LATENCY_COUNTS - 1)] = svs.realtime - cl->frames[cl->lastframe & UPDATE_MASK].senttime; } } memset(&nullcmd, 0, sizeof(nullcmd)); MSG_ReadDeltaUsercmd(&net_message, &nullcmd, &oldest); MSG_ReadDeltaUsercmd(&net_message, &oldest, &oldcmd); MSG_ReadDeltaUsercmd(&net_message, &oldcmd, &newcmd); if(cl->state != cs_spawned){ cl->lastframe = -1; break; } // if the checksum fails, ignore the rest of the packet calculatedChecksum = COM_BlockSequenceCRCByte( net_message.data + checksumIndex + 1, net_message.readcount - checksumIndex - 1, cl->netchan.incoming_sequence); if(calculatedChecksum != checksum){ Com_DPrintf("Failed command checksum for %s(%d != %d)/%d\n", cl->name, calculatedChecksum, checksum, cl->netchan.incoming_sequence); return; } if(!paused->value){ net_drop = cl->netchan.dropped; if(net_drop < 20){ // if(net_drop > 2) // Com_Printf("drop %i\n", net_drop); while(net_drop > 2){ SV_ClientThink(cl, &cl->lastcmd); net_drop--; } if(net_drop > 1) SV_ClientThink(cl, &oldest); if(net_drop > 0) SV_ClientThink(cl, &oldcmd); } SV_ClientThink(cl, &newcmd); } cl->lastcmd = newcmd; break; case clc_stringcmd: s = MSG_ReadString(&net_message); // malicious users may try using too many string commands if(++stringCmdCount < MAX_STRINGCMDS) SV_ExecuteUserCommand(s); if(cl->state == cs_zombie) return; // disconnect command break; } } }