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