~ubuntu-branches/ubuntu/precise/openarena/precise

« back to all changes in this revision

Viewing changes to code/server/sv_client.c

  • Committer: Bazaar Package Importer
  • Author(s): Bruno "Fuddl" Kleinert
  • Date: 2007-01-20 12:28:09 UTC
  • Revision ID: james.westby@ubuntu.com-20070120122809-2yza5ojt7nqiyiam
Tags: upstream-0.6.0
ImportĀ upstreamĀ versionĀ 0.6.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
===========================================================================
 
3
Copyright (C) 1999-2005 Id Software, Inc.
 
4
 
 
5
This file is part of Quake III Arena source code.
 
6
 
 
7
Quake III Arena source code is free software; you can redistribute it
 
8
and/or modify it under the terms of the GNU General Public License as
 
9
published by the Free Software Foundation; either version 2 of the License,
 
10
or (at your option) any later version.
 
11
 
 
12
Quake III Arena source code is distributed in the hope that it will be
 
13
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
 
14
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
15
GNU General Public License for more details.
 
16
 
 
17
You should have received a copy of the GNU General Public License
 
18
along with Quake III Arena source code; if not, write to the Free Software
 
19
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 
20
===========================================================================
 
21
*/
 
22
// sv_client.c -- server code for dealing with clients
 
23
 
 
24
#include "server.h"
 
25
 
 
26
static void SV_CloseDownload( client_t *cl );
 
27
 
 
28
/*
 
29
=================
 
30
SV_GetChallenge
 
31
 
 
32
A "getchallenge" OOB command has been received
 
33
Returns a challenge number that can be used
 
34
in a subsequent connectResponse command.
 
35
We do this to prevent denial of service attacks that
 
36
flood the server with invalid connection IPs.  With a
 
37
challenge, they must give a valid IP address.
 
38
 
 
39
If we are authorizing, a challenge request will cause a packet
 
40
to be sent to the authorize server.
 
41
 
 
42
When an authorizeip is returned, a challenge response will be
 
43
sent to that ip.
 
44
=================
 
45
*/
 
46
void SV_GetChallenge( netadr_t from ) {
 
47
        int             i;
 
48
        int             oldest;
 
49
        int             oldestTime;
 
50
        challenge_t     *challenge;
 
51
 
 
52
        // ignore if we are in single player
 
53
        if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER || Cvar_VariableValue("ui_singlePlayerActive")) {
 
54
                return;
 
55
        }
 
56
 
 
57
        oldest = 0;
 
58
        oldestTime = 0x7fffffff;
 
59
 
 
60
        // see if we already have a challenge for this ip
 
61
        challenge = &svs.challenges[0];
 
62
        for (i = 0 ; i < MAX_CHALLENGES ; i++, challenge++) {
 
63
                if ( !challenge->connected && NET_CompareAdr( from, challenge->adr ) ) {
 
64
                        break;
 
65
                }
 
66
                if ( challenge->time < oldestTime ) {
 
67
                        oldestTime = challenge->time;
 
68
                        oldest = i;
 
69
                }
 
70
        }
 
71
 
 
72
        if (i == MAX_CHALLENGES) {
 
73
                // this is the first time this client has asked for a challenge
 
74
                challenge = &svs.challenges[oldest];
 
75
 
 
76
                challenge->challenge = ( (rand() << 16) ^ rand() ) ^ svs.time;
 
77
                challenge->adr = from;
 
78
                challenge->firstTime = svs.time;
 
79
                challenge->time = svs.time;
 
80
                challenge->connected = qfalse;
 
81
                i = oldest;
 
82
        }
 
83
 
 
84
        // if they are on a lan address, send the challengeResponse immediately
 
85
        if ( Sys_IsLANAddress( from ) ) {
 
86
                challenge->pingTime = svs.time;
 
87
                NET_OutOfBandPrint( NS_SERVER, from, "challengeResponse %i", challenge->challenge );
 
88
                return;
 
89
        }
 
90
 
 
91
        // if there's no authorize server defined, just let them in
 
92
        if(strlen(AUTHORIZE_SERVER_NAME) < 1)
 
93
        {
 
94
                Com_Printf("Not authorizing client due to lack of auth server\n");
 
95
                challenge->pingTime = svs.time;
 
96
                NET_OutOfBandPrint( NS_SERVER, from, "challengeResponse %i", challenge->challenge );
 
97
                return;
 
98
        }
 
99
 
 
100
        // look up the authorize server's IP
 
101
        if ( !svs.authorizeAddress.ip[0] && svs.authorizeAddress.type != NA_BAD ) {
 
102
                Com_Printf( "Resolving %s\n", AUTHORIZE_SERVER_NAME );
 
103
                if ( !NET_StringToAdr( AUTHORIZE_SERVER_NAME, &svs.authorizeAddress ) ) {
 
104
                        Com_Printf( "Couldn't resolve address\n" );
 
105
                        return;
 
106
                }
 
107
                svs.authorizeAddress.port = BigShort( PORT_AUTHORIZE );
 
108
                Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", AUTHORIZE_SERVER_NAME,
 
109
                        svs.authorizeAddress.ip[0], svs.authorizeAddress.ip[1],
 
110
                        svs.authorizeAddress.ip[2], svs.authorizeAddress.ip[3],
 
111
                        BigShort( svs.authorizeAddress.port ) );
 
112
        }
 
113
 
 
114
        // if they have been challenging for a long time and we
 
115
        // haven't heard anything from the authorize server, go ahead and
 
116
        // let them in, assuming the id server is down
 
117
        if ( svs.time - challenge->firstTime > AUTHORIZE_TIMEOUT ) {
 
118
                Com_DPrintf( "authorize server timed out\n" );
 
119
 
 
120
                challenge->pingTime = svs.time;
 
121
                NET_OutOfBandPrint( NS_SERVER, challenge->adr, 
 
122
                        "challengeResponse %i", challenge->challenge );
 
123
                return;
 
124
        }
 
125
 
 
126
        // otherwise send their ip to the authorize server
 
127
        if ( svs.authorizeAddress.type != NA_BAD ) {
 
128
                cvar_t  *fs;
 
129
                char    game[1024];
 
130
 
 
131
                Com_DPrintf( "sending getIpAuthorize for %s\n", NET_AdrToString( from ));
 
132
                
 
133
                strcpy(game, BASEGAME);
 
134
                fs = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO );
 
135
                if (fs && fs->string[0] != 0) {
 
136
                        strcpy(game, fs->string);
 
137
                }
 
138
                
 
139
                // the 0 is for backwards compatibility with obsolete sv_allowanonymous flags
 
140
                // getIpAuthorize <challenge> <IP> <game> 0 <auth-flag>
 
141
                NET_OutOfBandPrint( NS_SERVER, svs.authorizeAddress,
 
142
                        "getIpAuthorize %i %i.%i.%i.%i %s 0 %s",  svs.challenges[i].challenge,
 
143
                        from.ip[0], from.ip[1], from.ip[2], from.ip[3], game, sv_strictAuth->string );
 
144
        }
 
145
}
 
146
 
 
147
/*
 
148
====================
 
149
SV_AuthorizeIpPacket
 
150
 
 
151
A packet has been returned from the authorize server.
 
152
If we have a challenge adr for that ip, send the
 
153
challengeResponse to it
 
154
====================
 
155
*/
 
156
void SV_AuthorizeIpPacket( netadr_t from ) {
 
157
        int             challenge;
 
158
        int             i;
 
159
        char    *s;
 
160
        char    *r;
 
161
        char    ret[1024];
 
162
 
 
163
        if ( !NET_CompareBaseAdr( from, svs.authorizeAddress ) ) {
 
164
                Com_Printf( "SV_AuthorizeIpPacket: not from authorize server\n" );
 
165
                return;
 
166
        }
 
167
 
 
168
        challenge = atoi( Cmd_Argv( 1 ) );
 
169
 
 
170
        for (i = 0 ; i < MAX_CHALLENGES ; i++) {
 
171
                if ( svs.challenges[i].challenge == challenge ) {
 
172
                        break;
 
173
                }
 
174
        }
 
175
        if ( i == MAX_CHALLENGES ) {
 
176
                Com_Printf( "SV_AuthorizeIpPacket: challenge not found\n" );
 
177
                return;
 
178
        }
 
179
 
 
180
        // send a packet back to the original client
 
181
        svs.challenges[i].pingTime = svs.time;
 
182
        s = Cmd_Argv( 2 );
 
183
        r = Cmd_Argv( 3 );                      // reason
 
184
 
 
185
        if ( !Q_stricmp( s, "demo" ) ) {
 
186
                if ( Cvar_VariableValue( "fs_restrict" ) ) {
 
187
                        // a demo client connecting to a demo server
 
188
                        NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, 
 
189
                                "challengeResponse %i", svs.challenges[i].challenge );
 
190
                        return;
 
191
                }
 
192
                // they are a demo client trying to connect to a real server
 
193
                NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, "print\nServer is not a demo server\n" );
 
194
                // clear the challenge record so it won't timeout and let them through
 
195
                Com_Memset( &svs.challenges[i], 0, sizeof( svs.challenges[i] ) );
 
196
                return;
 
197
        }
 
198
        if ( !Q_stricmp( s, "accept" ) ) {
 
199
                NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, 
 
200
                        "challengeResponse %i", svs.challenges[i].challenge );
 
201
                return;
 
202
        }
 
203
        if ( !Q_stricmp( s, "unknown" ) ) {
 
204
                if (!r) {
 
205
                        NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, "print\nAwaiting CD key authorization\n" );
 
206
                } else {
 
207
                        sprintf(ret, "print\n%s\n", r);
 
208
                        NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, ret );
 
209
                }
 
210
                // clear the challenge record so it won't timeout and let them through
 
211
                Com_Memset( &svs.challenges[i], 0, sizeof( svs.challenges[i] ) );
 
212
                return;
 
213
        }
 
214
 
 
215
        // authorization failed
 
216
        if (!r) {
 
217
                NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, "print\nSomeone is using this CD Key\n" );
 
218
        } else {
 
219
                sprintf(ret, "print\n%s\n", r);
 
220
                NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, ret );
 
221
        }
 
222
 
 
223
        // clear the challenge record so it won't timeout and let them through
 
224
        Com_Memset( &svs.challenges[i], 0, sizeof( svs.challenges[i] ) );
 
225
}
 
226
 
 
227
/*
 
228
==================
 
229
SV_DirectConnect
 
230
 
 
231
A "connect" OOB command has been received
 
232
==================
 
233
*/
 
234
 
 
235
#define PB_MESSAGE "PunkBuster Anti-Cheat software must be installed " \
 
236
                                "and Enabled in order to join this server. An updated game patch can be downloaded from " \
 
237
                                "www.idsoftware.com"
 
238
 
 
239
void SV_DirectConnect( netadr_t from ) {
 
240
        char            userinfo[MAX_INFO_STRING];
 
241
        int                     i;
 
242
        client_t        *cl, *newcl;
 
243
        client_t        temp;
 
244
        sharedEntity_t *ent;
 
245
        int                     clientNum;
 
246
        int                     version;
 
247
        int                     qport;
 
248
        int                     challenge;
 
249
        char            *password;
 
250
        int                     startIndex;
 
251
        intptr_t                denied;
 
252
        int                     count;
 
253
 
 
254
        Com_DPrintf ("SVC_DirectConnect ()\n");
 
255
 
 
256
        Q_strncpyz( userinfo, Cmd_Argv(1), sizeof(userinfo) );
 
257
 
 
258
        version = atoi( Info_ValueForKey( userinfo, "protocol" ) );
 
259
        if ( version != PROTOCOL_VERSION ) {
 
260
                NET_OutOfBandPrint( NS_SERVER, from, "print\nServer uses protocol version %i.\n", PROTOCOL_VERSION );
 
261
                Com_DPrintf ("    rejected connect from version %i\n", version);
 
262
                return;
 
263
        }
 
264
 
 
265
        challenge = atoi( Info_ValueForKey( userinfo, "challenge" ) );
 
266
        qport = atoi( Info_ValueForKey( userinfo, "qport" ) );
 
267
 
 
268
        // quick reject
 
269
        for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) {
 
270
                if ( cl->state == CS_FREE ) {
 
271
                        continue;
 
272
                }
 
273
                if ( NET_CompareBaseAdr( from, cl->netchan.remoteAddress )
 
274
                        && ( cl->netchan.qport == qport 
 
275
                        || from.port == cl->netchan.remoteAddress.port ) ) {
 
276
                        if (( svs.time - cl->lastConnectTime) 
 
277
                                < (sv_reconnectlimit->integer * 1000)) {
 
278
                                Com_DPrintf ("%s:reconnect rejected : too soon\n", NET_AdrToString (from));
 
279
                                return;
 
280
                        }
 
281
                        break;
 
282
                }
 
283
        }
 
284
 
 
285
        // see if the challenge is valid (LAN clients don't need to challenge)
 
286
        if ( !NET_IsLocalAddress (from) ) {
 
287
                int             ping;
 
288
 
 
289
                for (i=0 ; i<MAX_CHALLENGES ; i++) {
 
290
                        if (NET_CompareAdr(from, svs.challenges[i].adr)) {
 
291
                                if ( challenge == svs.challenges[i].challenge ) {
 
292
                                        break;          // good
 
293
                                }
 
294
                        }
 
295
                }
 
296
                if (i == MAX_CHALLENGES) {
 
297
                        NET_OutOfBandPrint( NS_SERVER, from, "print\nNo or bad challenge for address.\n" );
 
298
                        return;
 
299
                }
 
300
                // force the IP key/value pair so the game can filter based on ip
 
301
                Info_SetValueForKey( userinfo, "ip", NET_AdrToString( from ) );
 
302
 
 
303
                ping = svs.time - svs.challenges[i].pingTime;
 
304
                Com_Printf( "Client %i connecting with %i challenge ping\n", i, ping );
 
305
                svs.challenges[i].connected = qtrue;
 
306
 
 
307
                // never reject a LAN client based on ping
 
308
                if ( !Sys_IsLANAddress( from ) ) {
 
309
                        if ( sv_minPing->value && ping < sv_minPing->value ) {
 
310
                                // don't let them keep trying until they get a big delay
 
311
                                NET_OutOfBandPrint( NS_SERVER, from, "print\nServer is for high pings only\n" );
 
312
                                Com_DPrintf ("Client %i rejected on a too low ping\n", i);
 
313
                                // reset the address otherwise their ping will keep increasing
 
314
                                // with each connect message and they'd eventually be able to connect
 
315
                                svs.challenges[i].adr.port = 0;
 
316
                                return;
 
317
                        }
 
318
                        if ( sv_maxPing->value && ping > sv_maxPing->value ) {
 
319
                                NET_OutOfBandPrint( NS_SERVER, from, "print\nServer is for low pings only\n" );
 
320
                                Com_DPrintf ("Client %i rejected on a too high ping\n", i);
 
321
                                return;
 
322
                        }
 
323
                }
 
324
        } else {
 
325
                // force the "ip" info key to "localhost"
 
326
                Info_SetValueForKey( userinfo, "ip", "localhost" );
 
327
        }
 
328
 
 
329
        newcl = &temp;
 
330
        Com_Memset (newcl, 0, sizeof(client_t));
 
331
 
 
332
        // if there is already a slot for this ip, reuse it
 
333
        for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) {
 
334
                if ( cl->state == CS_FREE ) {
 
335
                        continue;
 
336
                }
 
337
                if ( NET_CompareBaseAdr( from, cl->netchan.remoteAddress )
 
338
                        && ( cl->netchan.qport == qport 
 
339
                        || from.port == cl->netchan.remoteAddress.port ) ) {
 
340
                        Com_Printf ("%s:reconnect\n", NET_AdrToString (from));
 
341
                        newcl = cl;
 
342
 
 
343
                        // this doesn't work because it nukes the players userinfo
 
344
 
 
345
//                      // disconnect the client from the game first so any flags the
 
346
//                      // player might have are dropped
 
347
//                      VM_Call( gvm, GAME_CLIENT_DISCONNECT, newcl - svs.clients );
 
348
                        //
 
349
                        goto gotnewcl;
 
350
                }
 
351
        }
 
352
 
 
353
        // find a client slot
 
354
        // if "sv_privateClients" is set > 0, then that number
 
355
        // of client slots will be reserved for connections that
 
356
        // have "password" set to the value of "sv_privatePassword"
 
357
        // Info requests will report the maxclients as if the private
 
358
        // slots didn't exist, to prevent people from trying to connect
 
359
        // to a full server.
 
360
        // This is to allow us to reserve a couple slots here on our
 
361
        // servers so we can play without having to kick people.
 
362
 
 
363
        // check for privateClient password
 
364
        password = Info_ValueForKey( userinfo, "password" );
 
365
        if ( !strcmp( password, sv_privatePassword->string ) ) {
 
366
                startIndex = 0;
 
367
        } else {
 
368
                // skip past the reserved slots
 
369
                startIndex = sv_privateClients->integer;
 
370
        }
 
371
 
 
372
        newcl = NULL;
 
373
        for ( i = startIndex; i < sv_maxclients->integer ; i++ ) {
 
374
                cl = &svs.clients[i];
 
375
                if (cl->state == CS_FREE) {
 
376
                        newcl = cl;
 
377
                        break;
 
378
                }
 
379
        }
 
380
 
 
381
        if ( !newcl ) {
 
382
                if ( NET_IsLocalAddress( from ) ) {
 
383
                        count = 0;
 
384
                        for ( i = startIndex; i < sv_maxclients->integer ; i++ ) {
 
385
                                cl = &svs.clients[i];
 
386
                                if (cl->netchan.remoteAddress.type == NA_BOT) {
 
387
                                        count++;
 
388
                                }
 
389
                        }
 
390
                        // if they're all bots
 
391
                        if (count >= sv_maxclients->integer - startIndex) {
 
392
                                SV_DropClient(&svs.clients[sv_maxclients->integer - 1], "only bots on server");
 
393
                                newcl = &svs.clients[sv_maxclients->integer - 1];
 
394
                        }
 
395
                        else {
 
396
                                Com_Error( ERR_FATAL, "server is full on local connect\n" );
 
397
                                return;
 
398
                        }
 
399
                }
 
400
                else {
 
401
                        NET_OutOfBandPrint( NS_SERVER, from, "print\nServer is full.\n" );
 
402
                        Com_DPrintf ("Rejected a connection.\n");
 
403
                        return;
 
404
                }
 
405
        }
 
406
 
 
407
        // we got a newcl, so reset the reliableSequence and reliableAcknowledge
 
408
        cl->reliableAcknowledge = 0;
 
409
        cl->reliableSequence = 0;
 
410
 
 
411
gotnewcl:       
 
412
        // build a new connection
 
413
        // accept the new client
 
414
        // this is the only place a client_t is ever initialized
 
415
        *newcl = temp;
 
416
        clientNum = newcl - svs.clients;
 
417
        ent = SV_GentityNum( clientNum );
 
418
        newcl->gentity = ent;
 
419
 
 
420
        // save the challenge
 
421
        newcl->challenge = challenge;
 
422
 
 
423
        // save the address
 
424
        Netchan_Setup (NS_SERVER, &newcl->netchan , from, qport);
 
425
        // init the netchan queue
 
426
        newcl->netchan_end_queue = &newcl->netchan_start_queue;
 
427
 
 
428
        // save the userinfo
 
429
        Q_strncpyz( newcl->userinfo, userinfo, sizeof(newcl->userinfo) );
 
430
 
 
431
        // get the game a chance to reject this connection or modify the userinfo
 
432
        denied = VM_Call( gvm, GAME_CLIENT_CONNECT, clientNum, qtrue, qfalse ); // firstTime = qtrue
 
433
        if ( denied ) {
 
434
                // we can't just use VM_ArgPtr, because that is only valid inside a VM_Call
 
435
                char *str = VM_ExplicitArgPtr( gvm, denied );
 
436
 
 
437
                NET_OutOfBandPrint( NS_SERVER, from, "print\n%s\n", str );
 
438
                Com_DPrintf ("Game rejected a connection: %s.\n", str);
 
439
                return;
 
440
        }
 
441
 
 
442
        SV_UserinfoChanged( newcl );
 
443
 
 
444
        // send the connect packet to the client
 
445
        NET_OutOfBandPrint( NS_SERVER, from, "connectResponse" );
 
446
 
 
447
        Com_DPrintf( "Going from CS_FREE to CS_CONNECTED for %s\n", newcl->name );
 
448
 
 
449
        newcl->state = CS_CONNECTED;
 
450
        newcl->nextSnapshotTime = svs.time;
 
451
        newcl->lastPacketTime = svs.time;
 
452
        newcl->lastConnectTime = svs.time;
 
453
        
 
454
        // when we receive the first packet from the client, we will
 
455
        // notice that it is from a different serverid and that the
 
456
        // gamestate message was not just sent, forcing a retransmit
 
457
        newcl->gamestateMessageNum = -1;
 
458
 
 
459
        // if this was the first client on the server, or the last client
 
460
        // the server can hold, send a heartbeat to the master.
 
461
        count = 0;
 
462
        for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) {
 
463
                if ( svs.clients[i].state >= CS_CONNECTED ) {
 
464
                        count++;
 
465
                }
 
466
        }
 
467
        if ( count == 1 || count == sv_maxclients->integer ) {
 
468
                SV_Heartbeat_f();
 
469
        }
 
470
}
 
471
 
 
472
 
 
473
/*
 
474
=====================
 
475
SV_DropClient
 
476
 
 
477
Called when the player is totally leaving the server, either willingly
 
478
or unwillingly.  This is NOT called if the entire server is quiting
 
479
or crashing -- SV_FinalMessage() will handle that
 
480
=====================
 
481
*/
 
482
void SV_DropClient( client_t *drop, const char *reason ) {
 
483
        int             i;
 
484
        challenge_t     *challenge;
 
485
 
 
486
        if ( drop->state == CS_ZOMBIE ) {
 
487
                return;         // already dropped
 
488
        }
 
489
 
 
490
        if (drop->netchan.remoteAddress.type != NA_BOT) {
 
491
                // see if we already have a challenge for this ip
 
492
                challenge = &svs.challenges[0];
 
493
 
 
494
                for (i = 0 ; i < MAX_CHALLENGES ; i++, challenge++) {
 
495
                        if ( NET_CompareAdr( drop->netchan.remoteAddress, challenge->adr ) ) {
 
496
                                challenge->connected = qfalse;
 
497
                                break;
 
498
                        }
 
499
                }
 
500
        }
 
501
 
 
502
        // Kill any download
 
503
        SV_CloseDownload( drop );
 
504
 
 
505
        // tell everyone why they got dropped
 
506
        SV_SendServerCommand( NULL, "print \"%s" S_COLOR_WHITE " %s\n\"", drop->name, reason );
 
507
 
 
508
        Com_DPrintf( "Going to CS_ZOMBIE for %s\n", drop->name );
 
509
        drop->state = CS_ZOMBIE;                // become free in a few seconds
 
510
 
 
511
        if (drop->download)     {
 
512
                FS_FCloseFile( drop->download );
 
513
                drop->download = 0;
 
514
        }
 
515
 
 
516
        // call the prog function for removing a client
 
517
        // this will remove the body, among other things
 
518
        VM_Call( gvm, GAME_CLIENT_DISCONNECT, drop - svs.clients );
 
519
 
 
520
        // add the disconnect command
 
521
        SV_SendServerCommand( drop, "disconnect \"%s\"", reason);
 
522
 
 
523
        if ( drop->netchan.remoteAddress.type == NA_BOT ) {
 
524
                SV_BotFreeClient( drop - svs.clients );
 
525
        }
 
526
 
 
527
        // nuke user info
 
528
        SV_SetUserinfo( drop - svs.clients, "" );
 
529
 
 
530
        // if this was the last client on the server, send a heartbeat
 
531
        // to the master so it is known the server is empty
 
532
        // send a heartbeat now so the master will get up to date info
 
533
        // if there is already a slot for this ip, reuse it
 
534
        for (i=0 ; i < sv_maxclients->integer ; i++ ) {
 
535
                if ( svs.clients[i].state >= CS_CONNECTED ) {
 
536
                        break;
 
537
                }
 
538
        }
 
539
        if ( i == sv_maxclients->integer ) {
 
540
                SV_Heartbeat_f();
 
541
        }
 
542
}
 
543
 
 
544
/*
 
545
================
 
546
SV_SendClientGameState
 
547
 
 
548
Sends the first message from the server to a connected client.
 
549
This will be sent on the initial connection and upon each new map load.
 
550
 
 
551
It will be resent if the client acknowledges a later message but has
 
552
the wrong gamestate.
 
553
================
 
554
*/
 
555
void SV_SendClientGameState( client_t *client ) {
 
556
        int                     start;
 
557
        entityState_t   *base, nullstate;
 
558
        msg_t           msg;
 
559
        byte            msgBuffer[MAX_MSGLEN];
 
560
 
 
561
        Com_DPrintf ("SV_SendClientGameState() for %s\n", client->name);
 
562
        Com_DPrintf( "Going from CS_CONNECTED to CS_PRIMED for %s\n", client->name );
 
563
        client->state = CS_PRIMED;
 
564
        client->pureAuthentic = 0;
 
565
        client->gotCP = qfalse;
 
566
 
 
567
        // when we receive the first packet from the client, we will
 
568
        // notice that it is from a different serverid and that the
 
569
        // gamestate message was not just sent, forcing a retransmit
 
570
        client->gamestateMessageNum = client->netchan.outgoingSequence;
 
571
 
 
572
        MSG_Init( &msg, msgBuffer, sizeof( msgBuffer ) );
 
573
 
 
574
        // NOTE, MRE: all server->client messages now acknowledge
 
575
        // let the client know which reliable clientCommands we have received
 
576
        MSG_WriteLong( &msg, client->lastClientCommand );
 
577
 
 
578
        // send any server commands waiting to be sent first.
 
579
        // we have to do this cause we send the client->reliableSequence
 
580
        // with a gamestate and it sets the clc.serverCommandSequence at
 
581
        // the client side
 
582
        SV_UpdateServerCommandsToClient( client, &msg );
 
583
 
 
584
        // send the gamestate
 
585
        MSG_WriteByte( &msg, svc_gamestate );
 
586
        MSG_WriteLong( &msg, client->reliableSequence );
 
587
 
 
588
        // write the configstrings
 
589
        for ( start = 0 ; start < MAX_CONFIGSTRINGS ; start++ ) {
 
590
                if (sv.configstrings[start][0]) {
 
591
                        MSG_WriteByte( &msg, svc_configstring );
 
592
                        MSG_WriteShort( &msg, start );
 
593
                        MSG_WriteBigString( &msg, sv.configstrings[start] );
 
594
                }
 
595
        }
 
596
 
 
597
        // write the baselines
 
598
        Com_Memset( &nullstate, 0, sizeof( nullstate ) );
 
599
        for ( start = 0 ; start < MAX_GENTITIES; start++ ) {
 
600
                base = &sv.svEntities[start].baseline;
 
601
                if ( !base->number ) {
 
602
                        continue;
 
603
                }
 
604
                MSG_WriteByte( &msg, svc_baseline );
 
605
                MSG_WriteDeltaEntity( &msg, &nullstate, base, qtrue );
 
606
        }
 
607
 
 
608
        MSG_WriteByte( &msg, svc_EOF );
 
609
 
 
610
        MSG_WriteLong( &msg, client - svs.clients);
 
611
 
 
612
        // write the checksum feed
 
613
        MSG_WriteLong( &msg, sv.checksumFeed);
 
614
 
 
615
        // deliver this to the client
 
616
        SV_SendMessageToClient( &msg, client );
 
617
}
 
618
 
 
619
 
 
620
/*
 
621
==================
 
622
SV_ClientEnterWorld
 
623
==================
 
624
*/
 
625
void SV_ClientEnterWorld( client_t *client, usercmd_t *cmd ) {
 
626
        int             clientNum;
 
627
        sharedEntity_t *ent;
 
628
 
 
629
        Com_DPrintf( "Going from CS_PRIMED to CS_ACTIVE for %s\n", client->name );
 
630
        client->state = CS_ACTIVE;
 
631
 
 
632
        // resend all configstrings using the cs commands since these are
 
633
        // no longer sent when the client is CS_PRIMED
 
634
        SV_UpdateConfigstrings( client );
 
635
 
 
636
        // set up the entity for the client
 
637
        clientNum = client - svs.clients;
 
638
        ent = SV_GentityNum( clientNum );
 
639
        ent->s.number = clientNum;
 
640
        client->gentity = ent;
 
641
 
 
642
        client->deltaMessage = -1;
 
643
        client->nextSnapshotTime = svs.time;    // generate a snapshot immediately
 
644
        client->lastUsercmd = *cmd;
 
645
 
 
646
        // call the game begin function
 
647
        VM_Call( gvm, GAME_CLIENT_BEGIN, client - svs.clients );
 
648
}
 
649
 
 
650
/*
 
651
============================================================
 
652
 
 
653
CLIENT COMMAND EXECUTION
 
654
 
 
655
============================================================
 
656
*/
 
657
 
 
658
/*
 
659
==================
 
660
SV_CloseDownload
 
661
 
 
662
clear/free any download vars
 
663
==================
 
664
*/
 
665
static void SV_CloseDownload( client_t *cl ) {
 
666
        int i;
 
667
 
 
668
        // EOF
 
669
        if (cl->download) {
 
670
                FS_FCloseFile( cl->download );
 
671
        }
 
672
        cl->download = 0;
 
673
        *cl->downloadName = 0;
 
674
 
 
675
        // Free the temporary buffer space
 
676
        for (i = 0; i < MAX_DOWNLOAD_WINDOW; i++) {
 
677
                if (cl->downloadBlocks[i]) {
 
678
                        Z_Free( cl->downloadBlocks[i] );
 
679
                        cl->downloadBlocks[i] = NULL;
 
680
                }
 
681
        }
 
682
 
 
683
}
 
684
 
 
685
/*
 
686
==================
 
687
SV_StopDownload_f
 
688
 
 
689
Abort a download if in progress
 
690
==================
 
691
*/
 
692
void SV_StopDownload_f( client_t *cl ) {
 
693
        if (*cl->downloadName)
 
694
                Com_DPrintf( "clientDownload: %d : file \"%s\" aborted\n", cl - svs.clients, cl->downloadName );
 
695
 
 
696
        SV_CloseDownload( cl );
 
697
}
 
698
 
 
699
/*
 
700
==================
 
701
SV_DoneDownload_f
 
702
 
 
703
Downloads are finished
 
704
==================
 
705
*/
 
706
void SV_DoneDownload_f( client_t *cl ) {
 
707
        Com_DPrintf( "clientDownload: %s Done\n", cl->name);
 
708
        // resend the game state to update any clients that entered during the download
 
709
        SV_SendClientGameState(cl);
 
710
}
 
711
 
 
712
/*
 
713
==================
 
714
SV_NextDownload_f
 
715
 
 
716
The argument will be the last acknowledged block from the client, it should be
 
717
the same as cl->downloadClientBlock
 
718
==================
 
719
*/
 
720
void SV_NextDownload_f( client_t *cl )
 
721
{
 
722
        int block = atoi( Cmd_Argv(1) );
 
723
 
 
724
        if (block == cl->downloadClientBlock) {
 
725
                Com_DPrintf( "clientDownload: %d : client acknowledge of block %d\n", cl - svs.clients, block );
 
726
 
 
727
                // Find out if we are done.  A zero-length block indicates EOF
 
728
                if (cl->downloadBlockSize[cl->downloadClientBlock % MAX_DOWNLOAD_WINDOW] == 0) {
 
729
                        Com_Printf( "clientDownload: %d : file \"%s\" completed\n", cl - svs.clients, cl->downloadName );
 
730
                        SV_CloseDownload( cl );
 
731
                        return;
 
732
                }
 
733
 
 
734
                cl->downloadSendTime = svs.time;
 
735
                cl->downloadClientBlock++;
 
736
                return;
 
737
        }
 
738
        // We aren't getting an acknowledge for the correct block, drop the client
 
739
        // FIXME: this is bad... the client will never parse the disconnect message
 
740
        //                      because the cgame isn't loaded yet
 
741
        SV_DropClient( cl, "broken download" );
 
742
}
 
743
 
 
744
/*
 
745
==================
 
746
SV_BeginDownload_f
 
747
==================
 
748
*/
 
749
void SV_BeginDownload_f( client_t *cl ) {
 
750
 
 
751
        // Kill any existing download
 
752
        SV_CloseDownload( cl );
 
753
 
 
754
        // cl->downloadName is non-zero now, SV_WriteDownloadToClient will see this and open
 
755
        // the file itself
 
756
        Q_strncpyz( cl->downloadName, Cmd_Argv(1), sizeof(cl->downloadName) );
 
757
}
 
758
 
 
759
/*
 
760
==================
 
761
SV_WriteDownloadToClient
 
762
 
 
763
Check to see if the client wants a file, open it if needed and start pumping the client
 
764
Fill up msg with data 
 
765
==================
 
766
*/
 
767
void SV_WriteDownloadToClient( client_t *cl , msg_t *msg )
 
768
{
 
769
        int curindex;
 
770
        int rate;
 
771
        int blockspersnap;
 
772
        int idPack = 0, missionPack = 0, unreferenced = 1;
 
773
        char errorMessage[1024];
 
774
        char pakbuf[MAX_QPATH], *pakptr;
 
775
        int numRefPaks;
 
776
 
 
777
        if (!*cl->downloadName)
 
778
                return; // Nothing being downloaded
 
779
 
 
780
        if (!cl->download) {
 
781
                // Chop off filename extension.
 
782
                Com_sprintf(pakbuf, sizeof(pakbuf), "%s", cl->downloadName);
 
783
                pakptr = Q_strrchr(pakbuf, '.');
 
784
                
 
785
                if(pakptr)
 
786
                {
 
787
                        *pakptr = '\0';
 
788
 
 
789
                        // Check for pk3 filename extension
 
790
                        if(!Q_stricmp(pakptr + 1, "pk3"))
 
791
                        {
 
792
                                const char *referencedPaks = FS_ReferencedPakNames();
 
793
 
 
794
                                // Check whether the file appears in the list of referenced
 
795
                                // paks to prevent downloading of arbitrary files.
 
796
                                Cmd_TokenizeStringIgnoreQuotes(referencedPaks);
 
797
                                numRefPaks = Cmd_Argc();
 
798
 
 
799
                                for(curindex = 0; curindex < numRefPaks; curindex++)
 
800
                                {
 
801
                                        if(!FS_FilenameCompare(Cmd_Argv(curindex), pakbuf))
 
802
                                        {
 
803
                                                unreferenced = 0;
 
804
 
 
805
                                                // now that we know the file is referenced,
 
806
                                                // check whether it's legal to download it.
 
807
                                                missionPack = FS_idPak(pakbuf, "missionpack");
 
808
                                                idPack = missionPack || FS_idPak(pakbuf, BASEGAME);
 
809
 
 
810
                                                break;
 
811
                                        }
 
812
                                }
 
813
                        }
 
814
                }
 
815
 
 
816
                // We open the file here
 
817
                if ( !(sv_allowDownload->integer & DLF_ENABLE) ||
 
818
                        (sv_allowDownload->integer & DLF_NO_UDP) ||
 
819
                        idPack || unreferenced ||
 
820
                        ( cl->downloadSize = FS_SV_FOpenFileRead( cl->downloadName, &cl->download ) ) <= 0 ) {
 
821
                        // cannot auto-download file
 
822
                        if(unreferenced)
 
823
                        {
 
824
                                Com_Printf("clientDownload: %d : \"%s\" is not referenced and cannot be downloaded.\n", cl - svs.clients, cl->downloadName);
 
825
                                Com_sprintf(errorMessage, sizeof(errorMessage), "File \"%s\" is not referenced and cannot be downloaded.", cl->downloadName);
 
826
                        }
 
827
                        else if (idPack) {
 
828
                                Com_Printf("clientDownload: %d : \"%s\" cannot download id pk3 files\n", cl - svs.clients, cl->downloadName);
 
829
                                if (missionPack) {
 
830
                                        Com_sprintf(errorMessage, sizeof(errorMessage), "Cannot autodownload Team Arena file \"%s\"\n"
 
831
                                                                        "The Team Arena mission pack can be found in your local game store.", cl->downloadName);
 
832
                                }
 
833
                                else {
 
834
                                        Com_sprintf(errorMessage, sizeof(errorMessage), "Cannot autodownload id pk3 file \"%s\"", cl->downloadName);
 
835
                                }
 
836
                        }
 
837
                        else if ( !(sv_allowDownload->integer & DLF_ENABLE) ||
 
838
                                (sv_allowDownload->integer & DLF_NO_UDP) ) {
 
839
 
 
840
                                Com_Printf("clientDownload: %d : \"%s\" download disabled", cl - svs.clients, cl->downloadName);
 
841
                                if (sv_pure->integer) {
 
842
                                        Com_sprintf(errorMessage, sizeof(errorMessage), "Could not download \"%s\" because autodownloading is disabled on the server.\n\n"
 
843
                                                                                "You will need to get this file elsewhere before you "
 
844
                                                                                "can connect to this pure server.\n", cl->downloadName);
 
845
                                } else {
 
846
                                        Com_sprintf(errorMessage, sizeof(errorMessage), "Could not download \"%s\" because autodownloading is disabled on the server.\n\n"
 
847
                    "The server you are connecting to is not a pure server, "
 
848
                    "set autodownload to No in your settings and you might be "
 
849
                    "able to join the game anyway.\n", cl->downloadName);
 
850
                                }
 
851
                        } else {
 
852
        // NOTE TTimo this is NOT supposed to happen unless bug in our filesystem scheme?
 
853
        //   if the pk3 is referenced, it must have been found somewhere in the filesystem
 
854
                                Com_Printf("clientDownload: %d : \"%s\" file not found on server\n", cl - svs.clients, cl->downloadName);
 
855
                                Com_sprintf(errorMessage, sizeof(errorMessage), "File \"%s\" not found on server for autodownloading.\n", cl->downloadName);
 
856
                        }
 
857
                        MSG_WriteByte( msg, svc_download );
 
858
                        MSG_WriteShort( msg, 0 ); // client is expecting block zero
 
859
                        MSG_WriteLong( msg, -1 ); // illegal file size
 
860
                        MSG_WriteString( msg, errorMessage );
 
861
 
 
862
                        *cl->downloadName = 0;
 
863
                        return;
 
864
                }
 
865
 
 
866
                Com_Printf( "clientDownload: %d : beginning \"%s\"\n", cl - svs.clients, cl->downloadName );
 
867
                
 
868
                // Init
 
869
                cl->downloadCurrentBlock = cl->downloadClientBlock = cl->downloadXmitBlock = 0;
 
870
                cl->downloadCount = 0;
 
871
                cl->downloadEOF = qfalse;
 
872
        }
 
873
 
 
874
        // Perform any reads that we need to
 
875
        while (cl->downloadCurrentBlock - cl->downloadClientBlock < MAX_DOWNLOAD_WINDOW &&
 
876
                cl->downloadSize != cl->downloadCount) {
 
877
 
 
878
                curindex = (cl->downloadCurrentBlock % MAX_DOWNLOAD_WINDOW);
 
879
 
 
880
                if (!cl->downloadBlocks[curindex])
 
881
                        cl->downloadBlocks[curindex] = Z_Malloc( MAX_DOWNLOAD_BLKSIZE );
 
882
 
 
883
                cl->downloadBlockSize[curindex] = FS_Read( cl->downloadBlocks[curindex], MAX_DOWNLOAD_BLKSIZE, cl->download );
 
884
 
 
885
                if (cl->downloadBlockSize[curindex] < 0) {
 
886
                        // EOF right now
 
887
                        cl->downloadCount = cl->downloadSize;
 
888
                        break;
 
889
                }
 
890
 
 
891
                cl->downloadCount += cl->downloadBlockSize[curindex];
 
892
 
 
893
                // Load in next block
 
894
                cl->downloadCurrentBlock++;
 
895
        }
 
896
 
 
897
        // Check to see if we have eof condition and add the EOF block
 
898
        if (cl->downloadCount == cl->downloadSize &&
 
899
                !cl->downloadEOF &&
 
900
                cl->downloadCurrentBlock - cl->downloadClientBlock < MAX_DOWNLOAD_WINDOW) {
 
901
 
 
902
                cl->downloadBlockSize[cl->downloadCurrentBlock % MAX_DOWNLOAD_WINDOW] = 0;
 
903
                cl->downloadCurrentBlock++;
 
904
 
 
905
                cl->downloadEOF = qtrue;  // We have added the EOF block
 
906
        }
 
907
 
 
908
        // Loop up to window size times based on how many blocks we can fit in the
 
909
        // client snapMsec and rate
 
910
 
 
911
        // based on the rate, how many bytes can we fit in the snapMsec time of the client
 
912
        // normal rate / snapshotMsec calculation
 
913
        rate = cl->rate;
 
914
        if ( sv_maxRate->integer ) {
 
915
                if ( sv_maxRate->integer < 1000 ) {
 
916
                        Cvar_Set( "sv_MaxRate", "1000" );
 
917
                }
 
918
                if ( sv_maxRate->integer < rate ) {
 
919
                        rate = sv_maxRate->integer;
 
920
                }
 
921
        }
 
922
        if ( sv_minRate->integer ) {
 
923
                if ( sv_minRate->integer < 1000 )
 
924
                        Cvar_Set( "sv_minRate", "1000" );
 
925
                if ( sv_minRate->integer > rate )
 
926
                        rate = sv_minRate->integer;
 
927
        }
 
928
 
 
929
        if (!rate) {
 
930
                blockspersnap = 1;
 
931
        } else {
 
932
                blockspersnap = ( (rate * cl->snapshotMsec) / 1000 + MAX_DOWNLOAD_BLKSIZE ) /
 
933
                        MAX_DOWNLOAD_BLKSIZE;
 
934
        }
 
935
 
 
936
        if (blockspersnap < 0)
 
937
                blockspersnap = 1;
 
938
 
 
939
        while (blockspersnap--) {
 
940
 
 
941
                // Write out the next section of the file, if we have already reached our window,
 
942
                // automatically start retransmitting
 
943
 
 
944
                if (cl->downloadClientBlock == cl->downloadCurrentBlock)
 
945
                        return; // Nothing to transmit
 
946
 
 
947
                if (cl->downloadXmitBlock == cl->downloadCurrentBlock) {
 
948
                        // We have transmitted the complete window, should we start resending?
 
949
 
 
950
                        //FIXME:  This uses a hardcoded one second timeout for lost blocks
 
951
                        //the timeout should be based on client rate somehow
 
952
                        if (svs.time - cl->downloadSendTime > 1000)
 
953
                                cl->downloadXmitBlock = cl->downloadClientBlock;
 
954
                        else
 
955
                                return;
 
956
                }
 
957
 
 
958
                // Send current block
 
959
                curindex = (cl->downloadXmitBlock % MAX_DOWNLOAD_WINDOW);
 
960
 
 
961
                MSG_WriteByte( msg, svc_download );
 
962
                MSG_WriteShort( msg, cl->downloadXmitBlock );
 
963
 
 
964
                // block zero is special, contains file size
 
965
                if ( cl->downloadXmitBlock == 0 )
 
966
                        MSG_WriteLong( msg, cl->downloadSize );
 
967
 
 
968
                MSG_WriteShort( msg, cl->downloadBlockSize[curindex] );
 
969
 
 
970
                // Write the block
 
971
                if ( cl->downloadBlockSize[curindex] ) {
 
972
                        MSG_WriteData( msg, cl->downloadBlocks[curindex], cl->downloadBlockSize[curindex] );
 
973
                }
 
974
 
 
975
                Com_DPrintf( "clientDownload: %d : writing block %d\n", cl - svs.clients, cl->downloadXmitBlock );
 
976
 
 
977
                // Move on to the next block
 
978
                // It will get sent with next snap shot.  The rate will keep us in line.
 
979
                cl->downloadXmitBlock++;
 
980
 
 
981
                cl->downloadSendTime = svs.time;
 
982
        }
 
983
}
 
984
 
 
985
/*
 
986
=================
 
987
SV_Disconnect_f
 
988
 
 
989
The client is going to disconnect, so remove the connection immediately  FIXME: move to game?
 
990
=================
 
991
*/
 
992
static void SV_Disconnect_f( client_t *cl ) {
 
993
        SV_DropClient( cl, "disconnected" );
 
994
}
 
995
 
 
996
/*
 
997
=================
 
998
SV_VerifyPaks_f
 
999
 
 
1000
If we are pure, disconnect the client if they do no meet the following conditions:
 
1001
 
 
1002
1. the first two checksums match our view of cgame and ui
 
1003
2. there are no any additional checksums that we do not have
 
1004
 
 
1005
This routine would be a bit simpler with a goto but i abstained
 
1006
 
 
1007
=================
 
1008
*/
 
1009
static void SV_VerifyPaks_f( client_t *cl ) {
 
1010
        int nChkSum1, nChkSum2, nClientPaks, nServerPaks, i, j, nCurArg;
 
1011
        int nClientChkSum[1024];
 
1012
        int nServerChkSum[1024];
 
1013
        const char *pPaks, *pArg;
 
1014
        qboolean bGood = qtrue;
 
1015
 
 
1016
        // if we are pure, we "expect" the client to load certain things from 
 
1017
        // certain pk3 files, namely we want the client to have loaded the
 
1018
        // ui and cgame that we think should be loaded based on the pure setting
 
1019
        //
 
1020
        if ( sv_pure->integer != 0 ) {
 
1021
 
 
1022
                bGood = qtrue;
 
1023
                nChkSum1 = nChkSum2 = 0;
 
1024
                // we run the game, so determine which cgame and ui the client "should" be running
 
1025
                bGood = (FS_FileIsInPAK("vm/cgame.qvm", &nChkSum1) == 1);
 
1026
                if (bGood)
 
1027
                        bGood = (FS_FileIsInPAK("vm/ui.qvm", &nChkSum2) == 1);
 
1028
 
 
1029
                nClientPaks = Cmd_Argc();
 
1030
 
 
1031
                // start at arg 2 ( skip serverId cl_paks )
 
1032
                nCurArg = 1;
 
1033
 
 
1034
                pArg = Cmd_Argv(nCurArg++);
 
1035
                if(!pArg) {
 
1036
                        bGood = qfalse;
 
1037
                }
 
1038
                else
 
1039
                {
 
1040
                        // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=475
 
1041
                        // we may get incoming cp sequences from a previous checksumFeed, which we need to ignore
 
1042
                        // since serverId is a frame count, it always goes up
 
1043
                        if (atoi(pArg) < sv.checksumFeedServerId)
 
1044
                        {
 
1045
                                Com_DPrintf("ignoring outdated cp command from client %s\n", cl->name);
 
1046
                                return;
 
1047
                        }
 
1048
                }
 
1049
        
 
1050
                // we basically use this while loop to avoid using 'goto' :)
 
1051
                while (bGood) {
 
1052
 
 
1053
                        // must be at least 6: "cl_paks cgame ui @ firstref ... numChecksums"
 
1054
                        // numChecksums is encoded
 
1055
                        if (nClientPaks < 6) {
 
1056
                                bGood = qfalse;
 
1057
                                break;
 
1058
                        }
 
1059
                        // verify first to be the cgame checksum
 
1060
                        pArg = Cmd_Argv(nCurArg++);
 
1061
                        if (!pArg || *pArg == '@' || atoi(pArg) != nChkSum1 ) {
 
1062
                                bGood = qfalse;
 
1063
                                break;
 
1064
                        }
 
1065
                        // verify the second to be the ui checksum
 
1066
                        pArg = Cmd_Argv(nCurArg++);
 
1067
                        if (!pArg || *pArg == '@' || atoi(pArg) != nChkSum2 ) {
 
1068
                                bGood = qfalse;
 
1069
                                break;
 
1070
                        }
 
1071
                        // should be sitting at the delimeter now
 
1072
                        pArg = Cmd_Argv(nCurArg++);
 
1073
                        if (*pArg != '@') {
 
1074
                                bGood = qfalse;
 
1075
                                break;
 
1076
                        }
 
1077
                        // store checksums since tokenization is not re-entrant
 
1078
                        for (i = 0; nCurArg < nClientPaks; i++) {
 
1079
                                nClientChkSum[i] = atoi(Cmd_Argv(nCurArg++));
 
1080
                        }
 
1081
 
 
1082
                        // store number to compare against (minus one cause the last is the number of checksums)
 
1083
                        nClientPaks = i - 1;
 
1084
 
 
1085
                        // make sure none of the client check sums are the same
 
1086
                        // so the client can't send 5 the same checksums
 
1087
                        for (i = 0; i < nClientPaks; i++) {
 
1088
                                for (j = 0; j < nClientPaks; j++) {
 
1089
                                        if (i == j)
 
1090
                                                continue;
 
1091
                                        if (nClientChkSum[i] == nClientChkSum[j]) {
 
1092
                                                bGood = qfalse;
 
1093
                                                break;
 
1094
                                        }
 
1095
                                }
 
1096
                                if (bGood == qfalse)
 
1097
                                        break;
 
1098
                        }
 
1099
                        if (bGood == qfalse)
 
1100
                                break;
 
1101
 
 
1102
                        // get the pure checksums of the pk3 files loaded by the server
 
1103
                        pPaks = FS_LoadedPakPureChecksums();
 
1104
                        Cmd_TokenizeString( pPaks );
 
1105
                        nServerPaks = Cmd_Argc();
 
1106
                        if (nServerPaks > 1024)
 
1107
                                nServerPaks = 1024;
 
1108
 
 
1109
                        for (i = 0; i < nServerPaks; i++) {
 
1110
                                nServerChkSum[i] = atoi(Cmd_Argv(i));
 
1111
                        }
 
1112
 
 
1113
                        // check if the client has provided any pure checksums of pk3 files not loaded by the server
 
1114
                        for (i = 0; i < nClientPaks; i++) {
 
1115
                                for (j = 0; j < nServerPaks; j++) {
 
1116
                                        if (nClientChkSum[i] == nServerChkSum[j]) {
 
1117
                                                break;
 
1118
                                        }
 
1119
                                }
 
1120
                                if (j >= nServerPaks) {
 
1121
                                        bGood = qfalse;
 
1122
                                        break;
 
1123
                                }
 
1124
                        }
 
1125
                        if ( bGood == qfalse ) {
 
1126
                                break;
 
1127
                        }
 
1128
 
 
1129
                        // check if the number of checksums was correct
 
1130
                        nChkSum1 = sv.checksumFeed;
 
1131
                        for (i = 0; i < nClientPaks; i++) {
 
1132
                                nChkSum1 ^= nClientChkSum[i];
 
1133
                        }
 
1134
                        nChkSum1 ^= nClientPaks;
 
1135
                        if (nChkSum1 != nClientChkSum[nClientPaks]) {
 
1136
                                bGood = qfalse;
 
1137
                                break;
 
1138
                        }
 
1139
 
 
1140
                        // break out
 
1141
                        break;
 
1142
                }
 
1143
 
 
1144
                cl->gotCP = qtrue;
 
1145
 
 
1146
                if (bGood) {
 
1147
                        cl->pureAuthentic = 1;
 
1148
                } 
 
1149
                else {
 
1150
                        cl->pureAuthentic = 0;
 
1151
                        cl->nextSnapshotTime = -1;
 
1152
                        cl->state = CS_ACTIVE;
 
1153
                        SV_SendClientSnapshot( cl );
 
1154
                        SV_DropClient( cl, "Unpure client detected. Invalid .PK3 files referenced!" );
 
1155
                }
 
1156
        }
 
1157
}
 
1158
 
 
1159
/*
 
1160
=================
 
1161
SV_ResetPureClient_f
 
1162
=================
 
1163
*/
 
1164
static void SV_ResetPureClient_f( client_t *cl ) {
 
1165
        cl->pureAuthentic = 0;
 
1166
        cl->gotCP = qfalse;
 
1167
}
 
1168
 
 
1169
/*
 
1170
=================
 
1171
SV_UserinfoChanged
 
1172
 
 
1173
Pull specific info from a newly changed userinfo string
 
1174
into a more C friendly form.
 
1175
=================
 
1176
*/
 
1177
void SV_UserinfoChanged( client_t *cl ) {
 
1178
        char    *val;
 
1179
        int             i;
 
1180
 
 
1181
        // name for C code
 
1182
        Q_strncpyz( cl->name, Info_ValueForKey (cl->userinfo, "name"), sizeof(cl->name) );
 
1183
 
 
1184
        // rate command
 
1185
 
 
1186
        // if the client is on the same subnet as the server and we aren't running an
 
1187
        // internet public server, assume they don't need a rate choke
 
1188
        if ( Sys_IsLANAddress( cl->netchan.remoteAddress ) && com_dedicated->integer != 2 && sv_lanForceRate->integer == 1) {
 
1189
                cl->rate = 99999;       // lans should not rate limit
 
1190
        } else {
 
1191
                val = Info_ValueForKey (cl->userinfo, "rate");
 
1192
                if (strlen(val)) {
 
1193
                        i = atoi(val);
 
1194
                        cl->rate = i;
 
1195
                        if (cl->rate < 1000) {
 
1196
                                cl->rate = 1000;
 
1197
                        } else if (cl->rate > 90000) {
 
1198
                                cl->rate = 90000;
 
1199
                        }
 
1200
                } else {
 
1201
                        cl->rate = 3000;
 
1202
                }
 
1203
        }
 
1204
        val = Info_ValueForKey (cl->userinfo, "handicap");
 
1205
        if (strlen(val)) {
 
1206
                i = atoi(val);
 
1207
                if (i<=0 || i>100 || strlen(val) > 4) {
 
1208
                        Info_SetValueForKey( cl->userinfo, "handicap", "100" );
 
1209
                }
 
1210
        }
 
1211
 
 
1212
        // snaps command
 
1213
        val = Info_ValueForKey (cl->userinfo, "snaps");
 
1214
        if (strlen(val)) {
 
1215
                i = atoi(val);
 
1216
                if ( i < 1 ) {
 
1217
                        i = 1;
 
1218
                } else if ( i > sv_fps->integer ) {
 
1219
                        i = sv_fps->integer;
 
1220
                }
 
1221
                cl->snapshotMsec = 1000/i;
 
1222
        } else {
 
1223
                cl->snapshotMsec = 50;
 
1224
        }
 
1225
        
 
1226
        // TTimo
 
1227
        // maintain the IP information
 
1228
        // this is set in SV_DirectConnect (directly on the server, not transmitted), may be lost when client updates it's userinfo
 
1229
        // the banning code relies on this being consistently present
 
1230
        val = Info_ValueForKey (cl->userinfo, "ip");
 
1231
        if (!val[0])
 
1232
        {
 
1233
                //Com_DPrintf("Maintain IP in userinfo for '%s'\n", cl->name);
 
1234
                if ( !NET_IsLocalAddress(cl->netchan.remoteAddress) )
 
1235
                        Info_SetValueForKey( cl->userinfo, "ip", NET_AdrToString( cl->netchan.remoteAddress ) );
 
1236
                else
 
1237
                        // force the "ip" info key to "localhost" for local clients
 
1238
                        Info_SetValueForKey( cl->userinfo, "ip", "localhost" );
 
1239
        }
 
1240
}
 
1241
 
 
1242
 
 
1243
/*
 
1244
==================
 
1245
SV_UpdateUserinfo_f
 
1246
==================
 
1247
*/
 
1248
static void SV_UpdateUserinfo_f( client_t *cl ) {
 
1249
        Q_strncpyz( cl->userinfo, Cmd_Argv(1), sizeof(cl->userinfo) );
 
1250
 
 
1251
        SV_UserinfoChanged( cl );
 
1252
        // call prog code to allow overrides
 
1253
        VM_Call( gvm, GAME_CLIENT_USERINFO_CHANGED, cl - svs.clients );
 
1254
}
 
1255
 
 
1256
typedef struct {
 
1257
        char    *name;
 
1258
        void    (*func)( client_t *cl );
 
1259
} ucmd_t;
 
1260
 
 
1261
static ucmd_t ucmds[] = {
 
1262
        {"userinfo", SV_UpdateUserinfo_f},
 
1263
        {"disconnect", SV_Disconnect_f},
 
1264
        {"cp", SV_VerifyPaks_f},
 
1265
        {"vdr", SV_ResetPureClient_f},
 
1266
        {"download", SV_BeginDownload_f},
 
1267
        {"nextdl", SV_NextDownload_f},
 
1268
        {"stopdl", SV_StopDownload_f},
 
1269
        {"donedl", SV_DoneDownload_f},
 
1270
 
 
1271
        {NULL, NULL}
 
1272
};
 
1273
 
 
1274
/*
 
1275
==================
 
1276
SV_ExecuteClientCommand
 
1277
 
 
1278
Also called by bot code
 
1279
==================
 
1280
*/
 
1281
void SV_ExecuteClientCommand( client_t *cl, const char *s, qboolean clientOK ) {
 
1282
        ucmd_t  *u;
 
1283
        qboolean bProcessed = qfalse;
 
1284
        
 
1285
        Cmd_TokenizeString( s );
 
1286
 
 
1287
        // see if it is a server level command
 
1288
        for (u=ucmds ; u->name ; u++) {
 
1289
                if (!strcmp (Cmd_Argv(0), u->name) ) {
 
1290
                        u->func( cl );
 
1291
                        bProcessed = qtrue;
 
1292
                        break;
 
1293
                }
 
1294
        }
 
1295
 
 
1296
        if (clientOK) {
 
1297
                // pass unknown strings to the game
 
1298
                if (!u->name && sv.state == SS_GAME) {
 
1299
                        VM_Call( gvm, GAME_CLIENT_COMMAND, cl - svs.clients );
 
1300
                }
 
1301
        }
 
1302
        else if (!bProcessed)
 
1303
                Com_DPrintf( "client text ignored for %s: %s\n", cl->name, Cmd_Argv(0) );
 
1304
}
 
1305
 
 
1306
/*
 
1307
===============
 
1308
SV_ClientCommand
 
1309
===============
 
1310
*/
 
1311
static qboolean SV_ClientCommand( client_t *cl, msg_t *msg ) {
 
1312
        int             seq;
 
1313
        const char      *s;
 
1314
        qboolean clientOk = qtrue;
 
1315
 
 
1316
        seq = MSG_ReadLong( msg );
 
1317
        s = MSG_ReadString( msg );
 
1318
 
 
1319
        // see if we have already executed it
 
1320
        if ( cl->lastClientCommand >= seq ) {
 
1321
                return qtrue;
 
1322
        }
 
1323
 
 
1324
        Com_DPrintf( "clientCommand: %s : %i : %s\n", cl->name, seq, s );
 
1325
 
 
1326
        // drop the connection if we have somehow lost commands
 
1327
        if ( seq > cl->lastClientCommand + 1 ) {
 
1328
                Com_Printf( "Client %s lost %i clientCommands\n", cl->name, 
 
1329
                        seq - cl->lastClientCommand + 1 );
 
1330
                SV_DropClient( cl, "Lost reliable commands" );
 
1331
                return qfalse;
 
1332
        }
 
1333
 
 
1334
        // malicious users may try using too many string commands
 
1335
        // to lag other players.  If we decide that we want to stall
 
1336
        // the command, we will stop processing the rest of the packet,
 
1337
        // including the usercmd.  This causes flooders to lag themselves
 
1338
        // but not other people
 
1339
        // We don't do this when the client hasn't been active yet since its
 
1340
        // normal to spam a lot of commands when downloading
 
1341
        if ( !com_cl_running->integer && 
 
1342
                cl->state >= CS_ACTIVE &&
 
1343
                sv_floodProtect->integer && 
 
1344
                svs.time < cl->nextReliableTime ) {
 
1345
                // ignore any other text messages from this client but let them keep playing
 
1346
                // TTimo - moved the ignored verbose to the actual processing in SV_ExecuteClientCommand, only printing if the core doesn't intercept
 
1347
                clientOk = qfalse;
 
1348
        } 
 
1349
 
 
1350
        // don't allow another command for one second
 
1351
        cl->nextReliableTime = svs.time + 1000;
 
1352
 
 
1353
        SV_ExecuteClientCommand( cl, s, clientOk );
 
1354
 
 
1355
        cl->lastClientCommand = seq;
 
1356
        Com_sprintf(cl->lastClientCommandString, sizeof(cl->lastClientCommandString), "%s", s);
 
1357
 
 
1358
        return qtrue;           // continue procesing
 
1359
}
 
1360
 
 
1361
 
 
1362
//==================================================================================
 
1363
 
 
1364
 
 
1365
/*
 
1366
==================
 
1367
SV_ClientThink
 
1368
 
 
1369
Also called by bot code
 
1370
==================
 
1371
*/
 
1372
void SV_ClientThink (client_t *cl, usercmd_t *cmd) {
 
1373
        cl->lastUsercmd = *cmd;
 
1374
 
 
1375
        if ( cl->state != CS_ACTIVE ) {
 
1376
                return;         // may have been kicked during the last usercmd
 
1377
        }
 
1378
 
 
1379
        VM_Call( gvm, GAME_CLIENT_THINK, cl - svs.clients );
 
1380
}
 
1381
 
 
1382
/*
 
1383
==================
 
1384
SV_UserMove
 
1385
 
 
1386
The message usually contains all the movement commands 
 
1387
that were in the last three packets, so that the information
 
1388
in dropped packets can be recovered.
 
1389
 
 
1390
On very fast clients, there may be multiple usercmd packed into
 
1391
each of the backup packets.
 
1392
==================
 
1393
*/
 
1394
static void SV_UserMove( client_t *cl, msg_t *msg, qboolean delta ) {
 
1395
        int                     i, key;
 
1396
        int                     cmdCount;
 
1397
        usercmd_t       nullcmd;
 
1398
        usercmd_t       cmds[MAX_PACKET_USERCMDS];
 
1399
        usercmd_t       *cmd, *oldcmd;
 
1400
 
 
1401
        if ( delta ) {
 
1402
                cl->deltaMessage = cl->messageAcknowledge;
 
1403
        } else {
 
1404
                cl->deltaMessage = -1;
 
1405
        }
 
1406
 
 
1407
        cmdCount = MSG_ReadByte( msg );
 
1408
 
 
1409
        if ( cmdCount < 1 ) {
 
1410
                Com_Printf( "cmdCount < 1\n" );
 
1411
                return;
 
1412
        }
 
1413
 
 
1414
        if ( cmdCount > MAX_PACKET_USERCMDS ) {
 
1415
                Com_Printf( "cmdCount > MAX_PACKET_USERCMDS\n" );
 
1416
                return;
 
1417
        }
 
1418
 
 
1419
        // use the checksum feed in the key
 
1420
        key = sv.checksumFeed;
 
1421
        // also use the message acknowledge
 
1422
        key ^= cl->messageAcknowledge;
 
1423
        // also use the last acknowledged server command in the key
 
1424
        key ^= Com_HashKey(cl->reliableCommands[ cl->reliableAcknowledge & (MAX_RELIABLE_COMMANDS-1) ], 32);
 
1425
 
 
1426
        Com_Memset( &nullcmd, 0, sizeof(nullcmd) );
 
1427
        oldcmd = &nullcmd;
 
1428
        for ( i = 0 ; i < cmdCount ; i++ ) {
 
1429
                cmd = &cmds[i];
 
1430
                MSG_ReadDeltaUsercmdKey( msg, key, oldcmd, cmd );
 
1431
                oldcmd = cmd;
 
1432
        }
 
1433
 
 
1434
        // save time for ping calculation
 
1435
        cl->frames[ cl->messageAcknowledge & PACKET_MASK ].messageAcked = svs.time;
 
1436
 
 
1437
        // TTimo
 
1438
        // catch the no-cp-yet situation before SV_ClientEnterWorld
 
1439
        // if CS_ACTIVE, then it's time to trigger a new gamestate emission
 
1440
        // if not, then we are getting remaining parasite usermove commands, which we should ignore
 
1441
        if (sv_pure->integer != 0 && cl->pureAuthentic == 0 && !cl->gotCP) {
 
1442
                if (cl->state == CS_ACTIVE)
 
1443
                {
 
1444
                        // we didn't get a cp yet, don't assume anything and just send the gamestate all over again
 
1445
                        Com_DPrintf( "%s: didn't get cp command, resending gamestate\n", cl->name, cl->state );
 
1446
                        SV_SendClientGameState( cl );
 
1447
                }
 
1448
                return;
 
1449
        }                       
 
1450
        
 
1451
        // if this is the first usercmd we have received
 
1452
        // this gamestate, put the client into the world
 
1453
        if ( cl->state == CS_PRIMED ) {
 
1454
                SV_ClientEnterWorld( cl, &cmds[0] );
 
1455
                // the moves can be processed normaly
 
1456
        }
 
1457
        
 
1458
        // a bad cp command was sent, drop the client
 
1459
        if (sv_pure->integer != 0 && cl->pureAuthentic == 0) {          
 
1460
                SV_DropClient( cl, "Cannot validate pure client!");
 
1461
                return;
 
1462
        }
 
1463
 
 
1464
        if ( cl->state != CS_ACTIVE ) {
 
1465
                cl->deltaMessage = -1;
 
1466
                return;
 
1467
        }
 
1468
 
 
1469
        // usually, the first couple commands will be duplicates
 
1470
        // of ones we have previously received, but the servertimes
 
1471
        // in the commands will cause them to be immediately discarded
 
1472
        for ( i =  0 ; i < cmdCount ; i++ ) {
 
1473
                // if this is a cmd from before a map_restart ignore it
 
1474
                if ( cmds[i].serverTime > cmds[cmdCount-1].serverTime ) {
 
1475
                        continue;
 
1476
                }
 
1477
                // extremely lagged or cmd from before a map_restart
 
1478
                //if ( cmds[i].serverTime > svs.time + 3000 ) {
 
1479
                //      continue;
 
1480
                //}
 
1481
                // don't execute if this is an old cmd which is already executed
 
1482
                // these old cmds are included when cl_packetdup > 0
 
1483
                if ( cmds[i].serverTime <= cl->lastUsercmd.serverTime ) {
 
1484
                        continue;
 
1485
                }
 
1486
                SV_ClientThink (cl, &cmds[ i ]);
 
1487
        }
 
1488
}
 
1489
 
 
1490
 
 
1491
/*
 
1492
===========================================================================
 
1493
 
 
1494
USER CMD EXECUTION
 
1495
 
 
1496
===========================================================================
 
1497
*/
 
1498
 
 
1499
/*
 
1500
===================
 
1501
SV_ExecuteClientMessage
 
1502
 
 
1503
Parse a client packet
 
1504
===================
 
1505
*/
 
1506
void SV_ExecuteClientMessage( client_t *cl, msg_t *msg ) {
 
1507
        int                     c;
 
1508
        int                     serverId;
 
1509
 
 
1510
        MSG_Bitstream(msg);
 
1511
 
 
1512
        serverId = MSG_ReadLong( msg );
 
1513
        cl->messageAcknowledge = MSG_ReadLong( msg );
 
1514
 
 
1515
        if (cl->messageAcknowledge < 0) {
 
1516
                // usually only hackers create messages like this
 
1517
                // it is more annoying for them to let them hanging
 
1518
#ifndef NDEBUG
 
1519
                SV_DropClient( cl, "DEBUG: illegible client message" );
 
1520
#endif
 
1521
                return;
 
1522
        }
 
1523
 
 
1524
        cl->reliableAcknowledge = MSG_ReadLong( msg );
 
1525
 
 
1526
        // NOTE: when the client message is fux0red the acknowledgement numbers
 
1527
        // can be out of range, this could cause the server to send thousands of server
 
1528
        // commands which the server thinks are not yet acknowledged in SV_UpdateServerCommandsToClient
 
1529
        if (cl->reliableAcknowledge < cl->reliableSequence - MAX_RELIABLE_COMMANDS) {
 
1530
                // usually only hackers create messages like this
 
1531
                // it is more annoying for them to let them hanging
 
1532
#ifndef NDEBUG
 
1533
                SV_DropClient( cl, "DEBUG: illegible client message" );
 
1534
#endif
 
1535
                cl->reliableAcknowledge = cl->reliableSequence;
 
1536
                return;
 
1537
        }
 
1538
        // if this is a usercmd from a previous gamestate,
 
1539
        // ignore it or retransmit the current gamestate
 
1540
        // 
 
1541
        // if the client was downloading, let it stay at whatever serverId and
 
1542
        // gamestate it was at.  This allows it to keep downloading even when
 
1543
        // the gamestate changes.  After the download is finished, we'll
 
1544
        // notice and send it a new game state
 
1545
        //
 
1546
        // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=536
 
1547
        // don't drop as long as previous command was a nextdl, after a dl is done, downloadName is set back to ""
 
1548
        // but we still need to read the next message to move to next download or send gamestate
 
1549
        // I don't like this hack though, it must have been working fine at some point, suspecting the fix is somewhere else
 
1550
        if ( serverId != sv.serverId && !*cl->downloadName && !strstr(cl->lastClientCommandString, "nextdl") ) {
 
1551
                if ( serverId >= sv.restartedServerId && serverId < sv.serverId ) { // TTimo - use a comparison here to catch multiple map_restart
 
1552
                        // they just haven't caught the map_restart yet
 
1553
                        Com_DPrintf("%s : ignoring pre map_restart / outdated client message\n", cl->name);
 
1554
                        return;
 
1555
                }
 
1556
                // if we can tell that the client has dropped the last
 
1557
                // gamestate we sent them, resend it
 
1558
                if ( cl->messageAcknowledge > cl->gamestateMessageNum ) {
 
1559
                        Com_DPrintf( "%s : dropped gamestate, resending\n", cl->name );
 
1560
                        SV_SendClientGameState( cl );
 
1561
                }
 
1562
                return;
 
1563
        }
 
1564
 
 
1565
        // this client has acknowledged the new gamestate so it's
 
1566
        // safe to start sending it the real time again
 
1567
        if( cl->oldServerTime && serverId == sv.serverId ){
 
1568
                Com_DPrintf( "%s acknowledged gamestate\n", cl->name );
 
1569
                cl->oldServerTime = 0;
 
1570
        }
 
1571
 
 
1572
        // read optional clientCommand strings
 
1573
        do {
 
1574
                c = MSG_ReadByte( msg );
 
1575
                if ( c == clc_EOF ) {
 
1576
                        break;
 
1577
                }
 
1578
                if ( c != clc_clientCommand ) {
 
1579
                        break;
 
1580
                }
 
1581
                if ( !SV_ClientCommand( cl, msg ) ) {
 
1582
                        return; // we couldn't execute it because of the flood protection
 
1583
                }
 
1584
                if (cl->state == CS_ZOMBIE) {
 
1585
                        return; // disconnect command
 
1586
                }
 
1587
        } while ( 1 );
 
1588
 
 
1589
        // read the usercmd_t
 
1590
        if ( c == clc_move ) {
 
1591
                SV_UserMove( cl, msg, qtrue );
 
1592
        } else if ( c == clc_moveNoDelta ) {
 
1593
                SV_UserMove( cl, msg, qfalse );
 
1594
        } else if ( c != clc_EOF ) {
 
1595
                Com_Printf( "WARNING: bad command byte for client %i\n", cl - svs.clients );
 
1596
        }
 
1597
//      if ( msg->readcount != msg->cursize ) {
 
1598
//              Com_Printf( "WARNING: Junk at end of packet for client %i\n", cl - svs.clients );
 
1599
//      }
 
1600
}