<?php
if (MING != "merciless") require ('index.php');
else {
/*
Currently, Planet Mongo only has very limited user support.

New users must be entered manually by editing the "users.php" file in the config directory.

However, the user support is still very, very flexible and versatile, just like the rest of Planet Mongo.

You can password protect ANYTHING, from a content page to a single news post. Just put in
<!--[[ user : protect ]]--> at the top of the page (or before whatever you want protected).

TODO: Security issue: If there's a warning / error, it might end up not properly calling user : protect;

*/

$user = FALSE; // By default, no user is logged in.

////////////////////////////////
// How long should a user stay logged in after inactivity?
// In seconds. Precision is limited by $user_timeout_precision
// Default: 30 minutes.
$user_timeout_seconds = 30*60; 
////////////////////////////////


////////////////////////////////
// How precise should the timeout mechanism be?
// More precision means (slightly) slower log-in.
// In seconds. Default, 5 minutes.
$user_timeout_precision = 5*60; 
////////////////////////////////

////////////////////////////////
// Setting this to equal true will keep a users log-in alive 
// as long as the user has been active. The user will be 
// timed out if the user has spent more than
// $user_timeout_seconds since the last visit to a secure page
// (or a page with a call to "user : keep_alive").
// If it is false, the user will have to log in again after 
// $user_timeout_seconds no matter what.
$user_reset_timeout_with_activity = TRUE;

////////////////////////////////
// If this is set to TRUE, Planet Mongo will immediately stop execution if an invalid log in was supplied, 
// possibly producing messy, improper HTML. If it set to FALSE, it will attempt to finish outputting the 
// rest of the page before exploding dramatically.
$user_be_extra_secure_with_protected_page = FALSE;
////////////////////////////////



////////////////////////////////
// If you want a user to be allowed to change 
// IPs mid-login session, change this to FALSE.
$user_use_ip_in_authentipack = TRUE;
////////////////////////////////


////////////////////////////////
// The HTML for the log in prompt.
$user_html_login = '
<h2>Please log in to access this page:</h2>
<form action="#" class="form-login" method="post">
	Username: <input type="text" name="userusername" value="" /> <br />
	Password: <input type="password" name="userpassword" value="" /> <br />
	<input type="submit" style="display: inline; margin-right: 10px; padding: 3px" value="Log in" />
</form>
';
////////////////////////////////


/********************** Not very useful settings are contained here. *****************************/

////////////////////////////////
// The cookie used for the authentipack
$user_authentipack_cookie_name = 'pmuserauthentipack';
////////////////////////////////


$user_authentipack_divider = '|#$|';

$user_username_get_variable = 'userusername';
$user_username_post_variable = 'userusername';
$user_password_get_variable = 'userpassword';
$user_password_post_variable = 'userpassword';
$user_logoff_post_variable = 'logoff';
$user_logoff_get_variable = 'logoff';


/*************************************************************************************************/


/********************** Initial variable values. Do not change these. ****************************/
$user = FALSE; // Nobody is logged in by default.
$user_count = 0;
$user_list = Array();

/*************************************************************************************************/

class User {
	var $metadata = Array();
	var $username; // The User's username
	var $password; // The User's password, as an MD5 hash
	var $logged_in; // TRUE if the user is logged in, FALSE if not
	var $uid; // A number representing the user's User ID
	function User($name, $password, $metadata = FALSE, $uid = FALSE, $plaintext = TRUE) {
		$this->password = $plaintext ? md5($password) : $password;
		if ($uid == FALSE) {
			global $user_count;
			$uid = $user_count;
			$user_count++;
		}
		$this->uid = $uid;
		$this->username = $name;
		$this->logged_in = FALSE;	
	}
	function check_password($password) {
		return $this->password == $password;
	}
	function is_user($username) {
		return $this->username == $username;
	}
	function set($name, $value = TRUE) {
		$this->metadata[$name] = $value;
	}
	function get($name) {
		if(array_key_exists($name, $this->metadata)!== TRUE) return FALSE;
		return $this->metadata[$name];
	}
	function generate_authentipack ($ip, $time){
		global $main_salt, $user_authentipack_divider, $user_use_ip_in_authentipack;
		$encoded_part = "$main_salt|$time|".$this->password;
		if($user_use_ip_in_authentipack) $encoded_part = $encoded_part."|$ip";
		$authentipack = $this->username . $user_authentipack_divider . md5($encoded_part);
		return $authentipack;
	}
	function to_array() {
		return Array(
			'username' => $this->username,
			'password' => $this->password,
			'logged_in' => $this->logged_in,
			'meta' => $this->metadata
			);			
	}
}

// user_check_authentipack checks to see if $authentipack is still valid.
// It returns true if it is, and false if it has timed out (or there was a 
// password or IP address change).
// It does this by essentially looping through each of the authentipacks that 
// could have been issued for this user in the last X seconds, where X is equal 
// to the $user_timeout_seconds option, and checking each authentipack with 
// the authentipack offered to see if any line up.
function user_check_authentipack ($ip, $authentipack) {
	global $user_timeout_precision, $user_timeout_seconds, $user_authentipack_divider;
	$split = explode($user_authentipack_divider, $authentipack, 2);
	$username = $split[0]; // TODO: make the authentipack divider much simpler, and then make it 
	// start counting from the right instead of the left, to prevent issues with the divider being 
	// in the user name
	// TODO number 2: Maybe I should add a UID, since that is a pretty common sort of feature.
	// It might make things like here much less issued.
	$user = user_get($username);
	$current_tu = user_get_tu(time()); // $current_tu is the current time in Time Units
	$timeout_tu = user_get_tu($user_timeout_seconds); // $timeout_tu is the number of TUs until a timeout occurs
	$earliest_login_time_tu = $current_tu - $timeout_tu; // This is now equal to the earliest permissible log in time.
	for($check_time = $earliest_login_time_tu; $check_time <= $current_tu; $check_time++){
		$authentipack_for_this_time = $user->generate_authentipack($ip, $check_time);
		if($authentipack == $authentipack_for_this_time) return TRUE;
	}
	return FALSE;
}


// user_generate_authentipack generates an authentipack for a given username/ip/password combination at a certain time
/*function user_generate_authentipack ($ip, $username, $password, $time) {
	global $main_salt, $user_authentipack_divider, $user_use_ip_in_authentipack;
	$encoded_part = $user_use_ip_in_authentipack ? "$main_salt|$ip|$password|$time" : "$main_salt|$password|$time";
	$authentipack = "$username$user_authentipack_divider" . md5($encoded_part);
	return $authentipack;
}*/


// This will issue the client a cookie containing an authentipack for a given user.
// It assumes that the user has been properly validated.
function user_issue_authentipack_cookie($user) {
	global $user_authentipack_cookie_name;
	$ip = $_SERVER['REMOTE_ADDR'];
	$authentipack = $user->generate_authentipack($ip, user_get_tu(time()));
	setcookie($user_authentipack_cookie_name, $authentipack, 0); // Expires when the browser is closed.
	return $authentipack;
}

// user_get_tu converts $time (in seconds) to TUs (Time Units).
// A TU is a unit of time measurement, such that 1 TU = $user_timeout_precision seconds
function user_get_tu($time) {
	return floor($time / $GLOBALS['user_timeout_precision']);
}
function user_is_logged_on() {
	global $user;
	return $user !== FALSE;
}
function user_create($username, $password, $metadata = FALSE){
	$user = new User($username, $password);
	if($metadata != FALSE) {
		foreach($metadata as $key=>$value) $user->set($key, $value);
	}
	user_add($user);
	return $user;
}
function user_add ($user) {
	global $user_list;
	$user_list[] = $user;
}
function user_get ($username) {
	global $user_list;
	foreach ($user_list as $user){
		if($user->is_user($username)) return $user;
	}
	return FALSE;
}
function user_accept ($the_user, $mesg='') {
	global $user;
	if(strlen($mesg)>0)	outblurt($mesg);
	$user = $the_user;
	user_issue_authentipack_cookie($the_user);
}
function user_reject ($mesg='') {
	global $user_invalid_login_message, $user_be_extra_secure_with_protected_page, $user_html_login, $user;
	$user = FALSE;
	if(strlen($mesg)>0)	outblurt($mesg);
	outblurt($user_html_login);
	if($user_be_extra_secure_with_protected_page!==FALSE) main_stop_all();
	else main_stop_outblurt();
	return FALSE;
}
function user_check_credentials($user, $condition) {
	//TODO: add better error detection here for poorly constructed > or < thingamajiggers.
	$split = explode(' ', $condition, 3);
	$property_name = trim($split[0]);
	$lv = $user->get($property_name);
	if($lv === FALSE) return FALSE;
	$lv = trim($lv);
	$rv = trim($split[2]);
	$ret = FALSE;
	$op = trim($split[1]);
	if($op=='==') $ret = $lv == $rv;
	elseif($op=='!=') $ret = $lv != $rv;
	else {
		$lv = floatval($lv);
		$rv = floatval($rv);
		if($op=='>=') $ret = $lv >= $rv;
		elseif($op=='<=') $ret = $lv <= $rv;
		elseif($op=='>') $ret = $lv > $rv;
		elseif($op=='<') $ret = $lv < $rv;
		else {
			outblurt("Error [user_00]: Invalid credential specified.");
		}
	}
	return $ret;
}
function user_autoexecute () {
	function user_first_reject ($arg = '') {
		global $user, $user_rejection_reason, $user_authentipack_cookie_name;
		$user = FALSE;
		$user_rejection_reason = $arg;
		setcookie($user_authentipack_cookie_name, '', 1); // Clears cookie
		return FALSE;
	}
	global $user_logoff_get_variable, $user_logoff_post_variable;
	// First, we check to see if the user is trying to log off
	if (strlen($_GET[$user_logoff_get_variable])>0 || strlen($_POST[$user_logoff_post_variable])>0)
		return user_first_reject('Logged off successfully!');
	
	// Now, we check to see if the user is trying to log on.
	global $user_username_get_variable,  $user_username_post_variable, $user;
	$get_username = $_GET[$user_username_get_variable];
	$post_username = $_POST[$user_username_post_variable];
	$username = trim((strlen($post_username) > 0) ? $post_username : $get_username);
	if(strlen($username)>0){ // This appears to be a log-in attempt.
		global $user_password_get_variable,  $user_password_post_variable, $user_log_in_greeting;
		$get_password = $_GET[$user_password_get_variable];
		$post_password = $_POST[$user_password_post_variable];
		$password = md5(trim((strlen($post_password) > 0) ? $post_password : $get_password));
		$the_user = user_get($username);
		if($the_user) { // If the user exists...
			if($the_user->check_password($password)) user_accept($the_user); // The password is good
			else return user_first_reject('Sorry, password does not match username. Please try again.');
		} else return user_first_reject('Sorry, we do not have that username in our system.'); # TODO: changetocust
		//$valid = array_key_exists($username, $users) &&  // Checks if a user exists, and then checks 
		//			($users[$username]['password'] == $password); // the password.
		//if($valid) user_accept($username);
		//else return user_first_reject('Sorry, incorrect username or password. Please try again.'); # TODO: change this to customizable variable
	} else { // Okay, they aren't trying to log-in.
		global $user_authentipack_cookie_name;
		// First, see if they have an authentipack.
		if(isset($_COOKIE[$user_authentipack_cookie_name])){
			global $user_authentipack_divider; 
			// They do. Let's get it and try to figure out if it is still valid.
			$authentipack = $_COOKIE[$user_authentipack_cookie_name];
			$ip = $_SERVER['REMOTE_ADDR'];
			$split = explode($user_authentipack_divider, $authentipack, 2);
			$username = $split[0]; // TODO: make the authentipack divider much simpler, and then make it 
			if(user_check_authentipack($ip, $authentipack)) user_accept(user_get($username)); // Valid
			else return user_first_reject('Sorry, but your log in has timed out. Please log in again.'); // Invalid authentipack
		} else return user_first_reject(); // The user does not have an authentipack, is not trying to log in. Thus, 
		// they are completely rejectable.
	}
}

// The <!--[[ user : protect ]]--> command / function is called at the top of a content page (or any capable file)
// in order to make the visitor log-in as a user in order to see the rest of that file. Without any arguments, it 
// will disallow anybody who isn't logged in, and instead display a login prompt. With an argument, it will reject 
// any user that does not have the proper credentials. For example: <!--[[ user : protect : security_level < 10 ]]-->
// will prevent all users from seeing a particular page except for those with a security level less than 10. Six 
// comparisons are available: == != < > <= >=

function user_protect_doaction( $arg = '' ) {
	if(user_is_logged_on()) {
		if($arg = '' && !(user_check_credentials($user, $arg))) {
			user_reject('Although you are correctly logged in, your account does not have the permissions necessary 
			to view this section.');
			return FALSE;
		} else return TRUE;
	} else {
		global $user_rejection_reason;
		user_reject($user_rejection_reason);
		return FALSE;
	}
	
}

// This registures all the configuration values in config/users.php
// It is executed here simply because users.php calls a function defined above.
// By calling user_config(), we ensure that user_config() is executed after
// necessary functions are defined.
user_config();


// This does all the cookie stuff before anything is sent
user_autoexecute(); 

}
?>
