/* * Copyright(c) 1997-2001 Id Software, Inc. * Copyright(c) 2002 The Quakeforge Project. * Copyright(c) 2006 Quetoo. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or(at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "server.h" edict_t *sv_player; /* USER STRINGCMD EXECUTION sv_client and sv_player will be valid. */ /* SV_BeginDemoServer Begin a demo server. It is expected that the demo protocol has already been resolved by SV_CheckDemoProtocol. Therefore we simply open it. */ void SV_BeginDemoServer(void){ char demo[MAX_OSPATH]; Com_sprintf(demo, sizeof(demo), "demos/%s", sv.name); FS_FOpenFile(demo, &sv.demofile); if(!sv.demofile) // file was deleted during spawnserver Com_Error(ERR_DROP, "%s no longer exists\n", demo); } /* 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. */ void SV_New_f(void){ char *gamedir; int playernum; edict_t *ent; Com_DPrintf("New() from %s\n", sv_client->name); if(sv_client->state != cs_connected){ Com_Printf("New not valid -- already spawned\n"); return; } // demo servers just dump the file message if(sv.state == ss_demo){ SV_BeginDemoServer(); return; } // serverdata required to make sure the protocol is right, and to set the gamedir gamedir = Cvar_VariableString("gamedir"); // send the serverdata MSG_WriteByte(&sv_client->netchan.message, svc_serverdata); MSG_WriteLong(&sv_client->netchan.message, PROTOCOL_34); MSG_WriteLong(&sv_client->netchan.message, svs.spawncount); MSG_WriteByte(&sv_client->netchan.message, 0); MSG_WriteString(&sv_client->netchan.message, gamedir); playernum = sv_client - svs.clients; MSG_WriteShort(&sv_client->netchan.message, playernum); // send full levelname MSG_WriteString(&sv_client->netchan.message, sv.configstrings[CS_NAME]); // set up the entity for the client ent = EDICT_NUM(playernum + 1); ent->s.number = playernum + 1; sv_client->edict = ent; memset(&sv_client->lastcmd, 0, sizeof(sv_client->lastcmd)); // begin fetching configstrings MSG_WriteByte(&sv_client->netchan.message, svc_stufftext); MSG_WriteString(&sv_client->netchan.message, va("cmd configstrings %i 0\n", svs.spawncount)); } /* SV_Configstrings_f */ void SV_Configstrings_f(void){ int start; Com_DPrintf("Configstrings() from %s\n", sv_client->name); if(sv_client->state != cs_connected){ Com_Printf("configstrings not valid -- already spawned\n"); return; } // handle the case of a level changing while a client was connecting if(atoi(Cmd_Argv(1)) != svs.spawncount){ Com_Printf("SV_Configstrings_f from different level\n"); SV_New_f(); return; } start = atoi(Cmd_Argv(2)); if(start < 0){ // catch negative offset Com_Printf("Illegal configstring offset from %s\n", SV_ClientAdrToString(sv_client)); SV_KickClient(sv_client, NULL); return; } // write a packet full of data while(sv_client->netchan.message.cursize < MAX_MSGLEN / 2 && start < MAX_CONFIGSTRINGS){ if(sv.configstrings[start][0]){ MSG_WriteByte(&sv_client->netchan.message, svc_configstring); MSG_WriteShort(&sv_client->netchan.message, start); MSG_WriteString(&sv_client->netchan.message, sv.configstrings[start]); } start++; } // send next command if(start == MAX_CONFIGSTRINGS){ MSG_WriteByte(&sv_client->netchan.message, svc_stufftext); MSG_WriteString(&sv_client->netchan.message, va("cmd baselines %i 0\n", svs.spawncount)); } else { MSG_WriteByte(&sv_client->netchan.message, svc_stufftext); MSG_WriteString(&sv_client->netchan.message, va("cmd configstrings %i %i\n", svs.spawncount, start)); } } /* SV_Baselines_f */ void SV_Baselines_f(void){ int start; entity_state_t nullstate; entity_state_t *base; Com_DPrintf("Baselines() from %s\n", sv_client->name); if(sv_client->state != cs_connected){ Com_Printf("baselines not valid -- already spawned\n"); return; } // handle the case of a level changing while a client was connecting if(atoi(Cmd_Argv(1)) != svs.spawncount){ Com_Printf("SV_Baselines_f from different level\n"); SV_New_f(); return; } start = atoi(Cmd_Argv(2)); if(start < 0){ // catch negative offset Com_Printf("Illegal baseline offset from %s\n", SV_ClientAdrToString(sv_client)); SV_KickClient(sv_client, NULL); return; } memset(&nullstate, 0, sizeof(nullstate)); // write a packet full of data while(sv_client->netchan.message.cursize < MAX_MSGLEN / 2 && start < MAX_EDICTS){ base = &sv.baselines[start]; if(base->modelindex || base->sound || base->effects){ MSG_WriteByte(&sv_client->netchan.message, svc_spawnbaseline); MSG_WriteDeltaEntity(&nullstate, base, &sv_client->netchan.message, true, true); } start++; } // send next command if(start == MAX_EDICTS){ MSG_WriteByte(&sv_client->netchan.message, svc_stufftext); MSG_WriteString(&sv_client->netchan.message, va("precache %i\n", svs.spawncount)); } else { MSG_WriteByte(&sv_client->netchan.message, svc_stufftext); MSG_WriteString(&sv_client->netchan.message, va("cmd baselines %i %i\n", svs.spawncount, start)); } } /* SV_Begin_f */ void SV_Begin_f(void){ Com_DPrintf("Begin() from %s\n", sv_client->name); if(sv_client->state != cs_connected){ // catch duplicate spawns Com_Printf("Illegal begin from %s\n", SV_ClientAdrToString(sv_client)); SV_KickClient(sv_client, NULL); return; } // handle the case of a level changing while a client was connecting if(atoi(Cmd_Argv(1)) != svs.spawncount){ Com_Printf("SV_Begin_f from different level\n"); SV_New_f(); return; } sv_client->state = cs_spawned; // call the game begin function ge->ClientBegin(sv_player); Cbuf_InsertFromDefer(); } // svc_download can also take advantage of zlib compression extern void SV_ZlibClientDatagram(client_t *client, sizebuf_t *msg); /* SV_NextDownload_f */ void SV_NextDownload_f(void){ int r, size, percent; byte buf[MAX_MSGLEN]; sizebuf_t msg; if(!sv_client->download) return; r = sv_client->downloadsize - sv_client->downloadcount; if(r > 1280) // stock quake2 sends 1024 byte chunks r = 1280; // but i see no harm in sending another 256 bytes SZ_Init(&msg, buf, MAX_MSGLEN); MSG_WriteByte(&msg, svc_download); MSG_WriteShort(&msg, r); sv_client->downloadcount += r; size = sv_client->downloadsize; if(!size) size = 1; percent = sv_client->downloadcount * 100 / size; MSG_WriteByte(&msg, percent); SZ_Write(&msg, sv_client->download + sv_client->downloadcount - r, r); SV_ZlibClientDatagram(sv_client, &msg); // plus we'll deflate it if possible SZ_Write(&sv_client->netchan.message, msg.data, msg.cursize); if(sv_client->downloadcount != sv_client->downloadsize) return; FS_FreeFile(sv_client->download); sv_client->download = NULL; } /* SV_Download_f */ void SV_Download_f(void){ char *name; extern cvar_t *allow_download; extern cvar_t *allow_download_players; extern cvar_t *allow_download_models; extern cvar_t *allow_download_sounds; extern cvar_t *allow_download_maps; int offset = 0; name = Cmd_Argv(1); if(Cmd_Argc() > 2) offset = atoi(Cmd_Argv(2)); // downloaded offset // catch illegal offset or filenames if(offset < 0 || *name == '.' || *name == '/' || *name == '\\' || strstr(name, "..")){ Com_Printf("Illegal download offset from %s\n", SV_ClientAdrToString(sv_client)); SV_KickClient(sv_client, NULL); return; } if(!allow_download->value // first off global allow check || (strncmp(name, "players/", 8) == 0 && !allow_download_players->value) || (strncmp(name, "models/", 7) == 0 && !allow_download_models->value) || (strncmp(name, "sound/", 6) == 0 && !allow_download_sounds->value) || (strncmp(name, "maps/", 4) == 0 && !allow_download_maps->value) || !strstr(name, "/")){ MSG_WriteByte(&sv_client->netchan.message, svc_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->downloadsize = FS_LoadFile(name, (void **)(char *)&sv_client->download); sv_client->downloadcount = offset; if(offset > sv_client->downloadsize) sv_client->downloadcount = sv_client->downloadsize; if(!sv_client->download){ // legal filename, but missing file Com_DPrintf("Couldn't download %s to %s\n", name, sv_client->name); MSG_WriteByte(&sv_client->netchan.message, svc_download); MSG_WriteShort(&sv_client->netchan.message, -1); MSG_WriteByte(&sv_client->netchan.message, 0); return; } SV_NextDownload_f(); Com_DPrintf("Downloading %s to %s\n", name, sv_client->name); } /* SV_Disconnect_f The client is going to disconnect, so remove the connection immediately */ void SV_Disconnect_f(void){ SV_DropClient(sv_client); } /* SV_Serverinfo_f Dumps the serverinfo info string */ void SV_Info_f(void){ cvar_t *cvar; char line[MAX_STRING_CHARS]; if(!sv_client){ //print to server console Info_Print(Cvar_Serverinfo()); return; } for(cvar = cvar_vars; cvar; cvar = cvar->next){ if(!(cvar->flags & CVAR_SERVERINFO)) continue; //only print serverinfo cvars snprintf(line, MAX_STRING_CHARS, "%s %s\n", cvar->name, cvar->string); SV_ClientPrintf(sv_client, PRINT_MEDIUM, line); } } typedef struct { char *name; void (*func)(void); } ucmd_t; ucmd_t ucmds[] = { {"new", SV_New_f}, {"configstrings", SV_Configstrings_f}, {"baselines", SV_Baselines_f}, {"begin", SV_Begin_f}, {"disconnect", SV_Disconnect_f}, {"info", SV_Info_f}, {"download", SV_Download_f}, {"nextdl", SV_NextDownload_f}, {NULL, NULL} }; /* SV_ExecuteUserCommand */ void SV_ExecuteUserCommand(char *s){ ucmd_t *u; /* http://www.quakesrc.org/forum/topicDisplay.php?topicID=160 * the client can read the rcon_password variable, among others * so don't do any macro expansion on the server side */ Cmd_TokenizeString(s, false); if(strchr(s, '\xFF')){ // catch end of message exploit Com_Printf("Illegal command contained xFF from %s\n", SV_ClientAdrToString(sv_client)); SV_KickClient(sv_client, NULL); return; } sv_player = sv_client->edict; for(u = ucmds; u->name; u++) if(!strcmp(Cmd_Argv(0), u->name)){ u->func(); break; } if(!u->name && sv.state == ss_game) ge->ClientCommand(sv_player); } /* USER CMD EXECUTION */ /* SV_ClientThink Account for command timeslice and pass command to game dll. */ void SV_ClientThink(client_t *cl, usercmd_t *cmd){ cl->commandMsec -= cmd->msec; ge->ClientThink(cl->edict, cmd); } #define MAX_STRINGCMDS 8 /* 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, oldest, oldcmd, newcmd; int net_drop; int stringCmdCount; 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){ 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; if(cl->netchan.protocol != PROTOCOL_R1Q2) MSG_ReadByte(&net_message); // read checksum byte (ignored) 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; } else if(nullcmd.msec > 250 || oldest.msec > 250 || // catch illegal msec oldcmd.msec > 250 || newcmd.msec > 250){ Com_Printf("Illegal msec in usercmd from %s\n", SV_ClientAdrToString(cl)); SV_KickClient(cl, NULL); return; } if(!paused->value){ net_drop = cl->netchan.dropped; if(net_drop < 20){ 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; default: Com_Printf("SV_ReadClientMessage: unknown command %d\n", c); SV_DropClient(cl); return; } } }