/* * Sv_New_f * * Sends the first message from the server to a connected client. * This will be sent on the initial connection and upon each server load. */ static void Sv_New_f(void) { int player_num; Com_Debug("New() from %s\n", Sv_NetaddrToString(sv_client)); if (sv_client->state != SV_CLIENT_CONNECTED) { Com_Warn("Sv_New_f: %s already spawned\n", Sv_NetaddrToString(sv_client)); return; } // demo servers will send the demo file's server info packet if (sv.state == SV_ACTIVE_DEMO) { return; } // send the server data Msg_WriteByte(&sv_client->netchan.message, SV_CMD_SERVER_DATA); Msg_WriteLong(&sv_client->netchan.message, PROTOCOL); Msg_WriteLong(&sv_client->netchan.message, svs.spawn_count); Msg_WriteLong(&sv_client->netchan.message, svs.frame_rate); Msg_WriteByte(&sv_client->netchan.message, 0); Msg_WriteString(&sv_client->netchan.message, Cvar_GetString("game")); player_num = sv_client - svs.clients; Msg_WriteShort(&sv_client->netchan.message, player_num); // send full level name Msg_WriteString(&sv_client->netchan.message, sv.config_strings[CS_NAME]); // begin fetching config_strings Msg_WriteByte(&sv_client->netchan.message, SV_CMD_CBUF_TEXT); Msg_WriteString(&sv_client->netchan.message, va("config_strings %i 0\n", svs.spawn_count)); }
/* * Sv_Begin_f */ static void Sv_Begin_f(void) { Com_Debug("Begin() from %s\n", Sv_NetaddrToString(sv_client)); if (sv_client->state != SV_CLIENT_CONNECTED) { // catch duplicate spawns Com_Warn("Sv_Begin_f: Invalid Begin() from %s\n", Sv_NetaddrToString(sv_client)); Sv_KickClient(sv_client, NULL); return; } if (sv.state == SV_ACTIVE_DEMO) return; // handle the case of a level changing while a client was connecting if (strtoul(Cmd_Argv(1), NULL, 0) != svs.spawn_count) { Com_Debug("Sv_Begin_f: Stale spawn count from %s\n", Sv_NetaddrToString(sv_client)); Sv_New_f(); return; } sv_client->state = SV_CLIENT_ACTIVE; // call the game begin function svs.game->ClientBegin(sv_player); Cbuf_InsertFromDefer(); }
/* * Sv_UserInfoChanged * * Enforces safe user_info data before passing onto game module. */ void Sv_UserInfoChanged(sv_client_t *cl) { char *val; size_t i; if (*cl->user_info == '\0') { // catch empty user_info Com_Print("Empty user_info from %s\n", Sv_NetaddrToString(cl)); Sv_KickClient(cl, "Bad user info"); return; } if (strchr(cl->user_info, '\xFF')) { // catch end of message exploit Com_Print("Illegal user_info contained xFF from %s\n", Sv_NetaddrToString(cl)); Sv_KickClient(cl, "Bad user info"); return; } if (!ValidateUserInfo(cl->user_info)) { // catch otherwise invalid user_info Com_Print("Invalid user_info from %s\n", Sv_NetaddrToString(cl)); Sv_KickClient(cl, "Bad user info"); return; } val = GetUserInfo(cl->user_info, "skin"); if (strstr(val, "..")) // catch malformed skins SetUserInfo(cl->user_info, "skin", "enforcer/qforcer"); // call game code to allow overrides svs.game->ClientUserInfoChanged(cl->edict, cl->user_info); // name for C code, mask off high bit strncpy(cl->name, GetUserInfo(cl->user_info, "name"), sizeof(cl->name) - 1); for (i = 0; i < sizeof(cl->name); i++) { cl->name[i] &= 127; } // rate command val = GetUserInfo(cl->user_info, "rate"); if (*val != '\0') { cl->rate = atoi(val); if (cl->rate > CLIENT_RATE_MAX) cl->rate = CLIENT_RATE_MAX; else if (cl->rate < CLIENT_RATE_MIN) cl->rate = CLIENT_RATE_MIN; } // limit the print messages the client receives val = GetUserInfo(cl->user_info, "message_level"); if (*val != '\0') { cl->message_level = atoi(val); } // start/stop sending view angles for demo recording val = GetUserInfo(cl->user_info, "recording"); cl->recording = atoi(val) == 1; }
/* * Sv_ConfigStrings_f */ static void Sv_ConfigStrings_f(void) { unsigned int start; Com_Debug("ConfigStrings() from %s\n", Sv_NetaddrToString(sv_client)); if (sv_client->state != SV_CLIENT_CONNECTED) { Com_Warn("Sv_ConfigStrings_f: %s already spawned\n", Sv_NetaddrToString(sv_client)); return; } // handle the case of a level changing while a client was connecting if (strtoul(Cmd_Argv(1), NULL, 0) != svs.spawn_count) { Com_Debug("Sv_ConfigStrings_f: Stale spawn count from %s\n", Sv_NetaddrToString(sv_client)); Sv_New_f(); return; } start = strtoul(Cmd_Argv(2), NULL, 0); if (start >= MAX_CONFIG_STRINGS) { // catch bad offsets Com_Warn("Sv_ConfigStrings_f: Bad config_string offset from %s\n", Sv_NetaddrToString(sv_client)); Sv_KickClient(sv_client, NULL); return; } // write a packet full of data while (sv_client->netchan.message.size < MAX_MSG_SIZE / 2 && start < MAX_CONFIG_STRINGS) { if (sv.config_strings[start][0]) { Msg_WriteByte(&sv_client->netchan.message, SV_CMD_CONFIG_STRING); Msg_WriteShort(&sv_client->netchan.message, start); Msg_WriteString(&sv_client->netchan.message, sv.config_strings[start]); } start++; } // send next command if (start == MAX_CONFIG_STRINGS) { Msg_WriteByte(&sv_client->netchan.message, SV_CMD_CBUF_TEXT); Msg_WriteString(&sv_client->netchan.message, va("baselines %i 0\n", svs.spawn_count)); } else { Msg_WriteByte(&sv_client->netchan.message, SV_CMD_CBUF_TEXT); Msg_WriteString(&sv_client->netchan.message, va("config_strings %i %i\n", svs.spawn_count, start)); } }
/* * Sv_CheckCommandTimes * * Once per second, gives all clients an allotment of 1000 milliseconds * for their movement commands which will be decremented as we receive * new information from them. If they drift by a significant margin * over the next interval, assume they are trying to cheat. */ static void Sv_CheckCommandTimes(void) { static unsigned int last_check_time = -9999; int i; if (svs.real_time < last_check_time) { // wrap around from last level last_check_time = -9999; } // see if its time to check the movements if (svs.real_time - last_check_time < CMD_MSEC_CHECK_INTERVAL) { return; } last_check_time = svs.real_time; // inspect each client, ensuring they are reasonably in sync with us for (i = 0; i < sv_max_clients->integer; i++) { sv_client_t *cl = &svs.clients[i]; if (cl->state < SV_CLIENT_ACTIVE) { continue; } if (sv_enforce_time->value) { // check them if (abs(cl->cmd_msec) > CMD_MSEC_ALLOWABLE_DRIFT) { // irregular movement cl->cmd_msec_errors++; Com_Debug("%s drifted %dms\n", Sv_NetaddrToString(cl), cl->cmd_msec); if (cl->cmd_msec_errors >= sv_enforce_time->value) { Com_Warn("Sv_CheckCommandTimes: Too many errors from %s\n", Sv_NetaddrToString(cl)); Sv_KickClient(cl, "Irregular movement"); continue; } } else { // normal movement if (cl->cmd_msec_errors) { cl->cmd_msec_errors--; } } } cl->cmd_msec = CMD_MSEC_CHECK_INTERVAL; // reset for next cycle } }
/* * Sv_Baselines_f */ static void Sv_Baselines_f(void) { unsigned int start; entity_state_t nullstate; entity_state_t *base; Com_Debug("Baselines() from %s\n", Sv_NetaddrToString(sv_client)); if (sv_client->state != SV_CLIENT_CONNECTED) { Com_Warn("Sv_Baselines_f: %s already spawned\n", Sv_NetaddrToString(sv_client)); return; } // handle the case of a level changing while a client was connecting if (strtoul(Cmd_Argv(1), NULL, 0) != svs.spawn_count) { Com_Debug("Sv_Baselines_f: Stale spawn count from %s\n", Sv_NetaddrToString(sv_client)); Sv_New_f(); return; } start = strtoul(Cmd_Argv(2), NULL, 0); memset(&nullstate, 0, sizeof(nullstate)); // write a packet full of data while (sv_client->netchan.message.size < MAX_MSG_SIZE / 2 && start < MAX_EDICTS) { base = &sv.baselines[start]; if (base->model1 || base->sound || base->effects) { Msg_WriteByte(&sv_client->netchan.message, SV_CMD_ENTITY_BASELINE); Msg_WriteDeltaEntity(&nullstate, base, &sv_client->netchan.message, true, true); } start++; } // send next command if (start == MAX_EDICTS) { Msg_WriteByte(&sv_client->netchan.message, SV_CMD_CBUF_TEXT); Msg_WriteString(&sv_client->netchan.message, va("precache %i\n", svs.spawn_count)); } else { Msg_WriteByte(&sv_client->netchan.message, SV_CMD_CBUF_TEXT); Msg_WriteString(&sv_client->netchan.message, va("baselines %i %i\n", svs.spawn_count, start)); } }
/* * Sv_UserStringCommand * * Invoke the specified user string command. If we don't have a function for * it, pass it off to the game module. */ static void Sv_UserStringCommand(const char *s) { sv_user_string_cmd_t *c; Cmd_TokenizeString(s); if (strchr(s, '\xFF')) { // catch end of message exploit Com_Warn("Sv_ExecuteUserCommand: Illegal command from %s\n", Sv_NetaddrToString(sv_client)); Sv_KickClient(sv_client, NULL); return; } for (c = sv_user_string_cmds; c->name; c++) { if (!strcmp(Cmd_Argv(0), c->name)) { c->func(); break; } } if (!c->name) { // unmatched command if (sv.state == SV_ACTIVE_GAME) // maybe the game knows what to do with it svs.game->ClientCommand(sv_player); } }
/* * @brief Enforces safe user_info data before passing onto game module. */ void Sv_UserInfoChanged(sv_client_t *cl) { char *val; size_t i; if (*cl->user_info == '\0') { // catch empty user_info Com_Print("Empty user_info from %s\n", Sv_NetaddrToString(cl)); Sv_KickClient(cl, "Bad user info"); return; } if (strchr(cl->user_info, '\xFF')) { // catch end of message exploit Com_Print("Illegal user_info contained xFF from %s\n", Sv_NetaddrToString(cl)); Sv_KickClient(cl, "Bad user info"); return; } if (!ValidateUserInfo(cl->user_info)) { // catch otherwise invalid user_info Com_Print("Invalid user_info from %s\n", Sv_NetaddrToString(cl)); Sv_KickClient(cl, "Bad user info"); return; } // call game code to allow overrides svs.game->ClientUserInfoChanged(cl->entity, cl->user_info); // name for C code, mask off high bit g_strlcpy(cl->name, GetUserInfo(cl->user_info, "name"), sizeof(cl->name)); for (i = 0; i < sizeof(cl->name); i++) { cl->name[i] &= 127; } // rate command val = GetUserInfo(cl->user_info, "rate"); if (*val != '\0') { cl->rate = strtoul(val, NULL, 10); if (cl->rate > 0 && cl->rate < CLIENT_RATE_MIN) { cl->rate = CLIENT_RATE_MIN; } } // limit the print messages the client receives val = GetUserInfo(cl->user_info, "message_level"); if (*val != '\0') { cl->message_level = strtoul(val, NULL, 10); } }
/* * 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; } } }
/* * Sv_Download_f */ static void Sv_Download_f(void) { const char *name; void *buf; unsigned int i = 0, offset = 0; name = Cmd_Argv(1); if (Cmd_Argc() > 2) offset = strtoul(Cmd_Argv(2), NULL, 0); // downloaded offset // catch illegal offset or file_names if (*name == '.' || *name == '/' || *name == '\\' || strstr( name, "..")) { Com_Warn("Sv_Download_f: Malicious download (%s:%d) from %s\n", name, offset, Sv_NetaddrToString(sv_client)); Sv_KickClient(sv_client, NULL); return; } while (downloadable[i]) { // ensure download name is allowed if (GlobMatch(downloadable[i], name)) break; i++; } if (!downloadable[i]) { // it wasn't Com_Warn("Sv_Download_f: Illegal download (%s) from %s\n", name, Sv_NetaddrToString(sv_client)); Sv_KickClient(sv_client, NULL); return; } if (!sv_udp_download->value) { // lastly, ensure server wishes to allow Msg_WriteByte(&sv_client->netchan.message, SV_CMD_DOWNLOAD); Msg_WriteShort(&sv_client->netchan.message, -1); Msg_WriteByte(&sv_client->netchan.message, 0); return; } if (sv_client->download) // free last download Fs_FreeFile(sv_client->download); sv_client->download_size = Fs_LoadFile(name, &buf); sv_client->download = (byte *) buf; sv_client->download_count = offset; if (offset > sv_client->download_size) sv_client->download_count = sv_client->download_size; if (!sv_client->download) { // legal file name, but missing file Com_Warn("Sv_Download_f: Couldn't download %s to %s\n", name, Sv_NetaddrToString(sv_client)); Msg_WriteByte(&sv_client->netchan.message, SV_CMD_DOWNLOAD); Msg_WriteShort(&sv_client->netchan.message, -1); Msg_WriteByte(&sv_client->netchan.message, 0); return; } Sv_NextDownload_f(); Com_Debug("Downloading %s to %s\n", name, sv_client->name); }