/* * @brief A connection request that did not come from the master. */ static void Svc_Connect(void) { char user_info[MAX_USER_INFO_STRING]; sv_client_t *cl, *client; int32_t i; Com_Debug("Svc_Connect()\n"); net_addr_t *addr = &net_from; const int32_t version = strtol(Cmd_Argv(1), NULL, 0); // resolve protocol if (version != PROTOCOL) { Netchan_OutOfBandPrint(NS_UDP_SERVER, addr, "print\nServer is version %d.\n", PROTOCOL); return; } const uint8_t qport = strtoul(Cmd_Argv(2), NULL, 0); const uint32_t challenge = strtoul(Cmd_Argv(3), NULL, 0); // copy user_info, leave room for ip stuffing g_strlcpy(user_info, Cmd_Argv(4), sizeof(user_info) - 25); if (*user_info == '\0') { // catch empty user_info Com_Print("Empty user_info from %s\n", Net_NetaddrToString(addr)); Netchan_OutOfBandPrint(NS_UDP_SERVER, addr, "print\nConnection refused\n"); return; } if (strchr(user_info, '\xFF')) { // catch end of message in string exploit Com_Print("Illegal user_info contained xFF from %s\n", Net_NetaddrToString(addr)); Netchan_OutOfBandPrint(NS_UDP_SERVER, addr, "print\nConnection refused\n"); return; } if (strlen(GetUserInfo(user_info, "ip"))) { // catch spoofed ips Com_Print("Illegal user_info contained ip from %s\n", Net_NetaddrToString(addr)); Netchan_OutOfBandPrint(NS_UDP_SERVER, addr, "print\nConnection refused\n"); return; } if (!ValidateUserInfo(user_info)) { // catch otherwise invalid user_info Com_Print("Invalid user_info from %s\n", Net_NetaddrToString(addr)); Netchan_OutOfBandPrint(NS_UDP_SERVER, addr, "print\nConnection refused\n"); return; } // force the ip so the game can filter on it SetUserInfo(user_info, "ip", Net_NetaddrToString(addr)); // enforce a valid challenge to avoid denial of service attack for (i = 0; i < MAX_CHALLENGES; i++) { if (Net_CompareClientNetaddr(addr, &svs.challenges[i].addr)) { if (challenge == svs.challenges[i].challenge) { svs.challenges[i].challenge = 0; break; // good } Netchan_OutOfBandPrint(NS_UDP_SERVER, addr, "print\nBad challenge\n"); return; } } if (i == MAX_CHALLENGES) { Netchan_OutOfBandPrint(NS_UDP_SERVER, addr, "print\nNo challenge for address\n"); return; } // resolve the client slot client = NULL; // first check for an ungraceful reconnect (client crashed, perhaps) for (i = 0, cl = svs.clients; i < sv_max_clients->integer; i++, cl++) { const net_chan_t *ch = &cl->net_chan; if (cl->state == SV_CLIENT_FREE) // not in use, not interested continue; // the base address and either the qport or real port must match if (Net_CompareClientNetaddr(addr, &ch->remote_address)) { if (addr->port == ch->remote_address.port || qport == ch->qport) { client = cl; break; } } } // otherwise, treat as a fresh connect to a new slot if (!client) { for (i = 0, cl = svs.clients; i < sv_max_clients->integer; i++, cl++) { if (cl->state == SV_CLIENT_FREE && !cl->edict->ai) { // we have a free one client = cl; break; } } } // no soup for you, next!! if (!client) { Netchan_OutOfBandPrint(NS_UDP_SERVER, addr, "print\nServer is full\n"); Com_Debug("Rejected a connection\n"); return; } // give the game a chance to reject this connection or modify the user_info if (!(svs.game->ClientConnect(client->edict, user_info))) { const char *rejmsg = GetUserInfo(user_info, "rejmsg"); if (strlen(rejmsg)) { Netchan_OutOfBandPrint(NS_UDP_SERVER, addr, "print\n%s\nConnection refused\n", rejmsg); } else { Netchan_OutOfBandPrint(NS_UDP_SERVER, addr, "print\nConnection refused\n"); } Com_Debug("Game rejected a connection\n"); return; } // parse some info from the info strings g_strlcpy(client->user_info, user_info, sizeof(client->user_info)); Sv_UserInfoChanged(client); // send the connect packet to the client Netchan_OutOfBandPrint(NS_UDP_SERVER, addr, "client_connect %s", sv_download_url->string); Netchan_Setup(NS_UDP_SERVER, &client->net_chan, addr, qport); Mem_InitBuffer(&client->datagram.buffer, client->datagram.data, sizeof(client->datagram.data)); client->datagram.buffer.allow_overflow = true; client->last_message = svs.real_time; // don't timeout client->state = SV_CLIENT_CONNECTED; }
/* * Sv_ParseClientMessage * * The current net_message is parsed for the given client. */ void Sv_ParseClientMessage(sv_client_t *cl) { user_cmd_t null_cmd, oldest_cmd, old_cmd, new_cmd; int net_drop; int strings_issued; int moves_issued; int last_frame; int c; char *s; sv_client = cl; sv_player = sv_client->edict; // allow a finite number of moves and strings moves_issued = strings_issued = 0; while (true) { if (net_message.read > net_message.size) { Com_Warn("Sv_ParseClientMessage: Bad read from %s\n", Sv_NetaddrToString(sv_client)); Sv_DropClient(cl); return; } c = Msg_ReadByte(&net_message); if (c == -1) break; switch (c) { case CL_CMD_USER_INFO: strncpy(cl->user_info, Msg_ReadString(&net_message), sizeof(cl->user_info) - 1); Sv_UserInfoChanged(cl); break; case CL_CMD_MOVE: if (++moves_issued > CMD_MAX_MOVES) { return; // someone is trying to cheat } last_frame = Msg_ReadLong(&net_message); if (last_frame != cl->last_frame) { cl->last_frame = last_frame; if (cl->last_frame > -1) { cl->frame_latency[cl->last_frame & (CLIENT_LATENCY_COUNTS - 1)] = svs.real_time - cl->frames[cl->last_frame & UPDATE_MASK].sent_time; } } memset(&null_cmd, 0, sizeof(null_cmd)); Msg_ReadDeltaUsercmd(&net_message, &null_cmd, &oldest_cmd); Msg_ReadDeltaUsercmd(&net_message, &oldest_cmd, &old_cmd); Msg_ReadDeltaUsercmd(&net_message, &old_cmd, &new_cmd); // don't start delta compression until the client is spawned // TODO: should this be a little higher up? if (cl->state != SV_CLIENT_ACTIVE) { cl->last_frame = -1; break; } // catch extremely high msec movements if (null_cmd.msec > CMD_MAX_MSEC || oldest_cmd.msec > CMD_MAX_MSEC || old_cmd.msec > CMD_MAX_MSEC || new_cmd.msec > CMD_MAX_MSEC) { Com_Warn("Sv_ParseClientMessage: Illegal msec from %s\n", Sv_NetaddrToString(cl)); Sv_KickClient(cl, "Illegal movement"); return; } net_drop = cl->netchan.dropped; if (net_drop < 20) { while (net_drop > 2) { Sv_ClientThink(cl, &cl->last_cmd); net_drop--; } if (net_drop > 1) Sv_ClientThink(cl, &oldest_cmd); if (net_drop > 0) Sv_ClientThink(cl, &old_cmd); } Sv_ClientThink(cl, &new_cmd); cl->last_cmd = new_cmd; break; case CL_CMD_STRING: s = Msg_ReadString(&net_message); // malicious users may try using too many string commands if (++strings_issued < CMD_MAX_STRINGS) Sv_UserStringCommand(s); else { Com_Warn( "Sv_ParseClientMessage: CMD_MAX_STRINGS exceeded for %s\n", Sv_NetaddrToString(cl)); Sv_KickClient(cl, "Too many commands."); return; } if (cl->state == SV_CLIENT_FREE) return; // disconnect command break; default: Com_Print("Sv_ParseClientMessage: unknown command %d\n", c); Sv_DropClient(cl); return; } } }