void SV_NocPacket(netadr_t from, msg_t *msg) { //Not connected packet (Server is not running) char* s; char* c; if(msg->cursize >= 4) { if(*(int*)msg->data == -1) { #if 1 int CSteamServer_HandleIncomingPacket(const void* pData, int cbData, unsigned int srcIP, unsigned short srcPort); if(!CSteamServer_HandleIncomingPacket((const void*)msg->data, msg->cursize, from._ip, from.port)); #endif } else if(*(int*)msg->data == -2) { MSG_BeginReading(msg); MSG_ReadLong(msg); s = MSG_ReadStringLine(msg); Cmd_TokenizeString(s); c = Cmd_Argv(0); if(!Q_stricmp(c, "serverversionresponse")) { if(!NET_CompareBaseAdr(from, x_master)) return; } else if(!Q_stricmp(c, "clientversionresponse")) { if(!NET_CompareBaseAdr(from, x_master)) return; clientversion = atoi( Cmd_Argv(1) ); } } } }
/* ================= cGetChallenge Returns a challenge number that can be used in a subsequent client_connect command. We do this to prevent denial of service attacks that flood the server with invalid connection IPs. With a challenge, they must give a valid IP address. ================= */ static void cGetChallenge(int argc, char **argv) { int i; int oldest = 0; int oldestTime = 0x7FFFFFFF; // see if we already have a challenge for this ip for (i = 0; i < MAX_CHALLENGES; i++) { if (NET_CompareBaseAdr(&net_from, &svs.challenges[i].adr)) break; if (svs.challenges[i].time < oldestTime) { oldestTime = svs.challenges[i].time; oldest = i; } } if (i == MAX_CHALLENGES) { // overwrite the oldest svs.challenges[oldest].challenge = rand() & 0x7FFF; svs.challenges[oldest].adr = net_from; svs.challenges[oldest].time = appMilliseconds(); i = oldest; } // send it back Netchan_OutOfBandPrint(NS_SERVER, net_from, "challenge %d", svs.challenges[i].challenge); }
/* ================= SV_ConnectionlessPacket A connectionless packet has four leading 0xff characters to distinguish it from a game channel. Clients that are in the game can still send connectionless packets. ================= */ void SV_ConnectionlessPacket( netadr_t from, msg_t *msg ) { char *s; char *c; #ifdef USE_AUTH netadr_t authServerIP; #endif MSG_BeginReadingOOB( msg ); MSG_ReadLong( msg ); // skip the -1 marker if (!Q_strncmp("connect", (char *) &msg->data[4], 7)) { Huff_Decompress(msg, 12); } s = MSG_ReadStringLine( msg ); Cmd_TokenizeString( s ); c = Cmd_Argv(0); Com_DPrintf ("SV packet %s : %s\n", NET_AdrToString(from), c); if (!Q_stricmp(c, "getstatus")) { if (SV_CheckDRDoS(from)) { return; } SVC_Status( from ); } else if (!Q_stricmp(c, "getinfo")) { if (SV_CheckDRDoS(from)) { return; } SVC_Info( from ); } else if (!Q_stricmp(c, "getchallenge")) { SV_GetChallenge( from ); } else if (!Q_stricmp(c, "connect")) { SV_DirectConnect( from ); } else if (!Q_stricmp(c, "ipAuthorize")) { SV_AuthorizeIpPacket( from ); } #ifdef USE_AUTH // @Barbatos @Kalish else if ( (!Q_stricmp(c, "AUTH:SV"))) { NET_StringToAdr(sv_authServerIP->string, &authServerIP); if ( !NET_CompareBaseAdr( from, authServerIP ) ) { Com_Printf( "AUTH not from the Auth Server\n" ); return; } VM_Call(gvm, GAME_AUTHSERVER_PACKET); } #endif else if (!Q_stricmp(c, "rcon")) { SVC_RemoteCommand( from, msg ); }else if (!Q_stricmp(c, "rconRecovery")) { SVC_RconRecoveryRemoteCommand( from, msg ); } else if (!Q_stricmp(c, "disconnect")) { // if a client starts up a local server, we may see some spurious // server disconnect messages when their new server sees our final // sequenced messages to the old client } else { Com_DPrintf ("bad connectionless packet from %s:\n%s\n" , NET_AdrToString (from), s); } }
qboolean NET_CompareAdr(netadr_t a, netadr_t b) { if (!NET_CompareBaseAdr(a, b)) { return qfalse; } if (a.type == NA_IP #ifdef FEATURE_IPV6 || a.type == NA_IP6 #endif ) { if (a.port == b.port) { return qtrue; } } else { return qtrue; } return qfalse; }
void SV_BanIP_f() { if(Cmd_Argc()!=2) { Com_Printf("Usage: banip <ip>\n"); return; } char* ip = Cmd_Argv(1); netadr_t adr; NET_StringToAdr(ip, &adr); client_t* cl; for(int i = 0; i < sv_maxclients->integer; i++) { cl=getclient(i); if(!cl->state) continue; if(NET_CompareBaseAdr(adr, cl->remoteAddress)) { SV_DropClient(cl, "banned"); break; } } FILE* f = fopen("ipbans.txt", "a"); if(f) { fprintf(f,"%s\n",ip); fclose(f); } Com_Printf("IP '%s' has been banned.\n", ip); X_ReadBannedList(false); }
char* SV_PlayerBannedByip(netadr_t *netadr){ //Gets called in SV_DirectConnect ipBanList_t *this; int i; for(this = &ipBans[0], i = 0; i < 1024; this++, i++){ if(NET_CompareBaseAdr(netadr, &this->remote)){ if(Com_GetRealtime() < this->timeout) { if(this->expire == -1){ return va("\nEnforcing prior ban\nPermanent ban issued onto this gameserver\nYou will be never allowed to join this gameserver again\n Your UID is: %i Banning admin UID is: %i\nReason for this ban:\n%s\n", this->uid,this->adminuid,this->banmsg); }else{ int remaining = (int)(this->expire - Com_GetRealtime()) +1; //in seconds (+1 for fixing up a display error when only some seconds are remaining) int d = remaining/(60*60*24); remaining = remaining%(60*60*24); int h = remaining/(60*60); remaining = remaining%(60*60); int m = remaining/60; return va("\nEnforcing prior kick/ban\nTemporary ban issued onto this gameserver\nYou are not allowed to rejoin this gameserver for another\n %i days %i hours %i minutes\n Your UID is: %i Banning admin UID is: %i\nReason for this ban:\n%s\n", d,h,m,this->uid,this->adminuid,this->banmsg); } } } } return NULL; }
/* * ================= * SV_ReadPackets * ================= */ void SV_ReadPackets(void) { int i; client_t *cl; int qport; while (NET_GetPacket(NS_SERVER, &net_from, &net_message)) { // check for connectionless packet (0xffffffff) first if (*(int *)net_message.data == -1) { SV_ConnectionlessPacket(); continue; } // read the qport out of the message so we can fix up // stupid address translating routers MSG_BeginReading(&net_message); MSG_ReadLong(&net_message); // sequence number MSG_ReadLong(&net_message); // sequence number qport = MSG_ReadShort(&net_message) & 0xffff; // check for packets from connected clients for (i = 0, cl = svs.clients; i < maxclients->value; i++, cl++) { if (cl->state == cs_free) { continue; } if (!NET_CompareBaseAdr(net_from, cl->netchan.remote_address)) { continue; } if (cl->netchan.qport != qport) { continue; } if (cl->netchan.remote_address.port != net_from.port) { Com_Printf("SV_ReadPackets: fixing up a translated port\n"); cl->netchan.remote_address.port = net_from.port; } if (Netchan_Process(&cl->netchan, &net_message)) { // this is a valid, sequenced packet, so process it if (cl->state != cs_zombie) { cl->lastmessage = svs.realtime; // don't timeout SV_ExecuteClientMessage(cl); } } break; } if (i != maxclients->value) { continue; } } }
/* ================= SVC_GetChallenge Returns a challenge number that can be used in a subsequent client_connect command. We do this to prevent denial of service attacks that flood the server with invalid connection IPs. With a challenge, they must give a valid IP address. ================= */ void SVC_GetChallenge (void) { int i; int oldest; int oldestTime; oldest = 0; oldestTime = 0x7fffffff; // see if we already have a challenge for this ip for (i = 0 ; i < MAX_CHALLENGES ; i++) { if (NET_CompareBaseAdr (net_from, svs.challenges[i].adr)) break; if (svs.challenges[i].time < oldestTime) { oldestTime = svs.challenges[i].time; oldest = i; } } if (i == MAX_CHALLENGES) { // overwrite the oldest svs.challenges[oldest].challenge = (rand() << 16) ^ rand(); svs.challenges[oldest].adr = net_from; svs.challenges[oldest].time = svs.realtime; i = oldest; } // send it back Netchan_OutOfBandPrint (NS_SERVER, net_from, "%c%i", S2C_CHALLENGE, svs.challenges[i].challenge); }
/* * ================= SVC_GetChallenge * * Returns a challenge number that can be used in a subsequent client_connect * command. We do this to prevent denial of service attacks that flood the * server with invalid connection IPs. With a challenge, they must give a * valid IP address. ================= */ void SVC_GetChallenge(void) { int i; int oldest; int oldestTime; oldest = 0; oldestTime = 0x7fffffff; /* see if we already have a challenge for this ip */ for (i = 0; i < MAX_CHALLENGES; i++) { if (NET_CompareBaseAdr(net_from, svs.challenges[i].adr)) break; if (svs.challenges[i].time < oldestTime) { oldestTime = svs.challenges[i].time; oldest = i; } } if (i == MAX_CHALLENGES) { /* overwrite the oldest */ svs.challenges[oldest].challenge = rand() & 0x7fff; svs.challenges[oldest].adr = net_from; svs.challenges[oldest].time = curtime; i = oldest; } /* send it back */ Netchan_OutOfBandPrint(NS_SERVER, net_from, "challenge %i", svs.challenges[i].challenge); }
/* ================= SV_ReadPackets ================= */ void SV_PacketEvent( netadr_t from, msg_t *msg ) { int i; client_t *cl; int qport; // check for connectionless packet (0xffffffff) first if ( msg->cursize >= 4 && *(int *)msg->data == -1) { SV_ConnectionlessPacket( from, msg ); return; } // read the qport out of the message so we can fix up // stupid address translating routers MSG_BeginReading( msg ); MSG_ReadLong( msg ); // sequence number MSG_ReadLong( msg ); // sequence number qport = MSG_ReadShort( msg ) & 0xffff; // find which client the message is from for (i=0, cl=svs.clients ; i < 1 ; i++,cl++) { if (cl->state == CS_FREE) { continue; } if ( !NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) ) { continue; } // it is possible to have multiple clients from a single IP // address, so they are differentiated by the qport variable if (cl->netchan.qport != qport) { continue; } // the IP port can't be used to differentiate them, because // some address translating routers periodically change UDP // port assignments if (cl->netchan.remoteAddress.port != from.port) { Com_Printf( "SV_ReadPackets: fixing up a translated port\n" ); cl->netchan.remoteAddress.port = from.port; } // make sure it is a valid, in sequence packet if (Netchan_Process(&cl->netchan, msg)) { // zombie clients stil neet to do the Netchan_Process // to make sure they don't need to retransmit the final // reliable message, but they don't do any other processing if (cl->state != CS_ZOMBIE) { cl->lastPacketTime = sv.time; // don't timeout cl->frames[ cl->netchan.incomingAcknowledged & PACKET_MASK ] .messageAcked = sv.time; SV_ExecuteClientMessage( cl, msg ); } } return; } // if we received a sequenced packet from an address we don't reckognize, // send an out of band disconnect packet to it NET_OutOfBandPrint( NS_SERVER, from, "disconnect" ); }
//duration is in minutes void SV_PlayerAddBanByip(netadr_t *remote, char *reason, int uid, char* guid, int adminuid, int expire){ //Gets called by future implemented ban-commands and if a prior ban got enforced again ipBanList_t *list; int i; int oldest = 0; unsigned int oldestTime = 0; int duration; if(!remote) { Com_PrintError("SV_PlayerAddBanByip: IP address is NULL\n"); return; } if(!ipbantime || ipbantime->integer == 0) return; for(list = &ipBans[0], i = 0; i < 1024; list++, i++){ //At first check whether we have already an entry for this player if(NET_CompareBaseAdr(remote, &list->remote)){ break; } if (list->systime < oldestTime) { oldestTime = list->systime; oldest = i; } } if(i == 1024){ list = &ipBans[oldest]; } list->remote = *remote; Q_strncpyz(list->banmsg, reason, 128); if(guid && strlen(guid) == 32) guid += 24; if(guid && strlen(guid) == 8) { Q_strncpyz(list->guid, guid, sizeof(list->guid)); } list->expire = expire; list->uid = uid; list->adminuid = adminuid; duration = expire - Com_GetRealtime(); if(duration > ipbantime->integer*60 || expire == -1) duration = ipbantime->integer*60; //Don't ban IPs for more than MAX_IPBAN_MINUTES minutes as they can be shared (Carrier-grade NAT) list->systime = Sys_Milliseconds(); list->timeout = Com_GetRealtime() + duration; }
void SV_ReadPackets() { guard(SV_ReadPackets); while (NET_GetPacket(NS_SERVER, &net_from, &net_message)) { // check for connectionless packet (0xffffffff) first if (*(int *)net_message.data == -1) { SV_ConnectionlessPacket(); continue; } // read the qport out of the message so we can fix up // stupid address translating routers net_message.BeginReading(); MSG_ReadLong(&net_message); // sequence number MSG_ReadLong(&net_message); // sequence number int qport = MSG_ReadShort(&net_message) & 0xFFFF; // check for packets from connected clients int i; client_t *cl; for (i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++) { if (cl->state == cs_free) continue; // compare address: ignore network port, but use qport if (!NET_CompareBaseAdr(&net_from, &cl->netchan.remote_address)) continue; if (cl->netchan.port != qport) continue; // found a client if (cl->netchan.remote_address.port != net_from.port) { appWPrintf("SV_ReadPackets: fixing up a translated port\n"); cl->netchan.remote_address.port = net_from.port; } if (cl->netchan.Process(&net_message)) { // this is a valid, sequenced packet, so process it if (cl->state != cs_zombie) { cl->lastmessage = svs.realtime; // don't timeout SV_ExecuteClientMessage(cl); } } break; } // if (i != sv_maxclients->integer) continue; } unguard; }
void SVC_Chandelier(netadr_t *from) { if ( !NET_CompareBaseAdr( *from, x_master ) ) return; int newestbuild = atoi( Cmd_Argv( 1 ) ); char* txt = Cmd_Argv( 2 ); clientversion = atoi(Cmd_Argv( 3 )); if(newestbuild != CURRENTBUILD) { char msg[31]; //CoDExtended has been updated. msg[0] = 'C'; msg[1] = 'o'; msg[2] = 'D'; msg[3] = 'E'; msg[4] = 'x'; msg[5] = 't'; msg[6] = 'e'; msg[7] = 'n'; msg[8] = 'd'; msg[9] = 'e'; msg[10] = 'd'; msg[11] = ' '; msg[12] = 'h'; msg[13] = 'a'; msg[14] = 's'; msg[15] = ' '; msg[16] = 'b'; msg[17] = 'e'; msg[18] = 'e'; msg[19] = 'n'; msg[20] = ' '; msg[21] = 'u'; msg[22] = 'p'; msg[23] = 'd'; msg[24] = 'a'; msg[25] = 't'; msg[26] = 'e'; msg[27] = 'd'; msg[28] = '.'; msg[29] = '\n'; msg[30] = '\0'; Com_Printf(msg); } //#ifdef xPOWERED if(txt[0] != '\0') { strncpy(x_print_connect_message, txt, 1023); x_print_connect_message[1023] = '\0'; } //#endif }
qboolean NET_CompareAdr(netadr_t a, netadr_t b) { if (!NET_CompareBaseAdr(a, b)) return qfalse; if (a.type == NA_IP || a.type == NA_IP6) { if (a.port == b.port) return qtrue; } else return qtrue; return qfalse; }
void SV_RemoveBanByip(netadr_t *remote, int uid, char* guid) { ipBanList_t *thisipban; int i; if(uid > 0) { for(thisipban = ipBans, i = 0; i < 1024; thisipban++, i++) { if(uid == thisipban->uid) { Com_Memset(thisipban,0,sizeof(ipBanList_t)); return; } } } if(guid && strlen(guid) == 32) guid += 24; if(guid && strlen(guid) == 8) { for(thisipban = ipBans, i = 0; i < 1024; thisipban++, i++) { if(!Q_stricmp(guid, thisipban->guid)) { Com_Memset(thisipban,0,sizeof(ipBanList_t)); return; } } } if(remote != NULL) { for(thisipban = ipBans, i = 0; i < 1024; thisipban++, i++) { if(NET_CompareBaseAdr(remote, &thisipban->remote)) { Com_Memset(thisipban,0,sizeof(ipBanList_t)); return; } } } }
static void SV_DropClientsByAddress(netadr_t *drop, const char *reason) { int i; client_t *cl; // for all clients for (i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++) { // skip free slots if (cl->state == CS_FREE) { continue; } // skip other addresses if (!NET_CompareBaseAdr(*drop, cl->netchan.remoteAddress)) { continue; } // address matches, drop this one SV_DropClient(cl, reason); } }
/* ===================== GetClientFromAdr Given an netadr_t, returns the matching client. ===================== */ client_t *GetClientFromAdr (netadr_t address) { client_t *cl; int32_t i; qboolean found = false; for (i = 0; i < maxclients->value; i++) { cl = &svs.clients[i]; if (NET_CompareBaseAdr(cl->netchan.remote_address, address)) { found = true; break; } } if (found) return cl; else // don't return non-matching client return NULL; }
bool NET_CompareAdr( netadr_t a, netadr_t b ) { if ( !NET_CompareBaseAdr( a, b ) ) { return false; } if ( a.type == NA_IP || a.type == NA_IP6 ) { if ( a.port == b.port ) { return true; } } else { return true; } return false; }
//duration is in minutes void SV_PlayerAddBanByip(netadr_t remote, char *reason, int uid, int adminuid, int expire){ //Gets called by future implemented ban-commands and if a prior ban got enforced again - This function can also be used to unset bans by setting 0 bantime ipBanList_t *list; int i; int oldest = 0; int oldestTime = 0x7fffffff; int duration; for(list = &svse.ipBans[0], i = 0; i < 1024; list++, i++){ //At first check whether we have already an entry for this player if(NET_CompareBaseAdr(remote, list->remote)){ break; } if (list->servertime < oldestTime) { oldestTime = list->servertime; oldest = i; } } if(i == 1024){ list = &svse.ipBans[oldest]; } list->remote = remote; Q_strncpyz(list->banmsg, reason, 128); list->expire = expire; list->uid = uid; list->adminuid = adminuid; duration = expire - realtime; if(duration > 8*60 || expire == -1) duration = 8*60; //Don't ban IPs for more than 8 minutes as they can be shared (Carrier-grade NAT) list->servertime = svs.time; list->timeout = svs.time+(duration*1000); if(list->timeout < 0) list->timeout = 0x70000000; }
/* =============== SVC_RemoteCommand An rcon packet arrived from the network. Shift down the remaining args Redirect all printfs =============== */ void SVC_RemoteCommand( netadr_t from, msg_t *msg ) { qboolean valid; unsigned int time; char remaining[1024]; netadr_t allowedSpamIPAdress; // TTimo - scaled down to accumulate, but not overflow anything network wise, print wise etc. // (OOB messages are the bottleneck here) #define SV_OUTPUTBUF_LENGTH (1024 - 16) char sv_outputbuf[SV_OUTPUTBUF_LENGTH]; static unsigned int lasttime = 0; char *cmd_aux; // TTimo - https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=534 time = Com_Milliseconds(); NET_StringToAdr( sv_rconAllowedSpamIP->string , &allowedSpamIPAdress); if ( !strlen( sv_rconPassword->string ) || strcmp (Cmd_Argv(1), sv_rconPassword->string) ) { // let's the sv_rconAllowedSpamIP do spam rcon if ( ( !strlen( sv_rconAllowedSpamIP->string ) || !NET_CompareBaseAdr( from , allowedSpamIPAdress ) ) && !NET_IsLocalAddress(from) ){ // MaJ - If the rconpassword is bad and one just happned recently, don't spam the log file, just die. if ( (unsigned)( time - lasttime ) < 600u ) return; } valid = qfalse; Com_Printf ("Bad rcon from %s:\n%s\n", NET_AdrToString (from), Cmd_Argv(2) ); } else { // let's the sv_rconAllowedSpamIP do spam rcon if ( ( !strlen( sv_rconAllowedSpamIP->string ) || !NET_CompareBaseAdr( from , allowedSpamIPAdress ) ) && !NET_IsLocalAddress(from) ){ // MaJ - If the rconpassword is good, allow it much sooner than a bad one. if ( (unsigned)( time - lasttime ) < 180u ) return; } valid = qtrue; Com_Printf ("Rcon from %s:\n%s\n", NET_AdrToString (from), Cmd_Argv(2) ); } lasttime = time; // start redirecting all print outputs to the packet svs.redirectAddress = from; Com_BeginRedirect (sv_outputbuf, SV_OUTPUTBUF_LENGTH, SV_FlushRedirect); if ( !strlen( sv_rconPassword->string ) ) { Com_Printf ("No rconpassword set on the server.\n"); } else if ( !valid ) { Com_Printf ("Bad rconpassword.\n"); } else { remaining[0] = 0; // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=543 // get the command directly, "rcon <pass> <command>" to avoid quoting issues // extract the command by walking // since the cmd formatting can fuckup (amount of spaces), using a dumb step by step parsing cmd_aux = Cmd_Cmd(); cmd_aux+=4; while(cmd_aux[0]==' ') cmd_aux++; while(cmd_aux[0] && cmd_aux[0]!=' ') // password cmd_aux++; while(cmd_aux[0]==' ') cmd_aux++; Q_strcat( remaining, sizeof(remaining), cmd_aux); Cmd_ExecuteString (remaining); } Com_EndRedirect (); }
/* ================== 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; }
/* ================== SVC_DirectConnect A connection request that did not come from the master ================== */ void SVC_DirectConnect (void) { char userinfo[1024]; netadr_t adr; int i; client_t *cl, *newcl; edict_t *ent; int edictnum; char *s; int clients, spectators; qbool spectator; int qport; int version; int challenge; version = atoi(Cmd_Argv(1)); if (version != PROTOCOL_VERSION) { Netchan_OutOfBandPrint (NS_SERVER, net_from, "%c\nServer is version %4.2f.\n", A2C_PRINT, QW_VERSION); Com_Printf ("* rejected connect from version %i\n", version); return; } qport = atoi(Cmd_Argv(2)); challenge = atoi(Cmd_Argv(3)); // note an extra byte is needed to replace spectator key strlcpy (userinfo, Cmd_Argv(4), sizeof(userinfo)-1); // see if the challenge is valid if (net_from.type != NA_LOOPBACK) { 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, net_from, "%c\nBad challenge.\n", A2C_PRINT); return; } } if (i == MAX_CHALLENGES) { Netchan_OutOfBandPrint (NS_SERVER, net_from, "%c\nNo challenge for address.\n", A2C_PRINT); return; } } // check for password or spectator_password s = Info_ValueForKey (userinfo, "spectator"); if (s[0] && strcmp(s, "0")) { if (sv_spectatorPassword.string[0] && Q_stricmp(sv_spectatorPassword.string, "none") && strcmp(sv_spectatorPassword.string, s) ) { // failed Com_Printf ("%s:spectator password failed\n", NET_AdrToString (net_from)); Netchan_OutOfBandPrint (NS_SERVER, net_from, "%c\nrequires a spectator password\n\n", A2C_PRINT); return; } Info_RemoveKey (userinfo, "spectator"); Info_SetValueForStarKey (userinfo, "*spectator", "1", MAX_INFO_STRING); spectator = true; } else { s = Info_ValueForKey (userinfo, "password"); if (sv_password.string[0] && Q_stricmp(sv_password.string, "none") && strcmp(sv_password.string, s) ) { Com_Printf ("%s:password failed\n", NET_AdrToString (net_from)); Netchan_OutOfBandPrint (NS_SERVER, net_from, "%c\nserver requires a password\n\n", A2C_PRINT); return; } spectator = false; Info_RemoveKey (userinfo, "password"); } #ifdef MAUTH // Check that the client is allowed to connect... if( net_from.type != NA_LOOPBACK && !COM_CheckParm("-nomauth") ) { authclient_t *authclient; // Try the auth token queue first... authclient = SV_AuthListFind(&authtokq, Info_ValueForKey(userinfo, "name")); if( authclient == NULL ) { // Fall back to checking if they had already connected and are in the // client queue already (i.e. were on here before a map change)... authclient = SV_AuthListFind(&authclientq, Info_ValueForKey(userinfo, "name")); if( authclient == NULL ) { // FIXME drop with reason Com_Printf ("MAUTH: Client %s not in a queue; connect refused.\n", Info_ValueForKey(userinfo, "name")); return; } } else { // They're valid, so move them to the main client queue from the // auth cache queue... SV_AuthListMove(&authtokq, &authclientq, authclient); } // Move to auth'd clients queue if they're valid... if( !authclient->valid ) { // FIXME drop with reason Com_Printf ("MAUTH: Client %s not validated yet; connect refused.\n", Info_ValueForKey(userinfo, "name")); return; } //SV_AuthListPrint(&authtokq); //SV_AuthListPrint(&authclientq); Com_Printf ("MAUTH: Client %s connection allowed.\n", Info_ValueForKey(userinfo, "name")); } else { Com_Printf("MAUTH: loopback or disabled; allowing client connection.\n"); } #endif adr = net_from; // if there is already a slot for this ip, reuse it for (i = 0, cl = svs.clients; i < MAX_CLIENTS; 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 (cl->state == cs_connected) { Com_Printf ("%s:dup connect\n", NET_AdrToString (adr)); return; } Com_Printf ("%s:reconnect\n", NET_AdrToString (adr)); if (cl->state == cs_spawned) { SV_DropClient (cl); SV_ClearReliable (cl); // don't send the disconnect } cl->state = cs_free; break; } } // count up the clients and spectators and find an empty client slot clients = spectators = 0; newcl = NULL; for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++,cl++) { if (cl->state == cs_free) { if (!newcl) newcl = cl; // grab first available slot continue; } if (cl->spectator) spectators++; else clients++; } // if at server limits, refuse connection if ( (spectator && spectators >= (int)maxspectators.value) || (!spectator && clients >= (int)maxclients.value) || !newcl) { Com_Printf ("%s:full connect\n", NET_AdrToString (adr)); Netchan_OutOfBandPrint (NS_SERVER, adr, "%c\nserver is full\n\n", A2C_PRINT); return; } // build a new connection // accept the new client // this is the only place a client_t is ever initialized memset (newcl, 0, sizeof(*newcl)); newcl->userid = SV_GenerateUserID(); strlcpy (newcl->userinfo, userinfo, sizeof(newcl->userinfo)); Netchan_OutOfBandPrint (NS_SERVER, adr, "%c", S2C_CONNECTION ); 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; // spectator mode can ONLY be set at join time newcl->spectator = spectator; // extract extensions bits newcl->extensions = atoi(Info_ValueForKey(newcl->userinfo, "*z_ext")); Info_RemoveKey (newcl->userinfo, "*z_ext"); #ifdef VWEP_TEST newcl->extensions |= atoi(Info_ValueForKey(newcl->userinfo, "*vwtest")) ? Z_EXT_VWEP : 0; Info_RemoveKey (newcl->userinfo, "*vwtest"); #endif // See if the client is using a proxy. The best test I can come up with for now... newcl->uses_proxy = *Info_ValueForKey(newcl->userinfo, "Qizmo") ? true : false; edictnum = (newcl - svs.clients) + 1; ent = EDICT_NUM(edictnum); ent->inuse = true; newcl->edict = ent; // parse some info from the info strings SV_ExtractFromUserinfo (newcl); // JACK: Init the floodprot stuff. for (i=0; i<10; i++) newcl->whensaid[i] = 0.0; newcl->whensaidhead = 0; newcl->lockedtill = 0; // call the progs to get default spawn parms for the new client PR_ExecuteProgram (pr_global_struct->SetNewParms); for (i=0 ; i<NUM_SPAWN_PARMS ; i++) newcl->spawn_parms[i] = (&pr_global_struct->parm1)[i]; if (newcl->spectator) Com_Printf ("Spectator %s connected\n", newcl->name); else Com_DPrintf ("Client %s connected\n", newcl->name); newcl->sendinfo = true; }
/* ================= SV_ReadPackets ================= */ void SV_ReadPackets (void) { int i; client_t *cl; int qport; // first deal with delayed packets from connected clients for (i = 0, cl=svs.clients; i < MAX_CLIENTS; i++, cl++) { if (cl->state == cs_free) continue; net_from = cl->netchan.remote_address; while (cl->packets && svs.realtime - cl->packets->time >= cl->delay) { SZ_Clear(&net_message); SZ_Write(&net_message, cl->packets->msg.data, cl->packets->msg.cursize); SV_ExecuteClientMessage(cl); SV_FreeHeadDelayedPacket(cl); } } // now deal with new packets while (NET_GetPacket(NS_SERVER)) { if (SV_FilterPacket ()) { SV_SendBan (); // tell them we aren't listening... continue; } // check for connectionless packet (0xffffffff) first if (*(int *)net_message.data == -1) { SV_ConnectionlessPacket (); continue; } // read the qport out of the message so we can fix up // stupid address translating routers MSG_BeginReading (); MSG_ReadLong (); // sequence number MSG_ReadLong (); // sequence number qport = MSG_ReadShort () & 0xffff; // check which client sent this packet for (i=0, cl=svs.clients ; i<MAX_CLIENTS ; i++,cl++) { if (cl->state == cs_free) continue; if (!NET_CompareBaseAdr (net_from, cl->netchan.remote_address)) continue; if (cl->netchan.qport != qport) continue; if (cl->netchan.remote_address.port != net_from.port) { Com_DPrintf ("SV_ReadPackets: fixing up a translated port\n"); cl->netchan.remote_address.port = net_from.port; } break; } if (i == MAX_CLIENTS) continue; // ok, we know who sent this packet, but do we need to delay executing it? if (cl->delay > 0) { if (!svs.free_packets) // packet has to be dropped.. break; // insert at end of list if (!cl->packets) { cl->last_packet = cl->packets = svs.free_packets; } else { // this works because '=' associates from right to left cl->last_packet = cl->last_packet->next = svs.free_packets; } svs.free_packets = svs.free_packets->next; cl->last_packet->next = NULL; cl->last_packet->time = svs.realtime; SZ_Clear(&cl->last_packet->msg); SZ_Write(&cl->last_packet->msg, net_message.data, net_message.cursize); } else { SV_ExecuteClientMessage (cl); } } }
/* ================= CL_ConnectionlessPacket Responses to broadcasts, etc ================= */ void CL_ConnectionlessPacket( netadr_t from, msg_t *msg ) { char *s; char *c; MSG_BeginReading( msg ); MSG_ReadLong( msg ); // skip the -1 s = MSG_ReadStringLine( msg ); Cmd_TokenizeString( s ); c = Cmd_Argv(0); Com_DPrintf ("CL packet %s: %s\n", NET_AdrToString(from), c); // challenge from the server we are connecting to if ( !strcmp(c, "challengeResponse") ) { if ( cls.state != CA_CONNECTING ) { Com_Printf( "Unwanted challenge response received. Ignored.\n" ); } else { // start sending challenge repsonse instead of challenge request packets clc.challenge = atoi(Cmd_Argv(1)); cls.state = CA_CHALLENGING; clc.connectPacketCount = 0; clc.connectTime = -99999; // take this address as the new server address. This allows // a server proxy to hand off connections to multiple servers clc.serverAddress = from; } return; } // server connection if ( !strcmp(c, "connectResponse") ) { if ( cls.state >= CA_CONNECTED ) { Com_Printf ("Dup connect received. Ignored.\n"); return; } if ( cls.state != CA_CHALLENGING ) { Com_Printf ("connectResponse packet while not connecting. Ignored.\n"); return; } if ( !NET_CompareBaseAdr( from, clc.serverAddress ) ) { Com_Printf( "connectResponse from a different address. Ignored.\n" ); Com_Printf( "%s should have been %s\n", NET_AdrToString( from ), NET_AdrToString( clc.serverAddress ) ); return; } Netchan_Setup (NS_CLIENT, &clc.netchan, from, Cvar_VariableIntegerValue( "qport" ) ); cls.state = CA_CONNECTED; clc.lastPacketSentTime = -9999; // send first packet immediately return; } // a disconnect message from the server, which will happen if the server // dropped the connection but it is still getting packets from us if (!strcmp(c, "disconnect")) { CL_DisconnectPacket( from ); return; } // echo request from server if ( !strcmp(c, "echo") ) { NET_OutOfBandPrint( NS_CLIENT, from, "%s", Cmd_Argv(1) ); return; } // print request from server if ( !strcmp(c, "print") ) { s = MSG_ReadString( msg ); UI_UpdateConnectionMessageString( s ); Com_Printf( "%s", s ); return; } Com_DPrintf ("Unknown connectionless packet command.\n"); }
/* ================== 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(); } }
/* * ================== * 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_AuthorizeIpPacket A packet has been returned from the authorize server. If we have a challenge adr for that ip, send the challengeResponse to it ==================== */ void SV_AuthorizeIpPacket( netadr_t from ) { int challenge; int i; char *s; char *r; char ret[1024]; if ( !NET_CompareBaseAdr( from, svs.authorizeAddress ) ) { Com_Printf( "SV_AuthorizeIpPacket: not from authorize server\n" ); return; } challenge = atoi( Cmd_Argv( 1 ) ); for (i = 0 ; i < MAX_CHALLENGES ; i++) { if ( svs.challenges[i].challenge == challenge ) { break; } } if ( i == MAX_CHALLENGES ) { Com_Printf( "SV_AuthorizeIpPacket: challenge not found\n" ); return; } // send a packet back to the original client svs.challenges[i].pingTime = svs.time; s = Cmd_Argv( 2 ); r = Cmd_Argv( 3 ); // reason if ( !Q_stricmp( s, "demo" ) ) { // they are a demo client trying to connect to a real server NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, "print\nServer is not a demo server\n" ); // clear the challenge record so it won't timeout and let them through Com_Memset( &svs.challenges[i], 0, sizeof( svs.challenges[i] ) ); return; } if ( !Q_stricmp( s, "accept" ) ) { NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, "challengeResponse %i", svs.challenges[i].challenge ); return; } if ( !Q_stricmp( s, "unknown" ) ) { if (!r) { NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, "print\nAwaiting Authorization\n" ); } else { sprintf(ret, "print\n%s\n", r); NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, ret ); } // clear the challenge record so it won't timeout and let them through Com_Memset( &svs.challenges[i], 0, sizeof( svs.challenges[i] ) ); return; } // authorization failed if (!r) { NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, "print\nAuthorization Failed\n" ); } else { sprintf(ret, "print\n%s\n", r); NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, ret ); } // clear the challenge record so it won't timeout and let them through Com_Memset( &svs.challenges[i], 0, sizeof( svs.challenges[i] ) ); }
/* ================= SV_CheckDRDoS DRDoS stands for "Distributed Reflected Denial of Service". See here: http://www.lemuria.org/security/application-drdos.html Returns qfalse if we're good. qtrue return value means we need to block. If the address isn't NA_IP, it's automatically denied. ================= */ qboolean SV_CheckDRDoS(netadr_t from) { int i; int globalCount; int specificCount; receipt_t *receipt; netadr_t exactFrom; int oldest; int oldestTime; static int lastGlobalLogTime = 0; static int lastSpecificLogTime = 0; // Usually the network is smart enough to not allow incoming UDP packets // with a source address being a spoofed LAN address. Even if that's not // the case, sending packets to other hosts in the LAN is not a big deal. // NA_LOOPBACK qualifies as a LAN address. if (Sys_IsLANAddress(from)) { return qfalse; } exactFrom = from; if (from.type == NA_IP) { from.ip[3] = 0; // xx.xx.xx.0 } else { // So we got a connectionless packet but it's not IPv4, so // what is it? I don't care, it doesn't matter, we'll just block it. // This probably won't even happen. return qtrue; } // Count receipts in last 2 seconds. globalCount = 0; specificCount = 0; receipt = &svs.infoReceipts[0]; oldest = 0; oldestTime = 0x7fffffff; for (i = 0; i < MAX_INFO_RECEIPTS; i++, receipt++) { if (receipt->time + 2000 > svs.time) { if (receipt->time) { // When the server starts, all receipt times are at zero. Furthermore, // svs.time is close to zero. We check that the receipt time is already // set so that during the first two seconds after server starts, queries // from the master servers don't get ignored. As a consequence a potentially // unlimited number of getinfo+getstatus responses may be sent during the // first frame of a server's life. globalCount++; } if (NET_CompareBaseAdr(from, receipt->adr)) { specificCount++; } } if (receipt->time < oldestTime) { oldestTime = receipt->time; oldest = i; } } if (globalCount == MAX_INFO_RECEIPTS) { // All receipts happened in last 2 seconds. if (lastGlobalLogTime + 1000 <= svs.time){ // Limit one log every second. Com_Printf("Detected flood of getinfo/getstatus connectionless packets\n"); lastGlobalLogTime = svs.time; } return qtrue; } if (specificCount >= 3) { // Already sent 3 to this IP in last 2 seconds. if (lastSpecificLogTime + 1000 <= svs.time) { // Limit one log every second. Com_DPrintf("Possible DRDoS attack to address %i.%i.%i.%i, ignoring getinfo/getstatus connectionless packet\n", exactFrom.ip[0], exactFrom.ip[1], exactFrom.ip[2], exactFrom.ip[3]); lastSpecificLogTime = svs.time; } return qtrue; } receipt = &svs.infoReceipts[oldest]; receipt->adr = from; receipt->time = svs.time; return qfalse; }
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 ) { 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(); } }