1 /*
2 * Copyright(c) 1997-2001 Id Software, Inc.
3 * Copyright(c) 2002 The Quakeforge Project.
4 * Copyright(c) 2006 Quetoo.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or(at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14 *
15 * See the GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
20 */
21 
22 #include "server.h"
23 
24 edict_t *sv_player;
25 
26 /*
27 
28 USER STRINGCMD EXECUTION
29 
30 sv_client and sv_player will be valid.
31 */
32 
33 
34 /*
35 SV_BeginDemoServer
36 
37 Begin a demo server.  It is expected that the demo protocol has already
38 been resolved by SV_CheckDemoProtocol.  Therefore we simply open it.
39 */
SV_BeginDemoServer(void)40 void SV_BeginDemoServer(void){
41 	char demo[MAX_OSPATH];
42 
43 	Com_sprintf(demo, sizeof(demo), "demos/%s", sv.name);
44 	FS_FOpenFile(demo, &sv.demofile);
45 
46 	if(!sv.demofile)  // file was deleted during spawnserver
47 		Com_Error(ERR_DROP, "%s no longer exists\n", demo);
48 }
49 
50 
51 /*
52 SV_New_f
53 
54 Sends the first message from the server to a connected client.
55 This will be sent on the initial connection and upon each server load.
56 */
SV_New_f(void)57 void SV_New_f(void){
58 	char *gamedir;
59 	int playernum;
60 	edict_t *ent;
61 
62 	Com_DPrintf("New() from %s\n", sv_client->name);
63 
64 	if(sv_client->state != cs_connected){
65 		Com_Printf("New not valid -- already spawned\n");
66 		return;
67 	}
68 
69 	// demo servers just dump the file message
70 	if(sv.state == ss_demo){
71 		SV_BeginDemoServer();
72 		return;
73 	}
74 
75 	// serverdata required to make sure the protocol is right, and to set the gamedir
76 	gamedir = Cvar_VariableString("gamedir");
77 
78 	// send the serverdata
79 	MSG_WriteByte(&sv_client->netchan.message, svc_serverdata);
80 	MSG_WriteLong(&sv_client->netchan.message, PROTOCOL_34);
81 	MSG_WriteLong(&sv_client->netchan.message, svs.spawncount);
82 	MSG_WriteByte(&sv_client->netchan.message, 0);
83 	MSG_WriteString(&sv_client->netchan.message, gamedir);
84 
85 	playernum = sv_client - svs.clients;
86 	MSG_WriteShort(&sv_client->netchan.message, playernum);
87 
88 	// send full levelname
89 	MSG_WriteString(&sv_client->netchan.message, sv.configstrings[CS_NAME]);
90 
91 	// set up the entity for the client
92 	ent = EDICT_NUM(playernum + 1);
93 	ent->s.number = playernum + 1;
94 	sv_client->edict = ent;
95 	memset(&sv_client->lastcmd, 0, sizeof(sv_client->lastcmd));
96 
97 	// begin fetching configstrings
98 	MSG_WriteByte(&sv_client->netchan.message, svc_stufftext);
99 	MSG_WriteString(&sv_client->netchan.message, va("cmd configstrings %i 0\n", svs.spawncount));
100 }
101 
102 
103 /*
104 SV_Configstrings_f
105 */
SV_Configstrings_f(void)106 void SV_Configstrings_f(void){
107 	int start;
108 
109 	Com_DPrintf("Configstrings() from %s\n", sv_client->name);
110 
111 	if(sv_client->state != cs_connected){
112 		Com_Printf("configstrings not valid -- already spawned\n");
113 		return;
114 	}
115 
116 	// handle the case of a level changing while a client was connecting
117 	if(atoi(Cmd_Argv(1)) != svs.spawncount){
118 		Com_Printf("SV_Configstrings_f from different level\n");
119 		SV_New_f();
120 		return;
121 	}
122 
123 	start = atoi(Cmd_Argv(2));
124 
125 	if(start < 0){  // catch negative offset
126 		Com_Printf("Illegal configstring offset from %s\n", SV_ClientAdrToString(sv_client));
127 		SV_KickClient(sv_client, NULL);
128 		return;
129 	}
130 
131 	// write a packet full of data
132 	while(sv_client->netchan.message.cursize < MAX_MSGLEN / 2
133 			&& start < MAX_CONFIGSTRINGS){
134 		if(sv.configstrings[start][0]){
135 			MSG_WriteByte(&sv_client->netchan.message, svc_configstring);
136 			MSG_WriteShort(&sv_client->netchan.message, start);
137 			MSG_WriteString(&sv_client->netchan.message, sv.configstrings[start]);
138 		}
139 		start++;
140 	}
141 
142 	// send next command
143 	if(start == MAX_CONFIGSTRINGS){
144 		MSG_WriteByte(&sv_client->netchan.message, svc_stufftext);
145 		MSG_WriteString(&sv_client->netchan.message, va("cmd baselines %i 0\n", svs.spawncount));
146 	} else {
147 		MSG_WriteByte(&sv_client->netchan.message, svc_stufftext);
148 		MSG_WriteString(&sv_client->netchan.message, va("cmd configstrings %i %i\n", svs.spawncount, start));
149 	}
150 }
151 
152 /*
153 SV_Baselines_f
154 */
SV_Baselines_f(void)155 void SV_Baselines_f(void){
156 	int start;
157 	entity_state_t nullstate;
158 	entity_state_t *base;
159 
160 	Com_DPrintf("Baselines() from %s\n", sv_client->name);
161 
162 	if(sv_client->state != cs_connected){
163 		Com_Printf("baselines not valid -- already spawned\n");
164 		return;
165 	}
166 
167 	// handle the case of a level changing while a client was connecting
168 	if(atoi(Cmd_Argv(1)) != svs.spawncount){
169 		Com_Printf("SV_Baselines_f from different level\n");
170 		SV_New_f();
171 		return;
172 	}
173 
174 	start = atoi(Cmd_Argv(2));
175 
176 	if(start < 0){  // catch negative offset
177 		Com_Printf("Illegal baseline offset from %s\n", SV_ClientAdrToString(sv_client));
178 		SV_KickClient(sv_client, NULL);
179 		return;
180 	}
181 
182 	memset(&nullstate, 0, sizeof(nullstate));
183 
184 	// write a packet full of data
185 	while(sv_client->netchan.message.cursize < MAX_MSGLEN / 2
186 			&& start < MAX_EDICTS){
187 		base = &sv.baselines[start];
188 		if(base->modelindex || base->sound || base->effects){
189 			MSG_WriteByte(&sv_client->netchan.message, svc_spawnbaseline);
190 			MSG_WriteDeltaEntity(&nullstate, base, &sv_client->netchan.message, true, true);
191 		}
192 		start++;
193 	}
194 
195 	// send next command
196 	if(start == MAX_EDICTS){
197 		MSG_WriteByte(&sv_client->netchan.message, svc_stufftext);
198 		MSG_WriteString(&sv_client->netchan.message, va("precache %i\n", svs.spawncount));
199 	} else {
200 		MSG_WriteByte(&sv_client->netchan.message, svc_stufftext);
201 		MSG_WriteString(&sv_client->netchan.message, va("cmd baselines %i %i\n", svs.spawncount, start));
202 	}
203 }
204 
205 
206 /*
207 SV_Begin_f
208 */
SV_Begin_f(void)209 void SV_Begin_f(void){
210 	Com_DPrintf("Begin() from %s\n", sv_client->name);
211 
212 	if(sv_client->state != cs_connected){  // catch duplicate spawns
213 		Com_Printf("Illegal begin from %s\n", SV_ClientAdrToString(sv_client));
214 		SV_KickClient(sv_client, NULL);
215 		return;
216 	}
217 
218 	// handle the case of a level changing while a client was connecting
219 	if(atoi(Cmd_Argv(1)) != svs.spawncount){
220 		Com_Printf("SV_Begin_f from different level\n");
221 		SV_New_f();
222 		return;
223 	}
224 
225 	sv_client->state = cs_spawned;
226 
227 	// call the game begin function
228 	ge->ClientBegin(sv_player);
229 
230 	Cbuf_InsertFromDefer();
231 }
232 
233 
234 // svc_download can also take advantage of zlib compression
235 extern void SV_ZlibClientDatagram(client_t *client, sizebuf_t *msg);
236 
237 /*
238 SV_NextDownload_f
239 */
SV_NextDownload_f(void)240 void SV_NextDownload_f(void){
241 	int r, size, percent;
242 	byte buf[MAX_MSGLEN];
243 	sizebuf_t msg;
244 
245 	if(!sv_client->download)
246 		return;
247 
248 	r = sv_client->downloadsize - sv_client->downloadcount;
249 	if(r > 1280)  // stock quake2 sends 1024 byte chunks
250 		r = 1280;  // but i see no harm in sending another 256 bytes
251 
252 	SZ_Init(&msg, buf, MAX_MSGLEN);
253 
254 	MSG_WriteByte(&msg, svc_download);
255 	MSG_WriteShort(&msg, r);
256 
257 	sv_client->downloadcount += r;
258 	size = sv_client->downloadsize;
259 	if(!size)
260 		size = 1;
261 
262 	percent = sv_client->downloadcount * 100 / size;
263 	MSG_WriteByte(&msg, percent);
264 
265 	SZ_Write(&msg, sv_client->download + sv_client->downloadcount - r, r);
266 
267 	SV_ZlibClientDatagram(sv_client, &msg);  // plus we'll deflate it if possible
268 	SZ_Write(&sv_client->netchan.message, msg.data, msg.cursize);
269 
270 	if(sv_client->downloadcount != sv_client->downloadsize)
271 		return;
272 
273 	FS_FreeFile(sv_client->download);
274 	sv_client->download = NULL;
275 }
276 
277 
278 /*
279 SV_Download_f
280 */
SV_Download_f(void)281 void SV_Download_f(void){
282 	char *name;
283 	extern cvar_t *allow_download;
284 	extern cvar_t *allow_download_players;
285 	extern cvar_t *allow_download_models;
286 	extern cvar_t *allow_download_sounds;
287 	extern cvar_t *allow_download_maps;
288 	int offset = 0;
289 
290 	name = Cmd_Argv(1);
291 
292 	if(Cmd_Argc() > 2)
293 		offset = atoi(Cmd_Argv(2)); // downloaded offset
294 
295 	// catch illegal offset or filenames
296 	if(offset < 0 || *name == '.' || *name == '/' || *name == '\\' || strstr(name, "..")){
297 		Com_Printf("Illegal download offset from %s\n", SV_ClientAdrToString(sv_client));
298 		SV_KickClient(sv_client, NULL);
299 		return;
300 	}
301 
302 	if(!allow_download->value // first off global allow check
303 			|| (strncmp(name, "players/", 8) == 0 && !allow_download_players->value)
304 			|| (strncmp(name, "models/", 7) == 0 && !allow_download_models->value)
305 			|| (strncmp(name, "sound/", 6) == 0 && !allow_download_sounds->value)
306 			|| (strncmp(name, "maps/", 4) == 0 && !allow_download_maps->value)
307 			|| !strstr(name, "/")){
308 		MSG_WriteByte(&sv_client->netchan.message, svc_download);
309 		MSG_WriteShort(&sv_client->netchan.message, -1);
310 		MSG_WriteByte(&sv_client->netchan.message, 0);
311 		return;
312 	}
313 
314 	if(sv_client->download)  // free last download
315 		FS_FreeFile(sv_client->download);
316 
317 	sv_client->downloadsize = FS_LoadFile(name, (void **)(char *)&sv_client->download);
318 	sv_client->downloadcount = offset;
319 
320 	if(offset > sv_client->downloadsize)
321 		sv_client->downloadcount = sv_client->downloadsize;
322 
323 	if(!sv_client->download){  // legal filename, but missing file
324 		Com_DPrintf("Couldn't download %s to %s\n", name, sv_client->name);
325 		MSG_WriteByte(&sv_client->netchan.message, svc_download);
326 		MSG_WriteShort(&sv_client->netchan.message, -1);
327 		MSG_WriteByte(&sv_client->netchan.message, 0);
328 		return;
329 	}
330 
331 	SV_NextDownload_f();
332 	Com_DPrintf("Downloading %s to %s\n", name, sv_client->name);
333 }
334 
335 
336 /*
337 SV_Disconnect_f
338 
339 The client is going to disconnect, so remove the connection immediately
340 */
SV_Disconnect_f(void)341 void SV_Disconnect_f(void){
342 	SV_DropClient(sv_client);
343 }
344 
345 
346 /*
347 SV_Serverinfo_f
348 
349 Dumps the serverinfo info string
350 */
SV_Info_f(void)351 void SV_Info_f(void){
352 	cvar_t *cvar;
353 	char line[MAX_STRING_CHARS];
354 
355 	if(!sv_client){  //print to server console
356 		Info_Print(Cvar_Serverinfo());
357 		return;
358 	}
359 
360 	for(cvar = cvar_vars; cvar; cvar = cvar->next){
361 
362 		if(!(cvar->flags & CVAR_SERVERINFO))
363 			continue;  //only print serverinfo cvars
364 
365 		snprintf(line, MAX_STRING_CHARS, "%s %s\n", cvar->name, cvar->string);
366 		SV_ClientPrintf(sv_client, PRINT_MEDIUM, "%s", line);
367 	}
368 }
369 
370 
371 typedef struct {
372 	char *name;
373 	void (*func)(void);
374 } ucmd_t;
375 
376 ucmd_t ucmds[] = {
377 	{"new", SV_New_f},
378 	{"configstrings", SV_Configstrings_f},
379 	{"baselines", SV_Baselines_f},
380 	{"begin", SV_Begin_f},
381 	{"disconnect", SV_Disconnect_f},
382 	{"info", SV_Info_f},
383 	{"download", SV_Download_f},
384 	{"nextdl", SV_NextDownload_f},
385 	{NULL, NULL}
386 };
387 
388 /*
389 SV_ExecuteUserCommand
390 */
SV_ExecuteUserCommand(char * s)391 void SV_ExecuteUserCommand(char *s){
392 	ucmd_t *u;
393 
394 	/* http://www.quakesrc.org/forum/topicDisplay.php?topicID=160
395 	 * the client can read the rcon_password variable, among others
396 	 * so don't do any macro expansion on the server side */
397 	Cmd_TokenizeString(s, false);
398 
399 	if(strchr(s, '\xFF')){  // catch end of message exploit
400 		Com_Printf("Illegal command contained xFF from %s\n", SV_ClientAdrToString(sv_client));
401 		SV_KickClient(sv_client, NULL);
402 		return;
403 	}
404 
405 	sv_player = sv_client->edict;
406 
407 	for(u = ucmds; u->name; u++)
408 		if(!strcmp(Cmd_Argv(0), u->name)){
409 			u->func();
410 			break;
411 		}
412 
413 	if(!u->name && sv.state == ss_game)
414 		ge->ClientCommand(sv_player);
415 }
416 
417 /*
418 
419 USER CMD EXECUTION
420 
421 */
422 
423 
424 /*
425 SV_ClientThink
426 
427 Account for command timeslice and pass command to game dll.
428 */
SV_ClientThink(client_t * cl,usercmd_t * cmd)429 void SV_ClientThink(client_t *cl, usercmd_t *cmd){
430 	cl->commandMsec -= cmd->msec;
431 	ge->ClientThink(cl->edict, cmd);
432 }
433 
434 
435 #define MAX_STRINGCMDS 8
436 /*
437 SV_ExecuteClientMessage
438 
439 The current net_message is parsed for the given client
440 */
SV_ExecuteClientMessage(client_t * cl)441 void SV_ExecuteClientMessage(client_t *cl){
442 	int c;
443 	char *s;
444 
445 	usercmd_t nullcmd, oldest, oldcmd, newcmd;
446 	int net_drop;
447 	int stringCmdCount;
448 	qboolean move_issued;
449 	int lastframe;
450 
451 	sv_client = cl;
452 	sv_player = sv_client->edict;
453 
454 	// only allow one move command
455 	move_issued = false;
456 	stringCmdCount = 0;
457 
458 	while(1){
459 
460 		if(net_message.readcount > net_message.cursize){
461 			Com_Printf("SV_ReadClientMessage: badread\n");
462 			SV_DropClient(cl);
463 			return;
464 		}
465 
466 		c = MSG_ReadByte(&net_message);
467 		if(c == -1)
468 			break;
469 
470 		switch(c){
471 			case clc_nop:
472 				break;
473 
474 			case clc_userinfo:
475 				strncpy(cl->userinfo, MSG_ReadString(&net_message), sizeof(cl->userinfo) - 1);
476 				SV_UserinfoChanged(cl);
477 				break;
478 
479 			case clc_move:
480 				if(move_issued)
481 					return;  // someone is trying to cheat...
482 
483 				move_issued = true;
484 
485 				if(cl->netchan.protocol != PROTOCOL_R1Q2)
486 					MSG_ReadByte(&net_message);  // read checksum byte (ignored)
487 
488 				lastframe = MSG_ReadLong(&net_message);
489 				if(lastframe != cl->lastframe){
490 					cl->lastframe = lastframe;
491 					if(cl->lastframe > 0){
492 						cl->frame_latency[cl->lastframe & (LATENCY_COUNTS - 1)] =
493 							svs.realtime - cl->frames[cl->lastframe & UPDATE_MASK].senttime;
494 					}
495 				}
496 
497 				memset(&nullcmd, 0, sizeof(nullcmd));
498 				MSG_ReadDeltaUsercmd(&net_message, &nullcmd, &oldest);
499 				MSG_ReadDeltaUsercmd(&net_message, &oldest, &oldcmd);
500 				MSG_ReadDeltaUsercmd(&net_message, &oldcmd, &newcmd);
501 
502 				if(cl->state != cs_spawned){
503 					cl->lastframe = -1;
504 					break;
505 				}
506 				else if(nullcmd.msec > 250 || oldest.msec > 250 ||  // catch illegal msec
507 						oldcmd.msec > 250 || newcmd.msec > 250){
508 					Com_Printf("Illegal msec in usercmd from %s\n", SV_ClientAdrToString(cl));
509 					SV_KickClient(cl, NULL);
510 					return;
511 				}
512 
513 				if(!paused->value){
514 					net_drop = cl->netchan.dropped;
515 					if(net_drop < 20){
516 
517 						while(net_drop > 2){
518 							SV_ClientThink(cl, &cl->lastcmd);
519 							net_drop--;
520 						}
521 						if(net_drop > 1)
522 							SV_ClientThink(cl, &oldest);
523 						if(net_drop > 0)
524 							SV_ClientThink(cl, &oldcmd);
525 					}
526 					SV_ClientThink(cl, &newcmd);
527 				}
528 
529 				cl->lastcmd = newcmd;
530 				break;
531 
532 			case clc_stringcmd:
533 				s = MSG_ReadString(&net_message);
534 
535 				// malicious users may try using too many string commands
536 				if(++stringCmdCount < MAX_STRINGCMDS)
537 					SV_ExecuteUserCommand(s);
538 
539 				if(cl->state == cs_zombie)
540 					return;  // disconnect command
541 				break;
542 
543 			default:
544 				Com_Printf("SV_ReadClientMessage: unknown command %d\n", c);
545 				SV_DropClient(cl);
546 				return;
547 		}
548 	}
549 }
550