tbauth.php3 29.6 KB
Newer Older
1 2
<?php
#
Leigh B. Stoller's avatar
Leigh B. Stoller committed
3
# EMULAB-COPYRIGHT
4
# Copyright (c) 2000-2006 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
$CHECKLOGIN_HASHKEY             = null;
$CHECKLOGIN_HASHHASH            = null;
20 21 22 23 24 25 26 27 28

#
# 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
29 30
define("CHECKLOGIN_STATUSMASK",		0x0000ff);
define("CHECKLOGIN_MODMASK",		0xffff00);
31 32 33 34 35
#
# 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. 
#
36 37 38 39 40 41 42 43 44 45
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);
46
define("CHECKLOGIN_ADMINON",		0x020000);
47 48 49
define("CHECKLOGIN_WEBONLY",		0x040000);
define("CHECKLOGIN_PLABUSER",		0x080000);
define("CHECKLOGIN_STUDLY",		0x100000);
50
define("CHECKLOGIN_WIKIONLY",		0x200000);
51 52
define("CHECKLOGIN_OPSGUY",		0x400000);	# Member of emulab-ops.
define("CHECKLOGIN_ISFOREIGN_ADMIN",	0x800000);	# Admin of another Emulab. 
53

54 55 56
#
# Constants for tracking possible login attacks.
#
57 58
define("DOLOGIN_MAXUSERATTEMPTS",	15);
define("DOLOGIN_MAXIPATTEMPTS",		25);
59

60 61 62 63 64 65 66 67 68
#
# 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
69
    $random_bytes = fread($fp, 128);
70 71 72 73 74 75
    fclose($fp);

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

76 77 78 79 80 81 82 83 84 85 86 87 88
#
# Return the value of what we told the browser to remember for the login.
# Currently, this is an email address, stored in a long term cookie.
#
function REMEMBERED_ID() {
    global $TBEMAILCOOKIE;

    if (isset($_COOKIE[$TBEMAILCOOKIE])) {
	return $_COOKIE[$TBEMAILCOOKIE];
    }
    return null;
}

89 90 91
#
# 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
92
# and HASH back, and then check the DB to see if the user is really
93 94 95 96 97 98
# logged in.
# 
function GETLOGIN() {
    if (($uid = GETUID()) == FALSE)
	    return FALSE;

99 100
    $check = CHECKLOGIN($uid);

101
    if ($check & (CHECKLOGIN_LOGGEDIN|CHECKLOGIN_MAYBEVALID))
102 103 104 105 106 107 108 109 110 111 112
	    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
113
    global $TBNAMECOOKIE;
Chad Barb's avatar
 
Chad Barb committed
114
    global $nocookieuid;
115

Leigh B. Stoller's avatar
Leigh B. Stoller committed
116
    $curname = FALSE;
Chad Barb's avatar
 
Chad Barb committed
117

Leigh B. Stoller's avatar
Leigh B. Stoller committed
118 119 120
    # XXX - nocookieuid is sent by netbuild applet in URL.
    if (isset($_GET['nocookieuid'])) {
	$curname = $_GET['nocookieuid'];
Chad Barb's avatar
 
Chad Barb committed
121
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
122 123 124 125 126
    elseif (isset($_COOKIE[$TBNAMECOOKIE])) {
	$curname = $_COOKIE[$TBNAMECOOKIE];
    }
    else
	return FALSE;
Chad Barb's avatar
 
Chad Barb committed
127

Leigh B. Stoller's avatar
Leigh B. Stoller committed
128
    # Verify valid string (no special chars like single/double quotes!).
Leigh B. Stoller's avatar
Leigh B. Stoller committed
129 130 131 132
    # 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
133 134
	return FALSE;
    }
135 136 137
    return $curname;
}

138
#
139
# Verify a login by sucking a UIDs current hash value out of the database.
140 141 142
# 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.
#
143
# Returns a combination of the CHECKLOGIN values above.
144
#
145
function CHECKLOGIN($uid) {
146
    global $TBAUTHCOOKIE, $TBLOGINCOOKIE, $HTTP_COOKIE_VARS, $TBAUTHTIMEOUT;
147
    global $CHECKLOGIN_STATUS, $CHECKLOGIN_UID, $CHECKLOGIN_NODETYPES;
148
    global $CHECKLOGIN_WIKINAME, $TBOPSPID;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
149
    global $EXPOSEARCHIVE, $EXPOSETEMPLATES;
150
    global $CHECKLOGIN_HASHKEY, $CHECKLOGIN_HASHHASH;
Chad Barb's avatar
 
Chad Barb committed
151
    global $nocookieauth;
152
    
153 154 155 156 157 158 159
    #
    # If we already figured this out, do not duplicate work!
    #
    if ($CHECKLOGIN_STATUS != CHECKLOGIN_NOSTATUS) {
	return $CHECKLOGIN_STATUS;
    }
    $CHECKLOGIN_UID = $uid;
160

Chad Barb's avatar
 
Chad Barb committed
161 162 163 164 165 166 167
    # 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];
    }
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
    $hashhash = $HTTP_COOKIE_VARS[$TBLOGINCOOKIE];

    #
    # We have to get at least one of the hashes. The Java applets do not
    # send it, but web browsers will.
    #
    if (!isset($curhash) && !isset($hashhash)) {
	$CHECKLOGIN_STATUS = CHECKLOGIN_NOTLOGGEDIN;
	return $CHECKLOGIN_STATUS;
    }
    if (isset($curhash) &&
	! preg_match("/^[\w]+$/", $curhash)) {
	$CHECKLOGIN_STATUS = CHECKLOGIN_NOTLOGGEDIN;
	return $CHECKLOGIN_STATUS;
    }
    if (isset($hashhash) &&
	! preg_match("/^[\w]+$/", $hashhash)) {
	$CHECKLOGIN_STATUS = CHECKLOGIN_NOTLOGGEDIN;
	return $CHECKLOGIN_STATUS;
    }
188 189 190 191 192 193 194 195

    if (isset($curhash)) {
	$CHECKLOGIN_HASHKEY = $safe_curhash = addslashes($curhash);
    }
    if (isset($hashhash)) {
	$CHECKLOGIN_HASHHASH = $safe_hashhash = addslashes($hashhash);
    }
    $safe_uid = addslashes($uid);
Chad Barb's avatar
 
Chad Barb committed
196
    
197 198 199 200
    #
    # Note that we get multiple rows back because of the group_membership
    # join. No big deal.
    # 
201
    $query_result =
202
	DBQueryFatal("select NOW()>=u.pswd_expires,l.hashkey,l.timeout, ".
203
		     "       status,admin,cvsweb,g.trust,l.adminon,webonly, " .
204
		     "       user_interface,n.type,u.stud,u.wikiname, ".
205
		     "       u.wikionly,g.pid,u.foreign_admin " .
206 207
		     " from users as u ".
		     "left join login as l on l.uid=u.uid ".
208
		     "left join group_membership as g on g.uid=u.uid ".
209 210 211 212 213
		     "left join nodetypeXpid_permissions as n on g.pid=n.pid ".
		     "where u.uid='$safe_uid' and ".
		     (isset($curhash) ?
		      "l.hashkey='$safe_curhash'" :
		      "l.hashhash='$safe_hashhash'"));
214

215
    # No such user.
216 217 218 219 220 221 222
    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
223
    # values and the pid. pid is a hack.
224 225
    #
    $trusted = 0;
226
    $opsguy  = 0;
227
    
228 229 230 231 232 233 234 235 236 237 238 239
    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;
	}
240
	$adminon  = $row[7];
241
	$webonly  = $row[8];
242
	$interface= $row[9];
243 244

	$type     = $row[10];
245
	$stud     = $row[11];
246
	$wikiname = $row[12];
247
	$wikionly = $row[13];
248

249 250 251 252 253
	# Check for an ops guy.
	$pid = $row[14];
	if ($pid == $TBOPSPID) {
	    $opsguy = 1;
	}
254 255 256 257

	# Set foreign_admin=1 for admins of another Emulab.
	$foreign_admin   = $row[15];

258
	$CHECKLOGIN_NODETYPES[$type] = 1;
259
    }
260 261 262 263

    #
    # If user exists, but login has no entry, quit now.
    #
264 265 266 267
    if (!$hashkey) {
	$CHECKLOGIN_STATUS = CHECKLOGIN_NOTLOGGEDIN;
	return $CHECKLOGIN_STATUS;
    }
268

269
    #
270
    # Check for frozen account. Might do something interesting later.
271
    #
272
    if (! strcmp($status, TBDB_USERSTATUS_FROZEN)) {
273
	DBQueryFatal("DELETE FROM login WHERE uid='$safe_uid'");
274 275
	$CHECKLOGIN_STATUS = CHECKLOGIN_NOTLOGGEDIN;
	return $CHECKLOGIN_STATUS;
276 277
    }

278
    #
279 280
    # Check for expired login. It does not matter if the cookie matches,
    # kill the entry anyway so the user is officially logged out.
281
    #
282
    if (time() > $timeout) {
283
	DBQueryFatal("DELETE FROM login WHERE uid='$safe_uid'");
284 285 286
	$CHECKLOGIN_STATUS = CHECKLOGIN_TIMEDOUT;
	return $CHECKLOGIN_STATUS;
    }
287
    $CHECKLOGIN_IDLETIME = time() - ($timeout - $TBAUTHTIMEOUT);
288 289

    #
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304
    # 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).
305
	#
306 307 308 309 310 311 312 313 314
	if ($curhash != $hashkey) {
	    #
	    # User is not logged in from this browser. Must be stale.
	    # 
	    $CHECKLOGIN_STATUS = CHECKLOGIN_NOTLOGGEDIN;
	    return $CHECKLOGIN_STATUS;
	}
	else {
            #
315 316
	    # User is logged in.
	    #
317 318
	    $CHECKLOGIN_STATUS = CHECKLOGIN_LOGGEDIN;
	}
319 320
    }
    else {
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341
	#
	# 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!
	#
	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;
	}
342 343 344
    }

    #
345
    # Now add in the modifiers.
346
    #
347 348 349 350
    if ($expired)
	$CHECKLOGIN_STATUS |= CHECKLOGIN_PSWDEXPIRED;
    if ($admin)
	$CHECKLOGIN_STATUS |= CHECKLOGIN_ISADMIN;
351 352
    if ($adminon)
	$CHECKLOGIN_STATUS |= CHECKLOGIN_ADMINON;
353 354
    if ($webonly)
	$CHECKLOGIN_STATUS |= CHECKLOGIN_WEBONLY;
355 356
    if ($wikionly)
	$CHECKLOGIN_STATUS |= CHECKLOGIN_WIKIONLY;
357 358
    if ($trusted)
	$CHECKLOGIN_STATUS |= CHECKLOGIN_TRUSTED;
359 360
    if ($stud)
	$CHECKLOGIN_STATUS |= CHECKLOGIN_STUDLY;
361 362
    if ($cvsweb)
	$CHECKLOGIN_STATUS |= CHECKLOGIN_CVSWEB;
363
    if ($interface == TBDB_USER_INTERFACE_PLAB)
364
	$CHECKLOGIN_STATUS |= CHECKLOGIN_PLABUSER;
365 366 367 368 369 370 371 372
    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;
373 374
    if (isset($wikiname) && $wikiname != "")
	$CHECKLOGIN_WIKINAME = $wikiname;
375 376
    if ($opsguy)
	$CHECKLOGIN_STATUS |= CHECKLOGIN_OPSGUY;
377 378
    if ($foreign_admin)
	$CHECKLOGIN_STATUS |= CHECKLOGIN_ISFOREIGN_ADMIN;
379

380 381 382
    #
    # Set the magic enviroment variable, if appropriate, for the sake of
    # any processes we might spawn. We prepend an HTTP_ on the front of
383
    # the variable name, so that it will get through suexec.
384
    #
385
    if ($admin && $adminon) {
386 387
    	putenv("HTTP_WITH_TB_ADMIN_PRIVS=1");
    }
388 389 390
    # XXX Temporary.
    if ($stud) {
	$EXPOSEARCHIVE = 1;
391
	$EXPOSETEMPLATES = 1;
392
    }
393
    return $CHECKLOGIN_STATUS;
394 395 396 397
}

#
# This one checks for login, but then dies with an appropriate error
398 399
# message. The modifier allows you to turn off checks for specified
# conditions. 
400
#
401
function LOGGEDINORDIE($uid, $modifier = 0, $login_url = NULL) {
402
    global $TBBASE, $BASEPATH, $HTTP_COOKIE_VARS, $TBNAMECOOKIE;
403
    global $TBAUTHTIMEOUT, $CHECKLOGIN_HASHKEY;
404

Leigh B. Stoller's avatar
Leigh B. Stoller committed
405
    # If our login is not valid, then the uid is already set to "",
406
    # so refresh it to the cookie value. Then we can pass the right
407 408 409 410
    # uid to checklogin, so we can give the right error message.
    if ($uid == "") {
	$uid = $HTTP_COOKIE_VARS[$TBNAMECOOKIE];

Leigh B. Stoller's avatar
Leigh B. Stoller committed
411 412 413 414 415 416 417 418
	if ($uid == "") {
		$uid = FALSE;
	}
	else {
            # Verify valid string (no special chars like single/double quotes!)
	    if (! preg_match("/^[-\w]+$/", $uid)) {
		TBERROR("LOGGEDINORDIE: Illegal characters in $uid", 1);
	    }
419 420
	}
    }
421

422 423 424 425 426 427 428 429 430
    #
    # 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 ".
431
	"log in again.</a>\n";
432

433
    if ($uid == FALSE)
434
        USERERROR("You do not appear to be logged in! $link", 1);
435
    
436
    $status = CHECKLOGIN($uid);
437 438 439

    switch ($status & CHECKLOGIN_STATUSMASK) {
    case CHECKLOGIN_NOTLOGGEDIN:
440
        USERERROR("You do not appear to be logged in! $link", 1);
441
        break;
442
    case CHECKLOGIN_TIMEDOUT:
443
        USERERROR("Your login has timed out! $link", 1);
444
        break;
445 446
    case CHECKLOGIN_MAYBEVALID:
        USERERROR("Your login cannot be verified. Are cookies turned on? ".
447
		  "Are you using https? Are you logged in using another ".
448
		  "browser or another machine? $link", 1);
449
        break;
450
    case CHECKLOGIN_LOGGEDIN:
451 452 453 454 455 456
        #
	# 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.
        #
457 458
	if (! is_null($CHECKLOGIN_HASHKEY)) {
	    $timeout = time() + $TBAUTHTIMEOUT;
459

460 461 462
	    DBQueryFatal("UPDATE login set timeout='$timeout' ".
			 "where uid='$uid' and hashkey='$CHECKLOGIN_HASHKEY'");
	}
463 464 465
	break;
    default:
	TBERROR("LOGGEDINORDIE failed mysteriously", 1);
466
    }
467 468 469 470 471 472 473

    $status = $status & ~$modifier;

    #
    # Check other conditions.
    #
    if ($status & CHECKLOGIN_PSWDEXPIRED)
474 475
        USERERROR("Your password has expired. ".
		  "<a href=moduserinfo.php3>Please change it now!</a>", 1);
476 477 478 479 480 481
    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);
482
    if (($status & CHECKLOGIN_WEBONLY) && ! ISADMIN($uid))
483
        USERERROR("Your account does not permit you to access this page!", 1);
484 485
    if (($status & CHECKLOGIN_WIKIONLY) && ! ISADMIN($uid))
        USERERROR("Your account does not permit you to access this page!", 1);
486 487 488 489 490 491 492 493 494 495

    #
    # 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;
496 497 498
}

#
499 500 501 502
# 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.
503
#
504
function ISADMIN($uid = 1) {
505 506
    global $CHECKLOGIN_STATUS;
    
507
    if ($CHECKLOGIN_STATUS == CHECKLOGIN_NOSTATUS) {
508
	return 0;
509
    }
510

511
    return (($CHECKLOGIN_STATUS &
512 513
	     (CHECKLOGIN_LOGGEDIN|CHECKLOGIN_ISADMIN|CHECKLOGIN_ADMINON)) ==
	    (CHECKLOGIN_LOGGEDIN|CHECKLOGIN_ISADMIN|CHECKLOGIN_ADMINON));
514 515
}

516 517 518 519 520 521 522 523 524 525 526 527 528 529 530

# Is this user an admin of another Emulab.
function ISFOREIGN_ADMIN($uid = 1) {
    global $CHECKLOGIN_STATUS;

    # Definitely not, if not logged in.
    if ($CHECKLOGIN_STATUS == CHECKLOGIN_NOSTATUS) {
	return 0;
    }

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

531 532 533 534 535 536 537 538 539 540 541 542 543
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));
}

544 545 546 547 548 549 550 551 552 553 554 555 556
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));
}

557 558 559 560 561 562 563 564 565 566 567 568 569
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));
}

570
# Is this user a real administrator (ignore onoff bit).
571
function ISADMINISTRATOR() {
572 573 574 575 576 577 578 579 580 581
    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));
}

582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605
#
# Toggle current login admin bit. Must be an administrator of course!
#
function SETADMINMODE($uid, $onoff) {
    global $HTTP_COOKIE_VARS, $TBAUTHCOOKIE;
    
    # This makes sure the user is actually logged in secure (https).
    if (! ISADMINISTRATOR())
	return;

    $curhash = $HTTP_COOKIE_VARS[$TBAUTHCOOKIE];

    if (!isset($curhash) ||
	!preg_match("/^[\w]+$/", $curhash)) {
	return;
    }
    $onoff   = addslashes($onoff);
    $curhash = addslashes($curhash);
    $uid     = addslashes($uid);
    
    DBQueryFatal("update login set adminon='$onoff' ".
		 "where uid='$uid' and hashkey='$curhash'");
}

606
# Is this user a planetlab user? Returns 1 if they are, 0 if not.
607 608 609
function ISPLABUSER() {
    global $CHECKLOGIN_STATUS;

610 611 612 613 614 615 616 617 618
    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 =
619
	    DBQueryFatal("SELECT user_interface FROM users WHERE uid='$uid'");
620 621 622
	if (!mysql_num_rows($query_result)) {
	    return 0;
	}
623

624 625
	$row = mysql_fetch_row($query_result);
	if ($row[0]) {
626
	    return ($row[0] == TBDB_USER_INTERFACE_PLAB);
627 628 629
	} else {
	    return 0;
	}
630
    } else {
631
	#
632
	# For logged-in users, we recorded it in the the login status
633 634
	#
	return (($CHECKLOGIN_STATUS &
635 636
		 (CHECKLOGIN_PLABUSER)) ==
		(CHECKLOGIN_PLABUSER));
637 638 639
    }
}

640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662
#
# 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;
    }
}

663 664 665
#
# Attempt a login.
# 
666
function DOLOGIN($token, $password, $adminmode = 0) {
667 668
    global $TBAUTHCOOKIE, $TBAUTHDOMAIN, $TBAUTHTIMEOUT;
    global $TBNAMECOOKIE, $TBLOGINCOOKIE, $TBSECURECOOKIES;
669
    global $TBMAIL_OPS, $TBMAIL_AUDIT, $TBMAIL_WWW;
670
    global $WIKISUPPORT, $WIKICOOKIENAME;
671
    global $BUGDBSUPPORT, $BUGDBCOOKIENAME;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
672 673
    
    # Caller makes these checks too.
674 675
    if ((!TBvalid_uid($token) && !TBvalid_email($token)) ||
	!isset($password) || $password == "") {
676 677
	return -1;
    }
678
    $now = time();
679

680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705
    #
    # 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 =
706
	DBQueryFatal("select uid,usr_pswd,admin,weblogin_frozen,".
707
		     "       weblogin_failcount,weblogin_failstamp, ".
708
		     "       usr_email,usr_name,unix_uid,usr_email ".
709 710 711 712
		     "from users where ".
		     (TBvalid_email($token) ?
		      "usr_email='$token'" :
		      "uid='$token'"));
713 714 715 716

    #
    # Check password in the database against provided. 
    #
717 718
    do {
      if ($row = mysql_fetch_array($user_result)) {
719
	$uid         = $row['uid'];
720 721 722 723 724 725 726
        $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'];
727
	$uid_idx     = $row['unix_uid'];
728
	$usr_email   = $row['usr_email'];
729

730 731
	# Check for frozen accounts. We do not update the IP record when
	# an account is frozen.
732
	if ($frozen) {
733
	    DBQueryFatal("update users set ".
734 735 736 737 738 739
			 "       weblogin_failcount=weblogin_failcount+1, ".
			 "       weblogin_failstamp='$now' ".
			 "where uid='$uid'");
	    return -1;
	}
	
740
        $encoding = crypt("$password", $db_encoding);
741
        if (strcmp($encoding, $db_encoding)) {
742
	    #
743
	    # Bump count and check for too many consecutive failures.
744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761
	    #
	    $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', ".
762
			 "       weblogin_failstamp='$now' ".
763 764
			 "where uid='$uid'");
            break;
765
        }
766
	#
767
	# Pass!
768 769
	#

770
	#
771
	# Set adminmode off on new logins, unless user requested to be
772 773 774 775
	# 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. 
776
	#
777
	$adminon = 0;
778
	if ($adminmode && $isadmin) {
779
	    $adminon = 1;
780
	}
781 782 783 784

        #
        # Insert a record in the login table for this uid.
	#
785
	if (DOLOGIN_MAGIC($uid, $usr_email, $adminon) < 0) {
786 787 788 789 790 791 792 793 794 795
	    return -1;
	}

	#
	# Usage stats. 
	#
	DBQueryFatal("update user_stats set ".
		     " weblogin_count=weblogin_count+1, ".
		     " weblogin_last=now() ".
		     "where uid_idx='$uid_idx'");
796

797 798 799 800
	# Clear IP record since we have a sucessful login from the IP.
	if (isset($IP)) {
	    DBQueryFatal("delete from login_failures where IP='$IP'");
	}
801
	return 0;
802 803
      }
    } while (0);
804 805 806
    #
    # No such user
    #
807
    if (!isset($IP)) {
808
	return -1;
809 810 811
    }
	
    $ipfrozen = 0;
812 813 814 815
    if (isset($iprow)) {
	$ipfailcount = $iprow['failcount'];

        #
816
        # Bump count.
817 818 819 820
        #
	$ipfailcount++;
    }
    else {
821 822 823
	#
	# First failure.
	# 
824 825
	$ipfailcount = 1;
    }
826 827 828 829

    #
    # Check for too many consecutive failures.
    #
830 831 832 833 834 835
    if ($ipfailcount > DOLOGIN_MAXIPATTEMPTS) {
	$ipfrozen = 1;
	    
	TBMAIL($TBMAIL_OPS,
	       "Web Login Freeze: '$IP'",
	       "Logins has been frozen because there were too many login\n".
836
	       "failures from $IP. Last attempted uid was '$token'.\n\n",
837 838 839 840 841 842 843 844
	       "From: $TBMAIL_OPS\n".
	       "Bcc: $TBMAIL_AUDIT\n".
	       "Errors-To: $TBMAIL_WWW");
    }
    DBQueryFatal("replace into login_failures set ".
		 "       IP='$IP', ".
		 "       frozen='$ipfrozen', ".
		 "       failcount='$ipfailcount', ".
845
		 "       failstamp='$now'");
846 847 848
    return -1;
}

849
function DOLOGIN_MAGIC($uid, $email = null, $adminon = 0)
850 851
{
    global $TBAUTHCOOKIE, $TBAUTHDOMAIN, $TBAUTHTIMEOUT;
852
    global $TBNAMECOOKIE, $TBLOGINCOOKIE, $TBSECURECOOKIES, $TBEMAILCOOKIE;
853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869
    global $TBMAIL_OPS, $TBMAIL_AUDIT, $TBMAIL_WWW;
    global $WIKISUPPORT, $WIKICOOKIENAME;
    global $BUGDBSUPPORT, $BUGDBCOOKIENAME;
    
    # Caller makes these checks too.
    if (!TBvalid_uid($uid)) {
	return -1;
    }
    $now = time();

    #
    # 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. 
    #
    $timeout = $now + $TBAUTHTIMEOUT;
    $hashkey = GENHASH();
870 871 872 873
    $crc     = bin2hex(mhash(MHASH_CRC32, $hashkey));

    DBQueryFatal("replace into login (uid,hashkey,hashhash,timeout,adminon) ".
		 "values ('$uid', '$hashkey', '$crc', '$timeout', $adminon)");
874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898

    #
    # Issue the cookie requests so that subsequent pages come back
    # with the hash value and auth usr embedded.

    #
    # 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.
    #
    setcookie($TBAUTHCOOKIE, $hashkey, 0, "/",
	      $TBAUTHDOMAIN, $TBSECURECOOKIES);

    #
    # 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. 
    #
    setcookie($TBLOGINCOOKIE, $crc, 0, "/", $TBAUTHDOMAIN, 0);

    #
899 900 901 902 903 904 905 906 907 908 909 910 911 912
    # We want to remember who the user was each time they load a page
    # NOTE: This cookie is integral to authorization, since we do not pass
    # around the UID anymore, but look for it in the cookie.
    #
    setcookie($TBNAMECOOKIE, $uid, 0, "/", $TBAUTHDOMAIN, 0);

    #
    # This is a long term cookie so we can remember who the user was, and
    # and stick that in the login box.
    #
    if ($email) {
	$timeout = $now + (60 * 60 * 24 * 365);
	setcookie($TBEMAILCOOKIE, $email, $timeout, "/", $TBAUTHDOMAIN, 0);
    }
913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934

    #
    # 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);
    }
	
    #
    # Ditto for bugdb
    # 
    if ($BUGDBSUPPORT) {
	$flushtime = time() - 1000000;
	    
	setcookie($BUGDBCOOKIENAME, "", $flushtime, "/",
		  $TBAUTHDOMAIN, $TBSECURECOOKIES);
    }
	
935
    DBQueryFatal("update users set ".
936 937 938 939 940
		 "       weblogin_failcount=0,weblogin_failstamp=0 ".
		 "where uid='$uid'");

    return 0;
}
941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958

#
# 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];
959
        $encoding = crypt("$password", $db_encoding);
960 961 962 963 964 965 966 967 968
	
        if (strcmp($encoding, $db_encoding)) {
            return -1;
	}
	return 0;
    }
    return -1;
}

969
#
970
# Log out a UID.
971 972
#
function DOLOGOUT($uid) {
973
    global $CHECKLOGIN_STATUS, $TBAUTHCOOKIE, $TBLOGINCOOKIE, $TBAUTHDOMAIN;
974
    global $WIKISUPPORT, $WIKICOOKIENAME, $HTTP_COOKIE_VARS;
975
    global $BUGDBSUPPORT, $BUGDBCOOKIENAME;
976

977 978 979 980
    # Pedantic check.
    if (!TBvalid_uid($uid)) {
	return 1;
    }
981 982
    $CHECKLOGIN_STATUS = CHECKLOGIN_NOTLOGGEDIN;

983 984 985 986 987 988 989
    $curhash = $HTTP_COOKIE_VARS[$TBAUTHCOOKIE];
    $hashhash = $HTTP_COOKIE_VARS[$TBLOGINCOOKIE];
    
    #
    # We have to get at least one of the hashes. 
    #
    if (!isset($curhash) && !isset($hashhash)) {
990
	return 1;
991
    }
992 993 994 995 996 997 998 999 1000 1001
    if (isset($curhash) &&
	! preg_match("/^[\w]+$/", $curhash)) {
	return 1;
    }
    if (isset($hashhash) &&
	! preg_match("/^[\w]+$/", $hashhash)) {
	return 1;
    }
    $safe_curhash  = addslashes($curhash);
    $safe_hashhash = addslashes($hashhash);
1002

1003 1004 1005 1006 1007
    DBQueryFatal("delete from login ".
		 " where uid='$uid' and ".
		 (isset($curhash) ?
		  "hashkey='$safe_curhash'" :
		  "hashhash='$safe_hashhash'"));
1008 1009

    #
1010
    # Issue a cookie request to delete the cookies. 
1011
    #
1012
    setcookie($TBAUTHCOOKIE, "", $timeout, "/", $TBAUTHDOMAIN, 0);
1013
    setcookie($TBLOGINCOOKIE, "", $timeout, "/", $TBAUTHDOMAIN, 0);
1014 1015 1016
    if ($WIKISUPPORT) {
	setcookie($WIKICOOKIENAME, "", $timeout, "/", $TBAUTHDOMAIN, 0);
    }
1017 1018 1019
    if ($BUGDBSUPPORT) {
	setcookie($BUGDBCOOKIENAME, "", $timeout, "/", $TBAUTHDOMAIN, 0);
    }
1020 1021 1022 1023

    return 0;
}

1024
#
Chad Barb's avatar
 
Chad Barb committed
1025
# Simple "nologins" support.
1026 1027
#
function NOLOGINS() {
1028
    global $CHECKLOGIN_NOLOGINS;
1029

Chad Barb's avatar
 
Chad Barb committed
1030 1031
    if ($CHECKLOGIN_NOLOGINS == -1) {
	$CHECKLOGIN_NOLOGINS = TBGetSiteVar("web/nologins");
1032
    }
Chad Barb's avatar
 
Chad Barb committed
1033
	
1034
    return $CHECKLOGIN_NOLOGINS;
1035 1036
}

1037 1038 1039
function LASTWEBLOGIN($uid) {
    global $TBDBNAME;

1040
    $query_result =
1041 1042 1043
        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'");
1044 1045 1046
    
    if (mysql_num_rows($query_result)) {
	$lastrow      = mysql_fetch_array($query_result);
1047
	return $lastrow[weblogin_last];
1048 1049 1050 1051
    }
    return 0;
}

1052 1053
function HASREALACCOUNT($uid) {
    $query_result =
1054 1055
	DBQueryFatal("select status,webonly,wikionly from users ".
		     "where uid='$uid'");
1056 1057 1058 1059 1060

    if (!mysql_num_rows($query_result)) {
	return 0;
    }
    $row = mysql_fetch_array($query_result);
1061 1062 1063
    $status   = $row[0];
    $webonly  = $row[1];
    $wikionly = $row[2];
1064

1065
    if ($webonly || $wikionly ||
1066 1067
	(strcmp($status, TBDB_USERSTATUS_ACTIVE) &&
	 strcmp($status, TBDB_USERSTATUS_FROZEN))) {
1068 1069 1070 1071 1072
	return 0;
    }
    return 1;
}

1073 1074 1075 1076
#
# Beware empty spaces (cookies)!
# 
?>