1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3 
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8 
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12 
13 See the GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18 
19 
20 */
21 
22 #ifndef CLIENTONLY
23 #include "qwsvdef.h"
24 
25 #ifdef WEBSITE_LOGIN_SUPPORT
26 #undef WEBSITE_LOGIN_SUPPORT
27 #endif
28 #if defined(SERVERONLY) && defined(WWW_INTEGRATION)
29 #define WEBSITE_LOGIN_SUPPORT
30 #include "central.h"
31 #endif
32 
33 #define MAX_ACCOUNTS 1000
34 #define MAX_FAILURES 10
35 #define MAX_LOGINNAME (DIGEST_SIZE * 2 + 1)
36 #define ACC_FILE "accounts"
37 #define ACC_DIR "users"
38 
39 cvar_t sv_login = { "sv_login", "0" };	// if enabled, login required
40 #ifdef WEBSITE_LOGIN_SUPPORT
41 cvar_t sv_login_web = { "sv_login_web", "1" }; // 0=local files, 1=auth via website (bans can be in local files), 2=mandatory auth (must have account in local files)
42 #define LoginModeFileBased() ((int)sv_login_web.value == 0)
43 #define LoginModeOptionalWeb() ((int)sv_login_web.value == 1)
44 #define LoginModeMandatoryWeb() ((int)sv_login_web.value == 2)
45 #define LoginMustHaveLocalAccount() (LoginModeMandatoryWeb() || LoginModeFileBased())
46 #define WebLoginsEnabled() (!LoginModeFileBased())
47 #else
48 #define LoginModeFileBased() (1)
49 #define LoginModeOptionalWeb() (0)
50 #define LoginModeMandatoryWeb() (0)
51 #define LoginMustHaveLocalAccount() (1)
52 #define WebLoginsEnabled() (0)
53 #endif
54 
55 extern cvar_t sv_hashpasswords;
56 static void SV_SuccessfulLogin(client_t* cl);
57 static void SV_BlockedLogin(client_t* cl);
58 static void SV_ForceClientName(client_t* cl, const char* forced_name);
59 
60 typedef enum { a_free, a_ok, a_blocked } acc_state_t;
61 typedef enum { use_log, use_ip } quse_t;
62 
63 typedef struct
64 {
65 	char        login[MAX_LOGINNAME];
66 	char        pass[MAX_LOGINNAME];
67 	int         failures;
68 	int         inuse;
69 	ipfilter_t  address;
70 	acc_state_t state;
71 	quse_t      use;
72 } account_t;
73 
74 static account_t accounts[MAX_ACCOUNTS];
75 static int       num_accounts = 0;
76 
validAcc(char * acc)77 static qbool validAcc(char* acc)
78 {
79 	char* s = acc;
80 
81 	for (; *acc; acc++)
82 	{
83 		if (*acc < 'a' || *acc > 'z')
84 			if (*acc < 'A' || *acc > 'Z')
85 				if (*acc < '0' || *acc > '9')
86 					if (*acc != '.' && *acc != '_')
87 						return false;
88 	}
89 
90 	return acc - s <= MAX_LOGINNAME && acc - s >= 3;
91 }
92 
93 /*
94 =================
95 WriteAccounts
96 
97 Writes account list to disk
98 =================
99 */
100 
WriteAccounts(void)101 static void WriteAccounts(void)
102 {
103 	int c;
104 	FILE* f;
105 	account_t* acc;
106 
107 	//Sys_mkdir(ACC_DIR);
108 	if ((f = fopen(va("%s/" ACC_FILE, fs_gamedir), "wt")) == NULL)
109 	{
110 		Con_Printf("Warning: couldn't open for writing " ACC_FILE "\n");
111 		return;
112 	}
113 
114 	for (acc = accounts, c = 0; c < num_accounts; acc++)
115 	{
116 		if (acc->state == a_free)
117 			continue;
118 
119 		if (acc->use == use_log)
120 			fprintf(f, "%s %s %d %d\n", acc->login, acc->pass, acc->state, acc->failures);
121 		else
122 			fprintf(f, "%s %s %d\n", acc->login, acc->pass, acc->state);
123 
124 		c++;
125 	}
126 
127 	fclose(f);
128 
129 	// force cache rebuild.
130 	FS_FlushFSHash();
131 }
132 
133 /*
134 =================
135 SV_LoadAccounts
136 
137 loads account list from disk
138 =================
139 */
140 qbool StringToFilter(char* s, ipfilter_t* f);
SV_LoadAccounts(void)141 void SV_LoadAccounts(void)
142 {
143 	int i;
144 	FILE* f;
145 	account_t* acc = accounts;
146 	client_t* cl;
147 
148 	if ((f = fopen(va("%s/" ACC_FILE, fs_gamedir), "rt")) == NULL)
149 	{
150 		Con_DPrintf("couldn't open " ACC_FILE "\n");
151 		// logout
152 		num_accounts = 0;
153 		for (cl = svs.clients; cl - svs.clients < MAX_CLIENTS; cl++)
154 		{
155 			if (cl->logged > 0) {
156 				cl->logged = 0;
157 			}
158 			if (!cl->logged_in_via_web) {
159 				cl->login[0] = 0;
160 			}
161 		}
162 		return;
163 	}
164 
165 	while (!feof(f))
166 	{
167 		memset(acc, 0, sizeof(account_t));
168 		// Is realy safe to use 'fscanf(f, "%s", s)'? FIXME!
169 		if (fscanf(f, "%s", acc->login) != 1) {
170 			Con_Printf("Error reading account data\n");
171 			break;
172 		}
173 		if (StringToFilter(acc->login, &acc->address))
174 		{
175 			strlcpy(acc->pass, acc->login, MAX_LOGINNAME);
176 			acc->use = use_ip;
177 			if (fscanf(f, "%s %d\n", acc->pass, (int*)&acc->state) != 2) {
178 				Con_Printf("Error reading account data\n");
179 				break;
180 			}
181 		}
182 		else {
183 			if (fscanf(f, "%s %d %d\n", acc->pass, (int*)&acc->state, &acc->failures) != 3) {
184 				Con_Printf("Error reading account data\n");
185 				break;
186 			}
187 		}
188 
189 		if (acc->state != a_free) // lol?
190 			acc++;
191 	}
192 
193 	num_accounts = acc - accounts;
194 
195 	fclose(f);
196 
197 	// for every connected client check if their login is still valid
198 	for (cl = svs.clients; cl - svs.clients < MAX_CLIENTS; cl++)
199 	{
200 		if (cl->state < cs_connected)
201 			continue;
202 
203 		if (cl->logged <= 0)
204 			continue;
205 
206 		if (cl->logged_in_via_web)
207 			continue;
208 
209 		for (i = 0, acc = accounts; i < num_accounts; i++, acc++)
210 			if ((acc->use == use_log && !strncmp(acc->login, cl->login, CLIENT_LOGIN_LEN))
211 				|| (acc->use == use_ip && !strcmp(acc->login, va("%d.%d.%d.%d", cl->realip.ip[0], cl->realip.ip[1], cl->realip.ip[2], cl->realip.ip[3]))))
212 				break;
213 
214 		if (i < num_accounts && acc->state == a_ok)
215 		{
216 			// login again if possible
217 			if (!acc->inuse || acc->use == use_ip)
218 			{
219 				cl->logged = i + 1;
220 				if (acc->use == use_ip)
221 					strlcpy(cl->login, acc->pass, CLIENT_LOGIN_LEN);
222 
223 				acc->inuse++;
224 				continue;
225 			}
226 		}
227 		// login is not valid anymore, logout
228 		cl->logged = 0;
229 		cl->login[0] = 0;
230 	}
231 }
232 
233 /*
234 =================
235 SV_CreateAccount_f
236 
237 acc_create <login> [<password>]
238 if password is not given, login will be used for password
239 login/pass has to be max 16 chars and at least 3, only regular chars are acceptable
240 =================
241 */
SV_CreateAccount_f(void)242 void SV_CreateAccount_f(void)
243 {
244 	int i, spot, c;
245 	ipfilter_t adr;
246 	quse_t use;
247 
248 	if (Cmd_Argc() < 2)
249 	{
250 		Con_Printf("usage: acc_create <login> [<password>]\n       acc_create <address> <username>\nmaximum %d characters for login/pass\n", MAX_LOGINNAME - 1); //bliP: address typo
251 		return;
252 	}
253 
254 	if (num_accounts == MAX_ACCOUNTS)
255 	{
256 		Con_Printf("MAX_ACCOUNTS reached\n");
257 		return;
258 	}
259 
260 	if (StringToFilter(Cmd_Argv(1), &adr))
261 	{
262 		use = use_ip;
263 		if (Cmd_Argc() < 3)
264 		{
265 			Con_Printf("usage: acc_create <address> <username>\nmaximum %d characters for username\n", MAX_LOGINNAME - 1); //bliP; address typo
266 			return;
267 		}
268 	}
269 	else
270 	{
271 		use = use_log;
272 
273 		// validate user login/pass
274 		if (!validAcc(Cmd_Argv(1)))
275 		{
276 			Con_Printf("Invalid login!\n");
277 			return;
278 		}
279 
280 		if (Cmd_Argc() == 4 && !validAcc(Cmd_Argv(2)))
281 		{
282 			Con_Printf("Invalid pass!\n");
283 			return;
284 		}
285 	}
286 
287 	// find free spot, check if login exist;
288 	for (i = 0, c = 0, spot = -1; c < num_accounts; i++)
289 	{
290 		if (accounts[i].state == a_free)
291 		{
292 			if (spot == -1)	spot = i;
293 			continue;
294 		}
295 
296 		if (!strcasecmp(accounts[i].login, Cmd_Argv(1)) ||
297 			(use == use_ip && !strcasecmp(accounts[i].login, Cmd_Argv(2))))
298 			break;
299 
300 		c++;
301 	}
302 
303 	if (c < num_accounts)
304 	{
305 		Con_Printf("Login already in use\n");
306 		return;
307 	}
308 
309 	if (spot == -1)
310 		spot = i;
311 
312 	// create an account
313 	num_accounts++;
314 	strlcpy(accounts[spot].login, Cmd_Argv(1), MAX_LOGINNAME);
315 	if (Cmd_Argc() == 3)
316 		i = 2;
317 	else
318 		i = 1;
319 	strlcpy(accounts[spot].pass, (int)sv_hashpasswords.value && use == use_log ?
320 		SHA1(Cmd_Argv(i)) : Cmd_Argv(i), MAX_LOGINNAME);
321 
322 	accounts[spot].state = a_ok;
323 	accounts[spot].use = use;
324 
325 	Con_Printf("login %s created\n", Cmd_Argv(1));
326 	WriteAccounts();
327 }
328 
329 /*
330 =================
331 SV_RemoveAccount_f
332 
333 acc_remove <login>
334 removes the login
335 =================
336 */
SV_RemoveAccount_f(void)337 void SV_RemoveAccount_f(void)
338 {
339 	int i, c, j;
340 
341 	if (Cmd_Argc() < 2)
342 	{
343 		Con_Printf("usage: acc_remove <login>\n");
344 		return;
345 	}
346 
347 	for (i = 0, c = 0; c < num_accounts; i++)
348 	{
349 		if (accounts[i].state == a_free)
350 			continue;
351 
352 		if (!strcasecmp(accounts[i].login, Cmd_Argv(1))) {
353 			// Logout anyone using this login
354 			if ((int)sv_login.value == 1) {
355 				// Mandatory web logins, or using local files
356 				if (LoginMustHaveLocalAccount()) {
357 					for (j = 0; j < MAX_CLIENTS; ++j) {
358 						client_t* cl = &svs.clients[j];
359 
360 						if (!strcasecmp(cl->login, Cmd_Argv(1))) {
361 							SV_Logout(cl);
362 							SV_DropClient(cl);
363 						}
364 					}
365 				}
366 			}
367 
368 			// Update 'logged' pointers back to accounts list
369 			if (i != num_accounts - 1) {
370 				memcpy(&accounts[i], &accounts[num_accounts - 1], sizeof(accounts[i]));
371 				memset(&accounts[num_accounts - 1], 0, sizeof(accounts[num_accounts - 1]));
372 
373 				// Update references from the last account which we just moved
374 				for (j = 0; j < MAX_CLIENTS; ++j) {
375 					client_t* cl = &svs.clients[j];
376 					if (cl->logged == num_accounts) {
377 						cl->logged = i + 1;
378 					}
379 				}
380 			}
381 
382 			num_accounts--;
383 			Con_Printf("login %s removed\n", Cmd_Argv(1));
384 			WriteAccounts();
385 			return;
386 		}
387 
388 		c++;
389 	}
390 
391 	Con_Printf("account for %s not found\n", Cmd_Argv(1));
392 }
393 
394 /*
395 =================
396 SV_ListAccount_f
397 
398 shows the list of accounts
399 =================
400 */
SV_ListAccount_f(void)401 void SV_ListAccount_f(void)
402 {
403 	int i, c;
404 
405 	if (!num_accounts)
406 	{
407 		Con_Printf("account list is empty\n");
408 		return;
409 	}
410 
411 	Con_Printf("account list:\n");
412 
413 	for (i = 0, c = 0; c < num_accounts; i++)
414 	{
415 		if (accounts[i].state != a_free)
416 		{
417 			Con_Printf("%.16s %s\n", accounts[i].login, accounts[i].state == a_ok ? "" : "blocked");
418 			c++;
419 		}
420 	}
421 
422 	Con_Printf("%d login(s) found\n", num_accounts);
423 }
424 
425 /*
426 =================
427 SV_blockAccount
428 
429 blocks/unblocks an account
430 =================
431 */
SV_blockAccount(qbool block)432 void SV_blockAccount(qbool block)
433 {
434 	int i, j;
435 
436 	for (i = 0; i < num_accounts; i++)
437 	{
438 		if (accounts[i].state == a_free)
439 			continue;
440 
441 		if (!strcasecmp(accounts[i].login, Cmd_Argv(1)))
442 		{
443 			if (block) {
444 				accounts[i].state = a_blocked;
445 				Con_Printf("account %s blocked\n", Cmd_Argv(1));
446 
447 				for (j = 0; j < MAX_CLIENTS; ++j) {
448 					if (!strcasecmp(svs.clients[j].login, accounts[i].login)) {
449 						SV_DropClient(&svs.clients[j]);
450 						break;
451 					}
452 				}
453 				return;
454 			}
455 
456 			if (accounts[i].state != a_blocked) {
457 				Con_Printf("account %s not blocked\n", Cmd_Argv(1));
458 			}
459 			else {
460 				accounts[i].state = a_ok;
461 				accounts[i].failures = 0;
462 				Con_Printf("account %s unblocked\n", Cmd_Argv(1));
463 			}
464 
465 			return;
466 		}
467 	}
468 
469 	Con_Printf("account %s not found\n", Cmd_Argv(1));
470 }
471 
SV_UnblockAccount_f(void)472 void SV_UnblockAccount_f(void)
473 {
474 	if (Cmd_Argc() < 2)
475 	{
476 		Con_Printf("usage: acc_unblock <login>\n");
477 		return;
478 	}
479 
480 	SV_blockAccount(false);
481 	WriteAccounts();
482 }
483 
SV_BlockAccount_f(void)484 void SV_BlockAccount_f(void)
485 {
486 	if (Cmd_Argc() < 2)
487 	{
488 		Con_Printf("usage: acc_block <login>\n");
489 		return;
490 	}
491 
492 	SV_blockAccount(true);
493 	WriteAccounts();
494 }
495 
496 
497 /*
498 =================
499 checklogin
500 
501 returns positive value if login/pass are valid
502 values <= 0 indicates a failure
503 =================
504 */
checklogin(char * log1,char * pass,quse_t use)505 static int checklogin(char* log1, char* pass, quse_t use)
506 {
507 	int i, c;
508 
509 	for (i = 0, c = 0; c < num_accounts; i++)
510 	{
511 		if (accounts[i].state == a_free)
512 			continue;
513 
514 		if (use == accounts[i].use &&
515 			/*use == use_log && accounts[i].use == use_log && */
516 			!strcasecmp(log1, accounts[i].login))
517 		{
518 			if (accounts[i].state == a_blocked)
519 				return -2;
520 
521 			// Only do logins/failures if using file-based login list
522 			if (LoginMustHaveLocalAccount()) {
523 				if (accounts[i].inuse && accounts[i].use == use_log) {
524 					return -1;
525 				}
526 
527 				if (use == use_ip ||
528 					(!(int)sv_hashpasswords.value && !strcasecmp(pass,       accounts[i].pass)) ||
529 					( (int)sv_hashpasswords.value && !strcasecmp(SHA1(pass), accounts[i].pass)))
530 				{
531 					accounts[i].failures = 0;
532 					accounts[i].inuse++;
533 					return i + 1;
534 				}
535 
536 				if (++accounts[i].failures >= MAX_FAILURES) {
537 					Sys_Printf("account %s blocked after %d failed login attempts\n", accounts[i].login, accounts[i].failures);
538 					accounts[i].state = a_blocked;
539 				}
540 				WriteAccounts();
541 			}
542 			else {
543 				return i + 1;
544 			}
545 
546 			return 0;
547 		}
548 
549 		c++;
550 	}
551 
552 	return 0;
553 }
554 
Login_Init(void)555 void Login_Init(void)
556 {
557 	Cvar_Register(&sv_login);
558 #ifdef WEBSITE_LOGIN_SUPPORT
559 	Cvar_Register(&sv_login_web);
560 #endif
561 
562 	Cmd_AddCommand("acc_create", SV_CreateAccount_f);
563 	Cmd_AddCommand("acc_remove", SV_RemoveAccount_f);
564 	Cmd_AddCommand("acc_list", SV_ListAccount_f);
565 	Cmd_AddCommand("acc_unblock", SV_UnblockAccount_f);
566 	Cmd_AddCommand("acc_block", SV_BlockAccount_f);
567 
568 	// load account list
569 	//SV_LoadAccounts();
570 }
571 
572 /*
573 ===============
574 SV_Login
575 
576 called on connect after cmd new is issued
577 ===============
578 */
SV_Login(client_t * cl)579 qbool SV_Login(client_t* cl)
580 {
581 	extern cvar_t sv_registrationinfo;
582 	char* ip;
583 
584 	// is sv_login is disabled, login is not necessery
585 	if (!(int)sv_login.value) {
586 		// If using local files then logout
587 		if (!cl->logged_in_via_web) {
588 			SV_Logout(cl);
589 			cl->logged = -1;
590 		}
591 		return true;
592 	}
593 
594 	// if we're already logged return (probably map change)
595 	if (cl->logged > 0 || cl->logged_in_via_web) {
596 		return true;
597 	}
598 
599 	// sv_login == 1 -> spectators don't login
600 	if ((int)sv_login.value == 1 && cl->spectator)
601 	{
602 		SV_Logout(cl);
603 		cl->logged = -1;
604 		return true;
605 	}
606 
607 	// check for account for ip
608 	ip = va("%d.%d.%d.%d", cl->realip.ip[0], cl->realip.ip[1], cl->realip.ip[2], cl->realip.ip[3]);
609 	if ((cl->logged = checklogin(ip, ip, use_ip)) > 0)
610 	{
611 		strlcpy(cl->login, accounts[cl->logged - 1].pass, CLIENT_LOGIN_LEN);
612 		return true;
613 	}
614 
615 	// need to login before connecting
616 	cl->logged = 0;
617 	cl->login[0] = 0;
618 
619 	if (sv_registrationinfo.string[0])
620 		SV_ClientPrintf2(cl, PRINT_HIGH, "%s\n", sv_registrationinfo.string);
621 
622 	if (WebLoginsEnabled()) {
623 		char buffer[128];
624 		strlcpy(buffer, "//authprompt\n", sizeof(buffer));
625 
626 		ClientReliableWrite_Begin(cl, svc_stufftext, 2 + strlen(buffer));
627 		ClientReliableWrite_String(cl, buffer);
628 
629 		SV_ClientPrintf2(cl, PRINT_HIGH, "Enter username:\n");
630 	}
631 	else {
632 		SV_ClientPrintf2(cl, PRINT_HIGH, "Enter login & password:\n");
633 	}
634 
635 	return false;
636 }
637 
SV_Logout(client_t * cl)638 void SV_Logout(client_t* cl)
639 {
640 	if (cl->logged > 0 && cl->logged <= sizeof(accounts) / sizeof(accounts[0])) {
641 		accounts[cl->logged - 1].inuse--;
642 	}
643 
644 	Info_SetStar(&cl->_userinfo_ctx_, "*auth", "");
645 	Info_SetStar(&cl->_userinfo_ctx_, "*flag", "");
646 	ProcessUserInfoChange(cl, "*auth", cl->login);
647 	ProcessUserInfoChange(cl, "*flag", cl->login_flag);
648 
649 	memset(cl->login, 0, sizeof(cl->login));
650 	memset(cl->login_alias, 0, sizeof(cl->login_alias));
651 	memset(cl->login_flag, 0, sizeof(cl->login_flag));
652 	memset(cl->login_challenge, 0, sizeof(cl->login_challenge));
653 	memset(cl->login_confirmation, 0, sizeof(cl->login_confirmation));
654 	cl->logged = 0;
655 	cl->logged_in_via_web = false;
656 }
657 
658 #ifdef WEBSITE_LOGIN_SUPPORT
SV_ParseWebLogin(client_t * cl)659 void SV_ParseWebLogin(client_t* cl)
660 {
661 	char parameter[128] = { 0 };
662 	char* p;
663 
664 	strlcpy(parameter, Cmd_Argv(1), sizeof(parameter));
665 	for (p = parameter; *p > 32; ++p) {
666 	}
667 	*p = '\0';
668 
669 	if (!parameter[0]) {
670 		return;
671 	}
672 
673 	if (cl->login_challenge[0]) {
674 		// This is response to challenge, treat as password
675 		Central_VerifyChallengeResponse(cl, cl->login_challenge, parameter);
676 
677 		SV_ClientPrintf2(cl, PRINT_HIGH, "Challenge received, please wait...\n");
678 	}
679 	else if (curtime - cl->login_request_time < LOGIN_MIN_RETRY_TIME) {
680 		SV_ClientPrintf2(cl, PRINT_HIGH, "Please wait and try again\n");
681 	}
682 	else {
683 		// Treat as username
684 		Central_GenerateChallenge(cl, parameter, true);
685 
686 		SV_ClientPrintf2(cl, PRINT_HIGH, "Generating challenge, please wait...\n");
687 	}
688 }
689 #else
SV_ParseWebLogin(client_t * cl)690 void SV_ParseWebLogin(client_t* cl)
691 {
692 }
693 #endif
694 
SV_ParseLogin(client_t * cl)695 void SV_ParseLogin(client_t* cl)
696 {
697 	char *log1, *pass;
698 
699 	if (WebLoginsEnabled()) {
700 		SV_ParseWebLogin(cl);
701 		return;
702 	}
703 
704 	if (Cmd_Argc() > 2)
705 	{
706 		log1 = Cmd_Argv(1);
707 		pass = Cmd_Argv(2);
708 	}
709 	else
710 	{
711 		// bah usually whole text in 'say' is put into ""
712 		log1 = pass = Cmd_Argv(1);
713 		while (*pass && *pass != ' ')
714 			pass++;
715 
716 		if (*pass)
717 			*pass++ = 0;
718 
719 		while (*pass == ' ')
720 			pass++;
721 	}
722 
723 	// if login is parsed, we read just a password
724 	if (cl->login[0])
725 	{
726 		pass = log1;
727 		log1 = cl->login;
728 	}
729 	else
730 	{
731 		strlcpy(cl->login, log1, CLIENT_LOGIN_LEN);
732 	}
733 
734 	if (!*pass)
735 	{
736 		strlcpy(cl->login, log1, CLIENT_LOGIN_LEN);
737 		SV_ClientPrintf2(cl, PRINT_HIGH, "Enter password for %s:\n", cl->login);
738 
739 		return;
740 	}
741 
742 	cl->logged = checklogin(log1, pass, use_log);
743 
744 	switch (cl->logged)
745 	{
746 	case -2:
747 		SV_BlockedLogin(cl);
748 		break;
749 	case -1:
750 		SV_ClientPrintf2(cl, PRINT_HIGH, "Login in use!\ntry again:\n");
751 		cl->logged = 0;
752 		cl->login[0] = 0;
753 		break;
754 	case 0:
755 		SV_ClientPrintf2(cl, PRINT_HIGH, "Access denied\nPassword for %s:\n", cl->login);
756 		break;
757 	default:
758 		strlcpy(cl->login_alias, cl->login, sizeof(cl->login_alias));
759 		SV_SuccessfulLogin(cl);
760 		break;
761 	}
762 }
763 
SV_BlockedLogin(client_t * cl)764 static void SV_BlockedLogin(client_t* cl)
765 {
766 	SV_ClientPrintf2(cl, PRINT_HIGH, "Login blocked\n");
767 	SV_DropClient(cl);
768 }
769 
SV_SuccessfulLogin(client_t * cl)770 static void SV_SuccessfulLogin(client_t* cl)
771 {
772 	extern cvar_t sv_forcenick;
773 
774 	if (!cl->spectator || !GameStarted()) {
775 		SV_BroadcastPrintf(PRINT_HIGH, "%s logged in as %s\n", cl->name, cl->login);
776 	}
777 	if (cl->state < cs_spawned) {
778 		SV_ClientPrintf2(cl, PRINT_HIGH, "Welcome %s\n", cl->login);
779 	}
780 
781 	//VVD: forcenick ->
782 	if ((int)sv_forcenick.value)
783 	{
784 		const char* forced_name = cl->login_alias[0] ? cl->login_alias : cl->login;
785 
786 		if (forced_name[0]) {
787 			SV_ForceClientName(cl, forced_name);
788 		}
789 	}
790 	//<-
791 
792 	if (cl->state < cs_spawned) {
793 		MSG_WriteByte(&cl->netchan.message, svc_stufftext);
794 		MSG_WriteString(&cl->netchan.message, "cmd new\n");
795 	}
796 }
797 
SV_ForceClientName(client_t * cl,const char * forced_name)798 static void SV_ForceClientName(client_t* cl, const char* forced_name)
799 {
800 	char oldval[MAX_EXT_INFO_STRING];
801 	int i;
802 
803 	// If any other clients are using this name, kick them
804 	for (i = 0; i < MAX_CLIENTS; ++i) {
805 		client_t* other = &svs.clients[i];
806 
807 		if (!other->state)
808 			continue;
809 		if (other == cl) {
810 			continue;
811 		}
812 
813 		if (!Q_namecmp(other->name, forced_name)) {
814 			SV_KickClient(other, " (using authenticated user's name)");
815 		}
816 	}
817 
818 	// Set server-side name: allow colors/case changes
819 	if (!Q_namecmp(cl->name, forced_name)) {
820 		return;
821 	}
822 	strlcpy(oldval, cl->name, MAX_EXT_INFO_STRING);
823 	Info_Set(&cl->_userinfo_ctx_, "name", forced_name);
824 	ProcessUserInfoChange(cl, "name", oldval);
825 
826 	// Change name cvar in client
827 	MSG_WriteByte(&cl->netchan.message, svc_stufftext);
828 	MSG_WriteString(&cl->netchan.message, va("name %s\n", forced_name));
829 }
830 
SV_LoginCheckTimeOut(client_t * cl)831 void SV_LoginCheckTimeOut(client_t* cl)
832 {
833 	double connected = SV_ClientConnectedTime(cl);
834 
835 	if (connected && connected > 60)
836 	{
837 		Sys_Printf("Login time out for %s\n", cl->name);
838 
839 		SV_ClientPrintf2(cl, PRINT_HIGH, "Login timeout expired\n");
840 		SV_DropClient(cl);
841 	}
842 }
843 
SV_LoginWebCheck(client_t * cl)844 void SV_LoginWebCheck(client_t* cl)
845 {
846 	int status = checklogin(cl->login, cl->login, use_log);
847 
848 	if (status < 0) {
849 		// Server admin explicitly blocked this account
850 		SV_BlockedLogin(cl);
851 	}
852 	else if (status == 0 && LoginMustHaveLocalAccount()) {
853 		// Server admin needs to create accounts for people to use
854 		SV_BlockedLogin(cl);
855 	}
856 	else {
857 		// Continue logging in
858 		SV_SuccessfulLogin(cl);
859 	}
860 }
861 
SV_LoginWebFailed(client_t * cl)862 void SV_LoginWebFailed(client_t* cl)
863 {
864 	memset(cl->login_challenge, 0, sizeof(cl->login_challenge));
865 	cl->login_request_time = 0;
866 
867 	SV_ClientPrintf2(cl, PRINT_HIGH, "Challenge response failed.\n");
868 	if (cl->state < cs_spawned) {
869 		SV_BlockedLogin(cl);
870 	}
871 }
872 
SV_LoginRequired(client_t * cl)873 qbool SV_LoginRequired(client_t* cl)
874 {
875 	int login = (int)sv_login.value;
876 
877 	if (login == 2 || (login == 1 && !cl->spectator)) {
878 		if (WebLoginsEnabled()) {
879 			return !cl->logged_in_via_web;
880 		}
881 		else {
882 			return !cl->logged;
883 		}
884 	}
885 	return false;
886 }
887 
SV_LoginBlockJoinRequest(client_t * cl)888 qbool SV_LoginBlockJoinRequest(client_t* cl)
889 {
890 	if (WebLoginsEnabled()) {
891 		if (!cl->logged_in_via_web && (int)sv_login.value) {
892 			SV_ClientPrintf(cl, PRINT_HIGH, "This server requires users to login.  Please authenticate first (/cmd login <username>).\n");
893 			return true;
894 		}
895 	}
896 	else if (cl->logged <= 0 && (int)sv_login.value) {
897 		SV_ClientPrintf(cl, PRINT_HIGH, "This server requires users to login.  Please disconnect and reconnect as a player.\n");
898 		return true;
899 	}
900 
901 	// Allow
902 	return false;
903 }
904 
905 #endif // !CLIENTONLY
906