tbauth.php3 15.6 KB
Newer Older
1
2
<?php
#
Leigh B. Stoller's avatar
Leigh B. Stoller committed
3
# EMULAB-COPYRIGHT
4
# Copyright (c) 2000-2003 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
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# These global are to prevent repeated calls to the DB. 
#
$CHECKLOGIN_STATUS		= -1;
$CHECKLOGIN_UID			= 0;
$CHECKLOGIN_NOLOGINS		= -1;

#
# New Mapping. 
#
define("CHECKLOGIN_NOSTATUS",		-1);
define("CHECKLOGIN_NOTLOGGEDIN",	0);
define("CHECKLOGIN_LOGGEDIN",		1);
define("CHECKLOGIN_TIMEDOUT",		2);
define("CHECKLOGIN_MAYBEVALID",		4);
define("CHECKLOGIN_STATUSMASK",		0x000ff);
define("CHECKLOGIN_MODMASK",		0xfff00);
#
# 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. 
#
define("CHECKLOGIN_NEWUSER",		0x00100);
define("CHECKLOGIN_UNVERIFIED",		0x00200);
define("CHECKLOGIN_UNAPPROVED",		0x00400);
define("CHECKLOGIN_ACTIVE",		0x00800);
define("CHECKLOGIN_USERSTATUS",		0x00f00);
define("CHECKLOGIN_PSWDEXPIRED",	0x01000);
define("CHECKLOGIN_FROZEN",		0x02000);
define("CHECKLOGIN_ISADMIN",		0x04000);
define("CHECKLOGIN_TRUSTED",		0x08000);
define("CHECKLOGIN_CVSWEB",		0x10000);
42
define("CHECKLOGIN_ADMINOFF",		0x20000);
43
define("CHECKLOGIN_WEBONLY",		0x40000);
44
45
46
47
48
49
50
51
52
53

#
# 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
54
    $random_bytes = fread($fp, 128);
55
56
57
58
59
60
    fclose($fp);

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

61
62
63
#
# 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
64
# and HASH back, and then check the DB to see if the user is really
65
66
67
68
69
70
# logged in.
# 
function GETLOGIN() {
    if (($uid = GETUID()) == FALSE)
	    return FALSE;

71
72
    $check = CHECKLOGIN($uid);

73
    if ($check & (CHECKLOGIN_LOGGEDIN|CHECKLOGIN_MAYBEVALID))
74
75
76
77
78
79
80
81
82
83
84
	    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
85
    global $TBNAMECOOKIE;
Chad Barb's avatar
   
Chad Barb committed
86
    global $nocookieuid;
87

Leigh B. Stoller's avatar
Leigh B. Stoller committed
88
    $curname = FALSE;
Chad Barb's avatar
   
Chad Barb committed
89

Leigh B. Stoller's avatar
Leigh B. Stoller committed
90
91
92
    # XXX - nocookieuid is sent by netbuild applet in URL.
    if (isset($_GET['nocookieuid'])) {
	$curname = $_GET['nocookieuid'];
Chad Barb's avatar
   
Chad Barb committed
93
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
94
95
96
97
98
    elseif (isset($_COOKIE[$TBNAMECOOKIE])) {
	$curname = $_COOKIE[$TBNAMECOOKIE];
    }
    else
	return FALSE;
Chad Barb's avatar
   
Chad Barb committed
99

Leigh B. Stoller's avatar
Leigh B. Stoller committed
100
101
    # Verify valid string (no special chars like single/double quotes!).
    if (!TBvalid_uid($curname)) {
Chad Barb's avatar
   
Chad Barb committed
102
103
	return FALSE;
    }
104
105
106
    return $curname;
}

107
#
108
# Verify a login by sucking a UIDs current hash value out of the database.
109
110
111
# 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.
#
112
# Returns a combination of the CHECKLOGIN values above.
113
#
114
function CHECKLOGIN($uid) {
115
    global $TBAUTHCOOKIE, $HTTP_COOKIE_VARS, $TBAUTHTIMEOUT;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
116
    global $CHECKLOGIN_STATUS, $CHECKLOGIN_UID;
Chad Barb's avatar
   
Chad Barb committed
117
    global $nocookieauth;
118
119
120
121
122
123
124
    #
    # If we already figured this out, do not duplicate work!
    #
    if ($CHECKLOGIN_STATUS != CHECKLOGIN_NOSTATUS) {
	return $CHECKLOGIN_STATUS;
    }
    $CHECKLOGIN_UID = $uid;
125

Chad Barb's avatar
   
Chad Barb committed
126
127
128
129
130
131
132
133
    # 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];
    }
    
134
135
136
137
    #
    # Note that we get multiple rows back because of the group_membership
    # join. No big deal.
    # 
138
    $query_result =
139
	DBQueryFatal("select NOW()>=u.pswd_expires,l.hashkey,l.timeout, ".
140
		     "       status,admin,cvsweb,g.trust,adminoff,webonly ".
141
142
		     " from users as u ".
		     "left join login as l on l.uid=u.uid ".
143
		     "left join group_membership as g on g.uid=u.uid ".
144
		     "where u.uid='$uid'");
145

146
    # No such user.
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
    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
    # values. 
    #
    $trusted = 0;
    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;
	}
169
170
	$adminoff = $row[7];
	$webonly  = $row[8];
171
    }
172
173
174
175

    #
    # If user exists, but login has no entry, quit now.
    #
176
177
178
179
    if (!$hashkey) {
	$CHECKLOGIN_STATUS = CHECKLOGIN_NOTLOGGEDIN;
	return $CHECKLOGIN_STATUS;
    }
180

181
    #
182
    # Check for frozen account. Might do something interesting later.
183
    #
184
    if (! strcmp($status, TBDB_USERSTATUS_FROZEN)) {
185
	DBQueryFatal("DELETE FROM login WHERE uid='$uid'");
186
187
	$CHECKLOGIN_STATUS = CHECKLOGIN_NOTLOGGEDIN;
	return $CHECKLOGIN_STATUS;
188
189
    }

190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
    #
    # Check for expired login or for a hash that mismatches. Treat the same.
    #
    if ((time() > $timeout) ||
	(isset($curhash) && $curhash && strcmp($curhash, $hashkey))) {
	
        #
        # Clear out the database entry for completeness.
        #
	DBQueryFatal("DELETE FROM login WHERE uid='$uid'");
	$CHECKLOGIN_STATUS = CHECKLOGIN_TIMEDOUT;
	return $CHECKLOGIN_STATUS;
    }

    #
    # We know the login has not expired. We also know from the above
    # test that the hashkey was either null or matched. 
207
    # 
208
209
210
211
212
    if (strcmp($curhash, $hashkey) == 0) {
        #
   	# We 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.
213
	#
214
215
216
217
218
219
220
221
222
223
224
225
226
	$timeout = time() + $TBAUTHTIMEOUT;

	$query_result =
	    DBQueryFatal("UPDATE login set timeout='$timeout' ".
			 "WHERE uid='$uid'");

	$CHECKLOGIN_STATUS = CHECKLOGIN_LOGGEDIN;
    }
    else {
        #
	# A login is valid, but we have no proof yet. Cookies off?
	# 
	$CHECKLOGIN_STATUS = CHECKLOGIN_MAYBEVALID;
227
228
229
    }

    #
230
    # Now add in the modifiers.
231
    #
232
233
234
235
    if ($expired)
	$CHECKLOGIN_STATUS |= CHECKLOGIN_PSWDEXPIRED;
    if ($admin)
	$CHECKLOGIN_STATUS |= CHECKLOGIN_ISADMIN;
236
237
    if ($adminoff)
	$CHECKLOGIN_STATUS |= CHECKLOGIN_ADMINOFF;
238
239
    if ($webonly)
	$CHECKLOGIN_STATUS |= CHECKLOGIN_WEBONLY;
240
241
242
243
244
245
246
247
248
249
250
251
252
    if ($trusted)
	$CHECKLOGIN_STATUS |= CHECKLOGIN_TRUSTED;
    if ($cvsweb)
	$CHECKLOGIN_STATUS |= CHECKLOGIN_CVSWEB;
    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;

253
254
255
    #
    # Set the magic enviroment variable, if appropriate, for the sake of
    # any processes we might spawn. We prepend an HTTP_ on the front of
256
	# the variable name, so that it will get through suexec.
257
258
259
260
261
    #
    if ($admin && !$adminoff) {
    	putenv("HTTP_WITH_TB_ADMIN_PRIVS=1");
    }

262
    return $CHECKLOGIN_STATUS;
263
264
265
266
}

#
# This one checks for login, but then dies with an appropriate error
267
268
# message. The modifier allows you to turn off checks for specified
# conditions. 
269
#
270
function LOGGEDINORDIE($uid, $modifier = 0) {
271
272
    global $TBBASE, $BASEPATH, $HTTP_COOKIE_VARS, $TBNAMECOOKIE;

Leigh B. Stoller's avatar
Leigh B. Stoller committed
273
    # If our login is not valid, then the uid is already set to "",
274
275
276
277
    # 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]; }

278
279
    $link = "\n<a href=\"$TBBASE/login.php3?refer=1\">Please ".
	"log in again.</a>\n";
280

281
    if ($uid == FALSE)
282
        USERERROR("You do not appear to be logged in! $link", 1);
283
    
284
    $status = CHECKLOGIN($uid);
285
286
287

    switch ($status & CHECKLOGIN_STATUSMASK) {
    case CHECKLOGIN_NOTLOGGEDIN:
288
        USERERROR("You do not appear to be logged in! $link", 1);
289
        break;
290
    case CHECKLOGIN_TIMEDOUT:
291
        USERERROR("Your login has timed out! $link", 1);
292
        break;
293
294
    case CHECKLOGIN_MAYBEVALID:
        USERERROR("Your login cannot be verified. Are cookies turned on? ".
295
		  "Are you using https? Are you logged in using another ".
296
		  "browser or another machine? $link", 1);
297
        break;
298
299
300
301
    case CHECKLOGIN_LOGGEDIN:
	break;
    default:
	TBERROR("LOGGEDINORDIE failed mysteriously", 1);
302
    }
303
304
305
306
307
308
309

    $status = $status & ~$modifier;

    #
    # Check other conditions.
    #
    if ($status & CHECKLOGIN_PSWDEXPIRED)
310
311
        USERERROR("Your password has expired. ".
		  "<a href=moduserinfo.php3>Please change it now!</a>", 1);
312
313
314
315
316
317
    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);
318
319
    if ($status & CHECKLOGIN_WEBONLY)
        USERERROR("Your account does not permit you to access this page!", 1);
320
321
322
323
324
325
326
327
328
329

    #
    # 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;
330
331
332
}

#
333
334
335
336
# 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.
337
#
338
function ISADMIN($uid = 1) {
339
340
    global $CHECKLOGIN_STATUS;
    
341
342
    if ($CHECKLOGIN_STATUS == CHECKLOGIN_NOSTATUS) {
	$uid=GETUID();
343
	TBERROR("ISADMIN: $uid is not logged in!", 1);
344
    }
345

346
    return (($CHECKLOGIN_STATUS &
347
	     (CHECKLOGIN_LOGGEDIN|CHECKLOGIN_ISADMIN|CHECKLOGIN_ADMINOFF)) ==
348
	    (CHECKLOGIN_LOGGEDIN|CHECKLOGIN_ISADMIN));
349
350
}

351
# Is this user a real administrator (ignore onoff bit).
352
function ISADMINISTRATOR() {
353
354
355
356
357
358
359
360
361
362
    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));
}

363
364
365
#
# Attempt a login.
# 
Leigh B. Stoller's avatar
Leigh B. Stoller committed
366
function DOLOGIN($uid, $password, $adminmode = 0) {
367
    global $TBDBNAME, $TBAUTHCOOKIE, $TBAUTHDOMAIN, $TBAUTHTIMEOUT;
368
    global $TBNAMECOOKIE, $TBSECURECOOKIES;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
369
370
371
    
    # Caller makes these checks too.
    if (!TBvalid_uid($uid) || !isset($password) || $password == "") {
372
373
374
	return -1;
    }

375
    $query_result =
376
	DBQueryFatal("SELECT usr_pswd,admin FROM users WHERE uid='$uid'");
377
378
379
380
381
382

    #
    # Check password in the database against provided. 
    #
    if ($row = mysql_fetch_row($query_result)) {
        $db_encoding = $row[0];
383
	$isadmin     = $row[1];
384
        $encoding = crypt("$password", $db_encoding);
385
386
387
388
389
390
391
392
        if (strcmp($encoding, $db_encoding)) {
            return -1;
        }
        #
        # 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. 
        #
393
	$timeout = time() + $TBAUTHTIMEOUT;
394
	$hashkey = GENHASH();
395
396
397
        $query_result =
	    DBQueryFatal("SELECT timeout FROM login WHERE uid='$uid'");
	
398
	if (mysql_num_rows($query_result)) {
399
400
401
	    DBQueryFatal("UPDATE login set ".
			 "timeout='$timeout', hashkey='$hashkey' ".
			 "WHERE uid='$uid'");
402
403
	}
	else {
404
405
	    DBQueryFatal("INSERT into login (uid, hashkey, timeout) ".
			 "VALUES ('$uid', '$hashkey', '$timeout')");
406
407
	}

408
	#
409
	# Usage stats. 
410
	#
411
412
413
414
	DBQueryFatal("update user_stats set ".
		     " weblogin_count=weblogin_count+1, ".
		     " weblogin_last=now() ".
		     "where uid='$uid'");
415

416
	#
417
418
419
420
421
422
423
424
425
	# Issue the cookie requests so that subsequent pages come back
	# with the hash value and auth usr embedded.

	#
	# For the hashkey, we give it a longish timeout since we are going
	# to control the actual timeout via the database. This just avoids
	# having to update the hash as we update the timeout in the database
	# each time the user does something. Eventually the cookie will
	# expire and the user will be forced to log in again anyway. 
426
	#
427
	$timeout = time() + (60 * 60 * 24) + $TBAUTHTIMEOUT;
428
429
	setcookie($TBAUTHCOOKIE, $hashkey, $timeout, "/",
                  $TBAUTHDOMAIN, $TBSECURECOOKIES);
430

431
432
433
434
435
436
437
438
439
440
441
	#
	# 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.
	# 
	$timeout = time() + (60 * 60 * 24 * 32);
	setcookie($TBNAMECOOKIE, $uid, $timeout, "/", $TBAUTHDOMAIN, 0);

442
	#
443
444
445
446
447
	# 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. 
448
	#
449
450
451
452
453
	$adminoff = 1;
	if ($adminmode && $isadmin) {
	    $adminoff = 0;
	}
	DBQueryFatal("update users set adminoff=$adminoff where uid='$uid'");
454

455
456
457
458
459
460
461
462
	return 0;
    }
    #
    # No such user
    #
    return -1;
}

463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480

#
# 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];
481
        $encoding = crypt("$password", $db_encoding);
482
483
484
485
486
487
488
489
490
	
        if (strcmp($encoding, $db_encoding)) {
            return -1;
	}
	return 0;
    }
    return -1;
}

491
#
492
# Log out a UID.
493
494
#
function DOLOGOUT($uid) {
495
    global $TBDBNAME, $TBSECURECOOKIES, $CHECKLOGIN_STATUS;
496

497
498
499
500
501
    # Pedantic check.
    if (!TBvalid_uid($uid)) {
	return 1;
    }

502
503
504
505
    $CHECKLOGIN_STATUS = CHECKLOGIN_NOTLOGGEDIN;

    $query_result =
	DBQueryFatal("SELECT hashkey timeout FROM login WHERE uid='$uid'");
506
507
508

    # Not logged in.
    if (($row = mysql_fetch_array($query_result)) == 0) {
509
	return 1;
510
511
512
    }

    $hashkey = $row[hashkey];
513
    $timeout = time() - 1000000;
514

515
    DBQueryFatal("DELETE FROM login WHERE uid='$uid'");
516
517
518
519

    #
    # Issue a cookie request to delete the cookie. 
    #
520
    setcookie($TBAUTHCOOKIE, "", $timeout, "/", $TBAUTHDOMAIN, 0);
521
522
523
524

    return 0;
}

525
#
Chad Barb's avatar
   
Chad Barb committed
526
# Simple "nologins" support.
527
528
#
function NOLOGINS() {
529
    global $CHECKLOGIN_NOLOGINS;
530

Chad Barb's avatar
   
Chad Barb committed
531
532
    if ($CHECKLOGIN_NOLOGINS == -1) {
	$CHECKLOGIN_NOLOGINS = TBGetSiteVar("web/nologins");
533
    }
Chad Barb's avatar
   
Chad Barb committed
534
	
535
    return $CHECKLOGIN_NOLOGINS;
536
537
}

538
539
540
function LASTWEBLOGIN($uid) {
    global $TBDBNAME;

541
    $query_result =
542
        DBQueryFatal("SELECT weblogin_last from user_stats where uid='$uid'");
543
544
545
    
    if (mysql_num_rows($query_result)) {
	$lastrow      = mysql_fetch_array($query_result);
546
	return $lastrow[weblogin_last];
547
548
549
550
    }
    return 0;
}

551
552
553
554
555
556
557
558
559
560
561
function HASREALACCOUNT($uid) {
    $query_result =
	DBQueryFatal("select status,webonly from users where uid='$uid'");

    if (!mysql_num_rows($query_result)) {
	return 0;
    }
    $row = mysql_fetch_array($query_result);
    $status  = $row[0];
    $webonly = $row[1];

562
563
564
    if ($webonly ||
	(strcmp($status, TBDB_USERSTATUS_ACTIVE) &&
	 strcmp($status, TBDB_USERSTATUS_FROZEN))) {
565
566
567
568
569
	return 0;
    }
    return 1;
}

570
571
572
573
#
# Beware empty spaces (cookies)!
# 
?>