tbauth.php3 12.5 KB
Newer Older
1 2 3
<?php
#
# Login support: Beware empty spaces (cookies)!
4 5
#

6 7 8 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
# 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);
37
define("CHECKLOGIN_ADMINOFF",		0x20000);
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54

#
# 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);
    }
    $random_bytes = fread($fp, 8);
    fclose($fp);

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

55 56 57
#
# 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
58
# and HASH back, and then check the DB to see if the user is really
59 60 61 62 63 64
# logged in.
# 
function GETLOGIN() {
    if (($uid = GETUID()) == FALSE)
	    return FALSE;

65 66
    $check = CHECKLOGIN($uid);

67
    if ($check & (CHECKLOGIN_LOGGEDIN|CHECKLOGIN_MAYBEVALID))
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
	    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() {
    global $TBNAMECOOKIE, $HTTP_COOKIE_VARS;

    $curname = $HTTP_COOKIE_VARS[$TBNAMECOOKIE];
    if ($curname == NULL)
	    return FALSE;

    return $curname;
}

88
#
89
# Verify a login by sucking a UIDs current hash value out of the database.
90 91 92
# 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.
#
93
# Returns a combination of the CHECKLOGIN values above.
94
#
95
function CHECKLOGIN($uid) {
96 97 98 99 100 101 102 103 104 105
    global $TBAUTHCOOKIE, $HTTP_COOKIE_VARS, $TBAUTHTIMEOUT;
    global $CHECKLOGIN_STATUS, $CHECKLOGIN_UID;

    #
    # If we already figured this out, do not duplicate work!
    #
    if ($CHECKLOGIN_STATUS != CHECKLOGIN_NOSTATUS) {
	return $CHECKLOGIN_STATUS;
    }
    $CHECKLOGIN_UID = $uid;
106 107

    $curhash = $HTTP_COOKIE_VARS[$TBAUTHCOOKIE];
108

109 110 111 112
    #
    # Note that we get multiple rows back because of the group_membership
    # join. No big deal.
    # 
113
    $query_result =
114
	DBQueryFatal("select NOW()>=u.pswd_expires,l.hashkey,l.timeout, ".
115
		     "       status,admin,cvsweb,g.trust,adminoff ".
116 117
		     " from users as u ".
		     "left join login as l on l.uid=u.uid ".
118
		     "left join group_membership as g on g.uid=u.uid ".
119
		     "where u.uid='$uid'");
120

121
    # No such user.
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
    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;
	}
144
	$adminoff= $row[7];
145
    }
146 147 148 149

    #
    # If user exists, but login has no entry, quit now.
    #
150 151 152 153
    if (!$hashkey) {
	$CHECKLOGIN_STATUS = CHECKLOGIN_NOTLOGGEDIN;
	return $CHECKLOGIN_STATUS;
    }
154

155
    #
156
    # Check for frozen account. Might do something interesting later.
157
    #
158
    if (! strcmp($status, TBDB_USERSTATUS_FROZEN)) {
159
	DBQueryFatal("DELETE FROM login WHERE uid='$uid'");
160 161
	$CHECKLOGIN_STATUS = CHECKLOGIN_NOTLOGGEDIN;
	return $CHECKLOGIN_STATUS;
162 163
    }

164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
    #
    # 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. 
181
    # 
182 183 184 185 186
    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.
187
	#
188 189 190 191 192 193 194 195 196 197 198 199 200
	$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;
201 202 203
    }

    #
204
    # Now add in the modifiers.
205
    #
206 207 208 209
    if ($expired)
	$CHECKLOGIN_STATUS |= CHECKLOGIN_PSWDEXPIRED;
    if ($admin)
	$CHECKLOGIN_STATUS |= CHECKLOGIN_ISADMIN;
210 211
    if ($adminoff)
	$CHECKLOGIN_STATUS |= CHECKLOGIN_ADMINOFF;
212 213 214 215 216 217 218 219 220 221 222 223 224 225
    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;

    return $CHECKLOGIN_STATUS;
226 227 228 229
}

#
# This one checks for login, but then dies with an appropriate error
230 231
# message. The modifier allows you to turn off checks for specified
# conditions. 
232
#
233 234 235 236
function LOGGEDINORDIE($uid, $modifier = 0) {
    if ($uid == FALSE)
        USERERROR("You do not appear to be logged in!", 1);
    
237
    $status = CHECKLOGIN($uid);
238 239 240

    switch ($status & CHECKLOGIN_STATUSMASK) {
    case CHECKLOGIN_NOTLOGGEDIN:
241 242
        USERERROR("You do not appear to be logged in!", 1);
        break;
243
    case CHECKLOGIN_TIMEDOUT:
244 245
        USERERROR("Your login has timed out! Please log in again.", 1);
        break;
246 247
    case CHECKLOGIN_MAYBEVALID:
        USERERROR("Your login cannot be verified. Are cookies turned on? ".
248 249
		  "Are you using https? Are you logged in using another ".
		  "browser or another machine?", 1);
250
        break;
251 252 253 254
    case CHECKLOGIN_LOGGEDIN:
	break;
    default:
	TBERROR("LOGGEDINORDIE failed mysteriously", 1);
255
    }
256 257 258 259 260 261 262

    $status = $status & ~$modifier;

    #
    # Check other conditions.
    #
    if ($status & CHECKLOGIN_PSWDEXPIRED)
263 264
        USERERROR("Your password has expired. ".
		  "<a href=moduserinfo.php3>Please change it now!</a>", 1);
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
    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);

    #
    # 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;
281 282 283
}

#
284 285 286
# Is this user an admin type? 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.
287
#
288 289 290 291 292
function ISADMIN($uid) {
    global $CHECKLOGIN_STATUS;
    
    if ($CHECKLOGIN_STATUS == CHECKLOGIN_NOSTATUS)
	TBERROR("ISADMIN: $uid is not logged in!", 1);
293

294
    return (($CHECKLOGIN_STATUS &
295
	     (CHECKLOGIN_LOGGEDIN|CHECKLOGIN_ISADMIN|CHECKLOGIN_ADMINOFF)) ==
296
	    (CHECKLOGIN_LOGGEDIN|CHECKLOGIN_ISADMIN));
297 298 299 300 301 302 303
}

#
# Attempt a login.
# 
function DOLOGIN($uid, $password) {
    global $TBDBNAME, $TBAUTHCOOKIE, $TBAUTHDOMAIN, $TBAUTHTIMEOUT;
304
    global $TBNAMECOOKIE, $TBSECURECOOKIES;
305

306 307 308 309 310
    if (! isset($password) ||
	strcmp($password, "") == 0) {
	return -1;
    }

311 312
    $query_result =
	DBQueryFatal("SELECT usr_pswd FROM users WHERE uid='$uid'");
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329

    #
    # Check password in the database against provided. 
    #
    if ($row = mysql_fetch_row($query_result)) {
        $db_encoding = $row[0];
        $salt = substr($db_encoding, 0, 2);
        if ($salt[0] == $salt[1]) { $salt = $salt[0]; }
        $encoding = crypt("$password", $salt);
        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. 
        #
330
	$timeout = time() + $TBAUTHTIMEOUT;
331
	$hashkey = GENHASH();
332 333 334
        $query_result =
	    DBQueryFatal("SELECT timeout FROM login WHERE uid='$uid'");
	
335
	if (mysql_num_rows($query_result)) {
336 337 338
	    DBQueryFatal("UPDATE login set ".
			 "timeout='$timeout', hashkey='$hashkey' ".
			 "WHERE uid='$uid'");
339 340
	}
	else {
341 342
	    DBQueryFatal("INSERT into login (uid, hashkey, timeout) ".
			 "VALUES ('$uid', '$hashkey', '$timeout')");
343 344
	}

345 346 347
	#
	# Create a last login record.
	#
348 349
	DBQueryFatal("REPLACE into lastlogin (uid, time) ".
		     " VALUES ('$uid', NOW())");
350

351
	#
352 353 354 355 356 357 358 359 360
	# 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. 
361
	#
362
	$timeout = time() + (60 * 60 * 24);
363 364
	setcookie($TBAUTHCOOKIE, $hashkey, $timeout, "/",
                  $TBAUTHDOMAIN, $TBSECURECOOKIES);
365

366 367 368 369 370 371 372 373 374 375 376
	#
	# 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);

377 378 379 380 381
	#
	# Clear adminoff on new logins.
	#
	DBQueryFatal("update users set adminoff=0 where uid='$uid'");

382 383 384 385 386 387 388 389 390
	return 0;
    }
    #
    # No such user
    #
    return -1;
}

#
391
# Log out a UID.
392 393
#
function DOLOGOUT($uid) {
394
    global $TBDBNAME, $TBSECURECOOKIES, $CHECKLOGIN_STATUS;
395

396 397 398 399
    $CHECKLOGIN_STATUS = CHECKLOGIN_NOTLOGGEDIN;

    $query_result =
	DBQueryFatal("SELECT hashkey timeout FROM login WHERE uid='$uid'");
400 401 402 403 404 405 406

    # Not logged in.
    if (($row = mysql_fetch_array($query_result)) == 0) {
	return 0;
    }

    $hashkey = $row[hashkey];
407
    $timeout = time() - 1000000;
408

409
    DBQueryFatal("DELETE FROM login WHERE uid='$uid'");
410 411 412 413

    #
    # Issue a cookie request to delete the cookie. 
    #
414
    setcookie($TBAUTHCOOKIE, "", $timeout, "/", $TBAUTHDOMAIN, 0);
415 416 417 418

    return 0;
}

419 420 421 422
#
# Primitive "nologins" support.
#
function NOLOGINS() {
423
    global $CHECKLOGIN_NOLOGINS;
424

425 426 427 428 429 430 431 432
    #
    # And lastly, check for NOLOGINS! 
    #
    if ($CHECKLOGIN_NOLOGINS >= 0)
	return $CHECKLOGIN_NOLOGINS;

    $query_result =
	DBQueryFatal("SELECT nologins FROM nologins where nologins=1");
433 434 435

    # No entry
    if (($row = mysql_fetch_array($query_result)) == 0) {
436 437 438 439
	$CHECKLOGIN_NOLOGINS = 0;
    }
    else {
	$CHECKLOGIN_NOLOGINS = $row[nologins];
440 441
    }

442
    return $CHECKLOGIN_NOLOGINS;
443 444
}

445 446 447
function LASTWEBLOGIN($uid) {
    global $TBDBNAME;

448 449
    $query_result =
	DBQueryFatal("SELECT time from lastlogin where uid='$uid'");
450 451 452 453 454 455 456 457
    
    if (mysql_num_rows($query_result)) {
	$lastrow      = mysql_fetch_array($query_result);
	return $lastrow[time];
    }
    return 0;
}

458 459 460 461
#
# Beware empty spaces (cookies)!
# 
?>