tbauth.php3 25.9 KB
Newer Older
1
2
<?php
#
Leigh B. Stoller's avatar
Leigh B. Stoller committed
3
# EMULAB-COPYRIGHT
4
# Copyright (c) 2000-2005 University of Utah and the Flux Group.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
5
6
7
# All rights reserved.
#
#
8
# Login support: Beware empty spaces (cookies)!
9
10
#

11
12
13
14
15
# These global are to prevent repeated calls to the DB. 
#
$CHECKLOGIN_STATUS		= -1;
$CHECKLOGIN_UID			= 0;
$CHECKLOGIN_NOLOGINS		= -1;
16
$CHECKLOGIN_WIKINAME            = "";
17
$CHECKLOGIN_IDLETIME            = 0;
18
19
20
21
22
23
24
25
26

#
# New Mapping. 
#
define("CHECKLOGIN_NOSTATUS",		-1);
define("CHECKLOGIN_NOTLOGGEDIN",	0);
define("CHECKLOGIN_LOGGEDIN",		1);
define("CHECKLOGIN_TIMEDOUT",		2);
define("CHECKLOGIN_MAYBEVALID",		4);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
27
28
define("CHECKLOGIN_STATUSMASK",		0x0000ff);
define("CHECKLOGIN_MODMASK",		0xffff00);
29
30
31
32
33
#
# These are modifiers of the above status fields. They are stored
# as a bit field in the top part. This is intended to localize as
# many queries related to login as possible. 
#
34
35
36
37
38
39
40
41
42
43
44
45
46
47
define("CHECKLOGIN_NEWUSER",		0x000100);
define("CHECKLOGIN_UNVERIFIED",		0x000200);
define("CHECKLOGIN_UNAPPROVED",		0x000400);
define("CHECKLOGIN_ACTIVE",		0x000800);
define("CHECKLOGIN_USERSTATUS",		0x000f00);
define("CHECKLOGIN_PSWDEXPIRED",	0x001000);
define("CHECKLOGIN_FROZEN",		0x002000);
define("CHECKLOGIN_ISADMIN",		0x004000);
define("CHECKLOGIN_TRUSTED",		0x008000);
define("CHECKLOGIN_CVSWEB",		0x010000);
define("CHECKLOGIN_ADMINOFF",		0x020000);
define("CHECKLOGIN_WEBONLY",		0x040000);
define("CHECKLOGIN_PLABUSER",		0x080000);
define("CHECKLOGIN_STUDLY",		0x100000);
48
define("CHECKLOGIN_WIKIONLY",		0x200000);
49
define("CHECKLOGIN_OPSGUY",		0x400000);	# member of emulab-ops
50

51
52
53
#
# Constants for tracking possible login attacks.
#
54
55
define("DOLOGIN_MAXUSERATTEMPTS",	15);
define("DOLOGIN_MAXIPATTEMPTS",		25);
56

57
58
59
60
61
62
63
64
65
#
# Generate a hash value suitable for authorization. We use the results of
# microtime, combined with a random number.
# 
function GENHASH() {
    $fp = fopen("/dev/urandom", "r");
    if (! $fp) {
        TBERROR("Error opening /dev/urandom", 1);
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
66
    $random_bytes = fread($fp, 128);
67
68
69
70
71
72
    fclose($fp);

    $hash  = mhash (MHASH_MD5, bin2hex($retval) . " " . microtime());
    return bin2hex($hash);
}

73
74
75
#
# Return the value of the currently logged in uid, or null if not
# logged in. Basically, check the browser to see if its sending a UID
76
# and HASH back, and then check the DB to see if the user is really
77
78
79
80
81
82
# logged in.
# 
function GETLOGIN() {
    if (($uid = GETUID()) == FALSE)
	    return FALSE;

83
84
    $check = CHECKLOGIN($uid);

85
    if ($check & (CHECKLOGIN_LOGGEDIN|CHECKLOGIN_MAYBEVALID))
86
87
88
89
90
91
92
93
94
95
96
	    return $uid;

    return FALSE;
}

#
# Return the value of the UID cookie. This does not check to see if
# this person is currently logged in. We just want to know what the
# browser thinks, if anything.
# 
function GETUID() {
Leigh B. Stoller's avatar
Leigh B. Stoller committed
97
    global $TBNAMECOOKIE;
Chad Barb's avatar
 
Chad Barb committed
98
    global $nocookieuid;
99

Leigh B. Stoller's avatar
Leigh B. Stoller committed
100
    $curname = FALSE;
Chad Barb's avatar
 
Chad Barb committed
101

Leigh B. Stoller's avatar
Leigh B. Stoller committed
102
103
104
    # XXX - nocookieuid is sent by netbuild applet in URL.
    if (isset($_GET['nocookieuid'])) {
	$curname = $_GET['nocookieuid'];
Chad Barb's avatar
 
Chad Barb committed
105
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
106
107
108
109
110
    elseif (isset($_COOKIE[$TBNAMECOOKIE])) {
	$curname = $_COOKIE[$TBNAMECOOKIE];
    }
    else
	return FALSE;
Chad Barb's avatar
 
Chad Barb committed
111

Leigh B. Stoller's avatar
Leigh B. Stoller committed
112
    # Verify valid string (no special chars like single/double quotes!).
Leigh B. Stoller's avatar
Leigh B. Stoller committed
113
114
115
116
    # We do not use the standard check function here, since we want to
    # avoid a DB access on each page until its required. Thats okay since
    # since we just need to ensure that we feed to the DB query is safe.
    if (! preg_match("/^[-\w]+$/", $curname)) {
Chad Barb's avatar
 
Chad Barb committed
117
118
	return FALSE;
    }
119
120
121
    return $curname;
}

122
#
123
# Verify a login by sucking a UIDs current hash value out of the database.
124
125
126
# If the login has expired, or of the hashkey in the database does not
# match what came back in the cookie, then the UID is no longer logged in.
#
127
# Returns a combination of the CHECKLOGIN values above.
128
#
129
function CHECKLOGIN($uid) {
130
    global $TBAUTHCOOKIE, $TBLOGINCOOKIE, $HTTP_COOKIE_VARS, $TBAUTHTIMEOUT;
131
    global $CHECKLOGIN_STATUS, $CHECKLOGIN_UID, $CHECKLOGIN_NODETYPES;
132
    global $CHECKLOGIN_WIKINAME, $TBOPSPID;
133
    global $EXPOSEARCHIVE;
Chad Barb's avatar
 
Chad Barb committed
134
    global $nocookieauth;
135
136
137
138
139
140
141
    #
    # If we already figured this out, do not duplicate work!
    #
    if ($CHECKLOGIN_STATUS != CHECKLOGIN_NOSTATUS) {
	return $CHECKLOGIN_STATUS;
    }
    $CHECKLOGIN_UID = $uid;
142

Chad Barb's avatar
 
Chad Barb committed
143
144
145
146
147
148
149
150
    # for java applet, we can send the key in the $auth variable,
    # rather than passing it is a cookie.
    if (isset($nocookieauth)) {
	$curhash = $nocookieauth;
    } else {
	$curhash = $HTTP_COOKIE_VARS[$TBAUTHCOOKIE];
    }
    
151
152
153
154
    #
    # Note that we get multiple rows back because of the group_membership
    # join. No big deal.
    # 
155
    $query_result =
156
	DBQueryFatal("select NOW()>=u.pswd_expires,l.hashkey,l.timeout, ".
157
		     "       status,admin,cvsweb,g.trust,adminoff,webonly, " .
158
		     "       user_interface,n.type,u.stud,u.wikiname, ".
159
		     "       u.wikionly,g.pid " .
160
161
		     " from users as u ".
		     "left join login as l on l.uid=u.uid ".
162
		     "left join group_membership as g on g.uid=u.uid ".
163
		     "left join nodetypeXpid_permissions as n on g.pid=n.pid " .
164
		     "where u.uid='$uid'");
165

166
    # No such user.
167
168
169
170
171
172
173
    if (! mysql_num_rows($query_result)) { 
	$CHECKLOGIN_STATUS = CHECKLOGIN_NOTLOGGEDIN;
	return $CHECKLOGIN_STATUS;
    }
    
    #
    # Scan the rows. All the info is duplicate, except for the trust
174
    # values and the pid. pid is a hack.
175
176
    #
    $trusted = 0;
177
    $opsguy  = 0;
178
    
179
180
181
182
183
184
185
186
187
188
189
190
    while ($row = mysql_fetch_array($query_result)) {
	$expired = $row[0];
	$hashkey = $row[1];
	$timeout = $row[2];
	$status  = $row[3];
	$admin   = $row[4];
	$cvsweb  = $row[5];

	if (! strcmp($row[6], "project_root") ||
	    ! strcmp($row[6], "group_root")) {
	    $trusted = 1;
	}
191
192
	$adminoff = $row[7];
	$webonly  = $row[8];
193
	$interface= $row[9];
194
195

	$type     = $row[10];
196
	$stud     = $row[11];
197
	$wikiname = $row[12];
198
	$wikionly = $row[13];
199

200
201
202
203
204
	# Check for an ops guy.
	$pid = $row[14];
	if ($pid == $TBOPSPID) {
	    $opsguy = 1;
	}
205
	$CHECKLOGIN_NODETYPES[$type] = 1;
206
    }
207
208
209
210

    #
    # If user exists, but login has no entry, quit now.
    #
211
212
213
214
    if (!$hashkey) {
	$CHECKLOGIN_STATUS = CHECKLOGIN_NOTLOGGEDIN;
	return $CHECKLOGIN_STATUS;
    }
215

216
    #
217
    # Check for frozen account. Might do something interesting later.
218
    #
219
    if (! strcmp($status, TBDB_USERSTATUS_FROZEN)) {
220
	DBQueryFatal("DELETE FROM login WHERE uid='$uid'");
221
222
	$CHECKLOGIN_STATUS = CHECKLOGIN_NOTLOGGEDIN;
	return $CHECKLOGIN_STATUS;
223
224
    }

225
    #
226
227
    # Check for expired login. It does not matter if the cookie matches,
    # kill the entry anyway so the user is officially logged out.
228
    #
229
    if (time() > $timeout) {
230
231
232
233
	DBQueryFatal("DELETE FROM login WHERE uid='$uid'");
	$CHECKLOGIN_STATUS = CHECKLOGIN_TIMEDOUT;
	return $CHECKLOGIN_STATUS;
    }
234
    $CHECKLOGIN_IDLETIME = time() - ($timeout - $TBAUTHTIMEOUT);
235
236

    #
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
    # We know the login has not expired. The problem is that we might not
    # have received a cookie since that is set to transfer only when using
    # https. However, we do not want the menu to be flipping back and forth
    # each time the user uses http (say, for documentation), and so the lack
    # of a cookie does not provide enough info to determine if the user is
    # logged in or not from the current browser. Also, we want to allow for
    # a user to switch browsers, and not get confused by getting a uid but
    # no valid cookie from the new browser. In that case the user should just
    # be able to login from the new browser; gets a standard not-logged-in
    # front page. In order to accomplish this, we need another cookie that is
    # set on login, cleared on logout.
    #
    if (isset($curhash)) {
	#
	# Got a cookie (https).
252
	#
253
254
255
256
257
258
259
260
261
	if ($curhash != $hashkey) {
	    #
	    # User is not logged in from this browser. Must be stale.
	    # 
	    $CHECKLOGIN_STATUS = CHECKLOGIN_NOTLOGGEDIN;
	    return $CHECKLOGIN_STATUS;
	}
	else {
            #
262
263
	    # User is logged in.
	    #
264
265
	    $CHECKLOGIN_STATUS = CHECKLOGIN_LOGGEDIN;
	}
266
267
    }
    else {
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
	#
	# No cookie. Might be because its http, so there is no way to tell
	# if user is not logged in from the current browser without more
	# information. We use another cookie for this, which is a crc of
	# of the real hash, and simply tells us what menu to draw, but does
	# not impart any privs!
	#
	$hashhash = $HTTP_COOKIE_VARS[$TBLOGINCOOKIE];
	
	if (isset($hashhash) &&
	    $hashhash == bin2hex(mhash(MHASH_CRC32, $hashkey))) {
            #
            # The login is probably valid, but we have no proof yet. 
            #
	    $CHECKLOGIN_STATUS = CHECKLOGIN_MAYBEVALID;
	}
	else {
	    #
	    # No hash of the hash, so assume no real cookie either. 
	    # 
	    $CHECKLOGIN_STATUS = CHECKLOGIN_NOTLOGGEDIN;
	    return $CHECKLOGIN_STATUS;
	}
291
292
293
    }

    #
294
    # Now add in the modifiers.
295
    #
296
297
298
299
    if ($expired)
	$CHECKLOGIN_STATUS |= CHECKLOGIN_PSWDEXPIRED;
    if ($admin)
	$CHECKLOGIN_STATUS |= CHECKLOGIN_ISADMIN;
300
301
    if ($adminoff)
	$CHECKLOGIN_STATUS |= CHECKLOGIN_ADMINOFF;
302
303
    if ($webonly)
	$CHECKLOGIN_STATUS |= CHECKLOGIN_WEBONLY;
304
305
    if ($wikionly)
	$CHECKLOGIN_STATUS |= CHECKLOGIN_WIKIONLY;
306
307
    if ($trusted)
	$CHECKLOGIN_STATUS |= CHECKLOGIN_TRUSTED;
308
309
    if ($stud)
	$CHECKLOGIN_STATUS |= CHECKLOGIN_STUDLY;
310
311
    if ($cvsweb)
	$CHECKLOGIN_STATUS |= CHECKLOGIN_CVSWEB;
312
    if ($interface == TBDB_USER_INTERFACE_PLAB)
313
	$CHECKLOGIN_STATUS |= CHECKLOGIN_PLABUSER;
314
315
316
317
318
319
320
321
    if (strcmp($status, TBDB_USERSTATUS_NEWUSER) == 0)
	$CHECKLOGIN_STATUS |= CHECKLOGIN_NEWUSER;
    if (strcmp($status, TBDB_USERSTATUS_UNAPPROVED) == 0)
	$CHECKLOGIN_STATUS |= CHECKLOGIN_UNAPPROVED;
    if (strcmp($status, TBDB_USERSTATUS_UNVERIFIED) == 0)
	$CHECKLOGIN_STATUS |= CHECKLOGIN_UNVERIFIED;
    if (strcmp($status, TBDB_USERSTATUS_ACTIVE) == 0)
	$CHECKLOGIN_STATUS |= CHECKLOGIN_ACTIVE;
322
323
    if (isset($wikiname) && $wikiname != "")
	$CHECKLOGIN_WIKINAME = $wikiname;
324
325
    if ($opsguy)
	$CHECKLOGIN_STATUS |= CHECKLOGIN_OPSGUY;
326

327
328
329
    #
    # Set the magic enviroment variable, if appropriate, for the sake of
    # any processes we might spawn. We prepend an HTTP_ on the front of
330
    # the variable name, so that it will get through suexec.
331
332
333
334
    #
    if ($admin && !$adminoff) {
    	putenv("HTTP_WITH_TB_ADMIN_PRIVS=1");
    }
335
336
337
338
    # XXX Temporary.
    if ($stud) {
	$EXPOSEARCHIVE = 1;
    }
339
    return $CHECKLOGIN_STATUS;
340
341
342
343
}

#
# This one checks for login, but then dies with an appropriate error
344
345
# message. The modifier allows you to turn off checks for specified
# conditions. 
346
#
347
function LOGGEDINORDIE($uid, $modifier = 0, $login_url = NULL) {
348
    global $TBBASE, $BASEPATH, $HTTP_COOKIE_VARS, $TBNAMECOOKIE;
349
    global $TBAUTHTIMEOUT;
350

Leigh B. Stoller's avatar
Leigh B. Stoller committed
351
    # If our login is not valid, then the uid is already set to "",
352
353
354
355
    # so refresh it to the cookie value. Then we can pass the right
    # uid to hcecklogin, so we can give the right error message.
    if ($uid=="") { $uid=$HTTP_COOKIE_VARS[$TBNAMECOOKIE]; }

356
357
358
359
360
361
362
363
364
    #
    # Allow the caller to specify a different URL to direct the user
    # to
    #
    if (!$login_url) {
	$login_url = "$TBBASE/login.php3?refer=1";
    }

    $link = "\n<a href=\"$login_url\">Please ".
365
	"log in again.</a>\n";
366

367
    if ($uid == FALSE)
368
        USERERROR("You do not appear to be logged in! $link", 1);
369
    
370
    $status = CHECKLOGIN($uid);
371
372
373

    switch ($status & CHECKLOGIN_STATUSMASK) {
    case CHECKLOGIN_NOTLOGGEDIN:
374
        USERERROR("You do not appear to be logged in! $link", 1);
375
        break;
376
    case CHECKLOGIN_TIMEDOUT:
377
        USERERROR("Your login has timed out! $link", 1);
378
        break;
379
380
    case CHECKLOGIN_MAYBEVALID:
        USERERROR("Your login cannot be verified. Are cookies turned on? ".
381
		  "Are you using https? Are you logged in using another ".
382
		  "browser or another machine? $link", 1);
383
        break;
384
    case CHECKLOGIN_LOGGEDIN:
385
386
387
388
389
390
391
392
393
        #
	# Update the time in the database.
        # Basically, each time the user does something, we bump the
	# logout further into the future. This avoids timing them
	# out just when they are doing useful work.
        #
	$timeout = time() + $TBAUTHTIMEOUT;

	DBQueryFatal("UPDATE login set timeout='$timeout' where uid='$uid'");
394
395
396
	break;
    default:
	TBERROR("LOGGEDINORDIE failed mysteriously", 1);
397
    }
398
399
400
401
402
403
404

    $status = $status & ~$modifier;

    #
    # Check other conditions.
    #
    if ($status & CHECKLOGIN_PSWDEXPIRED)
405
406
        USERERROR("Your password has expired. ".
		  "<a href=moduserinfo.php3>Please change it now!</a>", 1);
407
408
409
410
411
412
    if ($status & CHECKLOGIN_FROZEN)
        USERERROR("Your account has been frozen!", 1);
    if ($status & (CHECKLOGIN_UNVERIFIED|CHECKLOGIN_NEWUSER))
        USERERROR("You have not verified your account yet!", 1);
    if ($status & CHECKLOGIN_UNAPPROVED)
        USERERROR("Your account has not been approved yet!", 1);
413
    if (($status & CHECKLOGIN_WEBONLY) && ! ISADMIN($uid))
414
        USERERROR("Your account does not permit you to access this page!", 1);
415
416
    if (($status & CHECKLOGIN_WIKIONLY) && ! ISADMIN($uid))
        USERERROR("Your account does not permit you to access this page!", 1);
417
418
419
420
421
422
423
424
425
426

    #
    # Lastly, check for nologins here. This heads off a bunch of other
    # problems and checks we would need.
    #
    if (NOLOGINS() && !ISADMIN($uid))
        USERERROR("Sorry. The Web Interface is ".
		  "<a href=nologins.php3>Temporarily Unavailable!</a>", 1);

    return $uid;
427
428
429
}

#
430
431
432
433
# Is this user an admin type, and is his admin bit turned on.
# Its actually incorrect to look at the $uid. Its the currently logged
# in user that has to be admin. So ignore the uid and make sure
# there is a login status.
434
#
435
function ISADMIN($uid = 1) {
436
437
    global $CHECKLOGIN_STATUS;
    
438
439
    if ($CHECKLOGIN_STATUS == CHECKLOGIN_NOSTATUS) {
	$uid=GETUID();
440
	TBERROR("ISADMIN: $uid is not logged in!", 1);
441
    }
442

443
    return (($CHECKLOGIN_STATUS &
444
	     (CHECKLOGIN_LOGGEDIN|CHECKLOGIN_ISADMIN|CHECKLOGIN_ADMINOFF)) ==
445
	    (CHECKLOGIN_LOGGEDIN|CHECKLOGIN_ISADMIN));
446
447
}

448
449
450
451
452
453
454
455
456
457
458
459
460
function STUDLY() {
    global $CHECKLOGIN_STATUS;
    
    if ($CHECKLOGIN_STATUS == CHECKLOGIN_NOSTATUS) {
	$uid=GETUID();
	TBERROR("STUDLY: $uid is not logged in!", 1);
    }

    return (($CHECKLOGIN_STATUS &
	     (CHECKLOGIN_LOGGEDIN|CHECKLOGIN_STUDLY)) ==
	    (CHECKLOGIN_LOGGEDIN|CHECKLOGIN_STUDLY));
}

461
462
463
464
465
466
467
468
469
470
471
472
473
function OPSGUY() {
    global $CHECKLOGIN_STATUS;
    
    if ($CHECKLOGIN_STATUS == CHECKLOGIN_NOSTATUS) {
	$uid=GETUID();
	TBERROR("OPSGUY: $uid is not logged in!", 1);
    }

    return (($CHECKLOGIN_STATUS &
	     (CHECKLOGIN_LOGGEDIN|CHECKLOGIN_OPSGUY)) ==
	    (CHECKLOGIN_LOGGEDIN|CHECKLOGIN_OPSGUY));
}

474
475
476
477
478
479
480
481
482
483
484
485
486
function WIKIONLY() {
    global $CHECKLOGIN_STATUS;
    
    if ($CHECKLOGIN_STATUS == CHECKLOGIN_NOSTATUS) {
	$uid=GETUID();
	TBERROR("WIKIONLY: $uid is not logged in!", 1);
    }

    return (($CHECKLOGIN_STATUS &
	     (CHECKLOGIN_LOGGEDIN|CHECKLOGIN_WIKIONLY)) ==
	    (CHECKLOGIN_LOGGEDIN|CHECKLOGIN_WIKIONLY));
}

487
# Is this user a real administrator (ignore onoff bit).
488
function ISADMINISTRATOR() {
489
490
491
492
493
494
495
496
497
498
    global $CHECKLOGIN_STATUS;
    
    if ($CHECKLOGIN_STATUS == CHECKLOGIN_NOSTATUS)
	TBERROR("ISADMIN: $uid is not logged in!", 1);

    return (($CHECKLOGIN_STATUS &
	     (CHECKLOGIN_LOGGEDIN|CHECKLOGIN_ISADMIN)) ==
	    (CHECKLOGIN_LOGGEDIN|CHECKLOGIN_ISADMIN));
}

499
# Is this user a planetlab user? Returns 1 if they are, 0 if not.
500
501
502
function ISPLABUSER() {
    global $CHECKLOGIN_STATUS;

503
504
505
506
507
508
509
510
511
    if ($CHECKLOGIN_STATUS == CHECKLOGIN_NOSTATUS) {
	#
	# For users who are not logged in, we need to check the database
	#
	$uid = GETUID();
	if (!$uid) {
	    return 0;
	}
	$query_result =
512
	    DBQueryFatal("SELECT user_interface FROM users WHERE uid='$uid'");
513
514
515
	if (!mysql_num_rows($query_result)) {
	    return 0;
	}
516

517
518
	$row = mysql_fetch_row($query_result);
	if ($row[0]) {
519
	    return ($row[0] == TBDB_USER_INTERFACE_PLAB);
520
521
522
	} else {
	    return 0;
	}
523
    } else {
524
	#
525
	# For logged-in users, we recorded it in the the login status
526
527
	#
	return (($CHECKLOGIN_STATUS &
528
529
		 (CHECKLOGIN_PLABUSER)) ==
		(CHECKLOGIN_PLABUSER));
530
531
532
    }
}

533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
#
# Check to see if a user is allowed, in some project, to use the given node
# type. Returns 1 if allowed, 0 if not.
#
# NOTE: This is NOT intended as a real permissions check. It is intended only
# for display purposes (ie. deciding whether or not to give the user a link to
# the plab_ez page.) It does not require the user to be actually logged in, so
# that it still works for pages fetched through http. Thus, it may be possible
# for a clever user to fake it out.
#
function NODETYPE_ALLOWED($type) {
    global $CHECKLOGIN_NODETYPES;
    $uid = GETUID();
    if (!$uid) {
	return 0;
    }
    if ($CHECKLOGIN_NODETYPES[$type]) {
	return 1;
    } else {
	return 0;
    }
}

556
557
558
#
# Attempt a login.
# 
559
function DOLOGIN($token, $password, $adminmode = 0) {
560
561
    global $TBAUTHCOOKIE, $TBAUTHDOMAIN, $TBAUTHTIMEOUT;
    global $TBNAMECOOKIE, $TBLOGINCOOKIE, $TBSECURECOOKIES;
562
    global $TBMAIL_OPS, $TBMAIL_AUDIT, $TBMAIL_WWW;
563
    global $WIKISUPPORT, $WIKICOOKIENAME;
564
    global $BUGDBSUPPORT, $BUGDBCOOKIENAME;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
565
566
    
    # Caller makes these checks too.
567
568
    if ((!TBvalid_uid($token) && !TBvalid_email($token)) ||
	!isset($password) || $password == "") {
569
570
	return -1;
    }
571
    $now = time();
572

573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
    #
    # Check for a frozen IP address; too many failures.
    #
    unset($iprow);
    unset($IP);
    if (isset($_SERVER['REMOTE_ADDR'])) {
	$IP = $_SERVER['REMOTE_ADDR'];
	
	$ip_result =
	    DBQueryFatal("select * from login_failures ".
			 "where IP='$IP'");

	if ($iprow = mysql_fetch_array($ip_result)) {
	    $ipfrozen = $iprow['frozen'];

	    if ($ipfrozen) {
		DBQueryFatal("update login_failures set ".
			     "       failcount=failcount+1, ".
			     "       failstamp='$now' ".
			     "where IP='$IP'");
		return -1;
	    }
	}
    }

    $user_result =
599
	DBQueryFatal("select uid,usr_pswd,admin,weblogin_frozen,".
600
		     "       weblogin_failcount,weblogin_failstamp, ".
601
		     "       usr_email,usr_name,unix_uid ".
602
603
604
605
		     "from users where ".
		     (TBvalid_email($token) ?
		      "usr_email='$token'" :
		      "uid='$token'"));
606
607
608
609

    #
    # Check password in the database against provided. 
    #
610
611
    do {
      if ($row = mysql_fetch_array($user_result)) {
612
	$uid         = $row['uid'];
613
614
615
616
617
618
619
        $db_encoding = $row['usr_pswd'];
	$isadmin     = $row['admin'];
	$frozen      = $row['weblogin_frozen'];
	$failcount   = $row['weblogin_failcount'];
	$failstamp   = $row['weblogin_failstamp'];
	$usr_email   = $row['usr_email'];
	$usr_name    = $row['usr_name'];
620
	$uid_idx     = $row['unix_uid'];
621

622
623
	# Check for frozen accounts. We do not update the IP record when
	# an account is frozen.
624
	if ($frozen) {
625
	    DBQueryFatal("update users set ".
626
627
628
629
630
631
			 "       weblogin_failcount=weblogin_failcount+1, ".
			 "       weblogin_failstamp='$now' ".
			 "where uid='$uid'");
	    return -1;
	}
	
632
        $encoding = crypt("$password", $db_encoding);
633
        if (strcmp($encoding, $db_encoding)) {
634
	    #
635
	    # Bump count and check for too many consecutive failures.
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
	    #
	    $failcount++;
	    if ($failcount > DOLOGIN_MAXUSERATTEMPTS) {
		$frozen = 1;
	    
		TBMAIL("$usr_name '$uid' <$usr_email>",
		   "Web Login Freeze: '$uid'",
		   "Your login has been frozen because there were too many\n".
		   "login failures from " . $_SERVER['REMOTE_ADDR'] . ".\n\n".
		   "Testbed Operations has been notified.\n",
		   "From: $TBMAIL_OPS\n".
		   "Cc: $TBMAIL_OPS\n".
		   "Bcc: $TBMAIL_AUDIT\n".
		   "Errors-To: $TBMAIL_WWW");
	    }

	    DBQueryFatal("update users set weblogin_frozen='$frozen', ".
			 "       weblogin_failcount='$failcount', ".
654
			 "       weblogin_failstamp='$now' ".
655
656
			 "where uid='$uid'");
            break;
657
658
659
660
661
662
        }
        #
        # Pass! Insert a record in the login table for this uid with
        # the new hash value. If the user is already logged in, thats
        # okay; just update it in place with a new hash and timeout. 
        #
663
	$timeout = $now + $TBAUTHTIMEOUT;
664
	$hashkey = GENHASH();
665
666
667
        $query_result =
	    DBQueryFatal("SELECT timeout FROM login WHERE uid='$uid'");
	
668
	if (mysql_num_rows($query_result)) {
669
670
671
	    DBQueryFatal("UPDATE login set ".
			 "timeout='$timeout', hashkey='$hashkey' ".
			 "WHERE uid='$uid'");
672
673
	}
	else {
674
675
	    DBQueryFatal("INSERT into login (uid, hashkey, timeout) ".
			 "VALUES ('$uid', '$hashkey', '$timeout')");
676
677
	}

678
	#
679
	# Usage stats. 
680
	#
681
682
683
	DBQueryFatal("update user_stats set ".
		     " weblogin_count=weblogin_count+1, ".
		     " weblogin_last=now() ".
684
		     "where uid_idx='$uid_idx'");
685

686
	#
687
688
689
690
	# Issue the cookie requests so that subsequent pages come back
	# with the hash value and auth usr embedded.

	#
691
692
693
694
695
	# For the hashkey, we use a zero timeout so that the cookie is
	# a session cookie; killed when the browser is exited. Hopefully this
	# keeps the key from going to disk on the client machine. The cookie
	# lives as long as the browser is active, but we age the cookie here
	# at the server so it will become invalid at some point.
696
	#
697
	setcookie($TBAUTHCOOKIE, $hashkey, 0, "/",
698
                  $TBAUTHDOMAIN, $TBSECURECOOKIES);
699

700
701
702
703
704
705
706
707
708
709
	#
	# Another cookie, to help in menu generation. See above in
	# checklogin. This cookie is a simple hash of the real hash,
	# intended to indicate if the current browser holds a real hash.
	# All this does is change the menu options presented, imparting
	# no actual privs. 
	#
	$crc = bin2hex(mhash(MHASH_CRC32, $hashkey));
	setcookie($TBLOGINCOOKIE, $crc, 0, "/", $TBAUTHDOMAIN, 0);

710
711
712
713
714
715
716
717
	#
	# We give this a really long timeout. We want to remember who the
	# the user was each time they load a page, and more importantly,
	# each time they come back to the main page so we can fill in their
	# user name. NOTE: This cookie is integral to authorization, since
	# we do not pass around the UID anymore, but look for it in the
	# cookie.
	# 
718
	$timeout = $now + (60 * 60 * 24 * 32);
719
720
	setcookie($TBNAMECOOKIE, $uid, $timeout, "/", $TBAUTHDOMAIN, 0);

721
722
723
724
725
726
727
728
729
730
731
	#
	# Clear the existing Wiki cookie so that there is not an old one
	# for a different user, sitting in the brower. 
	# 
	if ($WIKISUPPORT) {
	    $flushtime = time() - 1000000;
	    
	    setcookie($WIKICOOKIENAME, "", $flushtime, "/",
		      $TBAUTHDOMAIN, $TBSECURECOOKIES);
	}
	
732
733
734
735
736
737
738
739
740
741
	#
	# Ditto for bugdb
	# 
	if ($BUGDBSUPPORT) {
	    $flushtime = time() - 1000000;
	    
	    setcookie($BUGDBCOOKIENAME, "", $flushtime, "/",
		      $TBAUTHDOMAIN, $TBSECURECOOKIES);
	}
	
742
	#
743
744
745
746
747
	# Set adminoff on new logins, unless user requested to be
	# logged in as admin (and is an admin of course!). This is
	# primarily to bypass the nologins directive which makes it
	# impossible for an admin to login when the web interface is
	# turned off. 
748
	#
749
750
751
752
	$adminoff = 1;
	if ($adminmode && $isadmin) {
	    $adminoff = 0;
	}
753
754
755
	DBQueryFatal("update users set adminoff=$adminoff, ".
		     "       weblogin_failcount=0,weblogin_failstamp=0 ".
		     "where uid='$uid'");
756

757
758
759
760
	# Clear IP record since we have a sucessful login from the IP.
	if (isset($IP)) {
	    DBQueryFatal("delete from login_failures where IP='$IP'");
	}
761
	return 0;
762
763
      }
    } while (0);
764
765
766
    #
    # No such user
    #
767
    if (!isset($IP)) {
768
	return -1;
769
770
771
    }
	
    $ipfrozen = 0;
772
773
774
775
    if (isset($iprow)) {
	$ipfailcount = $iprow['failcount'];

        #
776
        # Bump count.
777
778
779
780
        #
	$ipfailcount++;
    }
    else {
781
782
783
	#
	# First failure.
	# 
784
785
	$ipfailcount = 1;
    }
786
787
788
789

    #
    # Check for too many consecutive failures.
    #
790
791
792
793
794
795
    if ($ipfailcount > DOLOGIN_MAXIPATTEMPTS) {
	$ipfrozen = 1;
	    
	TBMAIL($TBMAIL_OPS,
	       "Web Login Freeze: '$IP'",
	       "Logins has been frozen because there were too many login\n".
796
	       "failures from $IP. Last attempted uid was '$token'.\n\n",
797
798
799
800
801
802
803
804
	       "From: $TBMAIL_OPS\n".
	       "Bcc: $TBMAIL_AUDIT\n".
	       "Errors-To: $TBMAIL_WWW");
    }
    DBQueryFatal("replace into login_failures set ".
		 "       IP='$IP', ".
		 "       frozen='$ipfrozen', ".
		 "       failcount='$ipfailcount', ".
805
		 "       failstamp='$now'");
806
807
808
    return -1;
}

809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826

#
# Verify a password
# 
function VERIFYPASSWD($uid, $password) {
    if (! isset($password) ||
	strcmp($password, "") == 0) {
	return -1;
    }

    $query_result =
	DBQueryFatal("SELECT usr_pswd FROM users WHERE uid='$uid'");

    #
    # Check password in the database against provided. 
    #
    if ($row = mysql_fetch_row($query_result)) {
        $db_encoding = $row[0];
827
        $encoding = crypt("$password", $db_encoding);
828
829
830
831
832
833
834
835
836
	
        if (strcmp($encoding, $db_encoding)) {
            return -1;
	}
	return 0;
    }
    return -1;
}

837
#
838
# Log out a UID.
839
840
#
function DOLOGOUT($uid) {
841
    global $CHECKLOGIN_STATUS, $TBAUTHCOOKIE, $TBLOGINCOOKIE, $TBAUTHDOMAIN;
842
    global $WIKISUPPORT, $WIKICOOKIENAME;
843
    global $BUGDBSUPPORT, $BUGDBCOOKIENAME;
844

845
846
847
848
849
    # Pedantic check.
    if (!TBvalid_uid($uid)) {
	return 1;
    }

850
851
852
853
    $CHECKLOGIN_STATUS = CHECKLOGIN_NOTLOGGEDIN;

    $query_result =
	DBQueryFatal("SELECT hashkey timeout FROM login WHERE uid='$uid'");
854
855
856

    # Not logged in.
    if (($row = mysql_fetch_array($query_result)) == 0) {
857
	return 1;
858
859
860
    }

    $hashkey = $row[hashkey];
861
    $timeout = time() - 1000000;
862

863
    DBQueryFatal("DELETE FROM login WHERE uid='$uid'");
864
865

    #
866
    # Issue a cookie request to delete the cookies. 
867
    #
868
    setcookie($TBAUTHCOOKIE, "", $timeout, "/", $TBAUTHDOMAIN, 0);
869
    setcookie($TBLOGINCOOKIE, "", $timeout, "/", $TBAUTHDOMAIN, 0);
870
871
872
    if ($WIKISUPPORT) {
	setcookie($WIKICOOKIENAME, "", $timeout, "/", $TBAUTHDOMAIN, 0);
    }
873
874
875
    if ($BUGDBSUPPORT) {
	setcookie($BUGDBCOOKIENAME, "", $timeout, "/", $TBAUTHDOMAIN, 0);
    }
876
877
878
879

    return 0;
}

880
#
Chad Barb's avatar
 
Chad Barb committed
881
# Simple "nologins" support.
882
883
#
function NOLOGINS() {
884
    global $CHECKLOGIN_NOLOGINS;
885

Chad Barb's avatar
 
Chad Barb committed
886
887
    if ($CHECKLOGIN_NOLOGINS == -1) {
	$CHECKLOGIN_NOLOGINS = TBGetSiteVar("web/nologins");
888
    }
Chad Barb's avatar
 
Chad Barb committed
889
	
890
    return $CHECKLOGIN_NOLOGINS;
891
892
}

893
894
895
function LASTWEBLOGIN($uid) {
    global $TBDBNAME;

896
    $query_result =
897
898
899
        DBQueryFatal("select weblogin_last from users as u ".
		     "left join user_stats as s on s.uid_idx=u.unix_uid ".
		     "where u.uid='$uid'");
900
901
902
    
    if (mysql_num_rows($query_result)) {
	$lastrow      = mysql_fetch_array($query_result);
903
	return $lastrow[weblogin_last];
904
905
906
907
    }
    return 0;
}

908
909
function HASREALACCOUNT($uid) {
    $query_result =
910
911
	DBQueryFatal("select status,webonly,wikionly from users ".
		     "where uid='$uid'");
912
913
914
915
916

    if (!mysql_num_rows($query_result)) {
	return 0;
    }
    $row = mysql_fetch_array($query_result);
917
918
919
    $status   = $row[0];
    $webonly  = $row[1];
    $wikionly = $row[2];
920

921
    if ($webonly || $wikionly ||
922
923
	(strcmp($status, TBDB_USERSTATUS_ACTIVE) &&
	 strcmp($status, TBDB_USERSTATUS_FROZEN))) {
924
925
926
927
928
	return 0;
    }
    return 1;
}

929
930
931
932
#
# Beware empty spaces (cookies)!
# 
?>