Skip to content
Snippets Groups Projects
session.php 10.3 KiB
Newer Older
<?
// To use:
// include_once "session.php"
// $mysession = new Session;
// 
// $mysession->loggedin is TRUE if they have logged in
//
// other attributes are :
// username   - the username they logged in with
// fullname   - whatever full name we know for them
// lastseen   - unix timestamp for their previous page access
// data   - var/array for persistant data, commit by calling the 'save' method


// Session management and authentication mechanism.
class Session {
public	$loggedin=FALSE;	// Is this a valid logged in user ?
public	$username='';		// Username
public	$fullname;		// Fullname
public  $email=0;		// Email waiting?
public  $email_forward;		// Email forwarded?
public  $groups  =array();	// users groups
public	$data='';		// Var/array for session persistant data
public	$token='';		// session identifier
public  $logintime='';  // Time which user last gave us credentials
public	$lastseen='';		// Time of last page request
private	$timeout = 2880;	// Idle timeout limit in minutes (session deleted), 2880 == 48 hours
private $anonymous_timeout = 120; // Idle timeout limit for sessions which aren't logged in (set lower to stop the session table getting swamped)
private $secure_timeout = 30;   // Idle timeout limit in minutes (consider session less secure, require reauth for sensitive ops)
private $max_session_length = 11520; // maximum length of a session, 11520 == 8 days
private	$table = "session";	// session storage table (const)
private $datahash='';		// hash of data field



	// Create a new (insecure) session
	private function newsession()
	{
		$token = $this->genSessionID(); 
		$DB->Execute("insert into {$this->table} (hash, lastseen, ip) values (?,NOW(),?)", array($token, $_SERVER['REMOTE_ADDR']));
		setcookie("sucssite_session", $token, NULL, "/", $preferred_hostname);
	
		// delete loggedin cookie if it exists
		setcookie("sucssite_loggedin", FALSE, time(), "/");
		$this->token = $token;
		return;
	}

	public function isSecure()
	{
		global $DB;
		// is user coming from the IP address they were when they logged in?
		if ($detail['ip'] != $_SERVER['REMOTE_ADDR']) {
			return false;
		} elseif (time() > ($this->logintime + $this->secure_timeout)) {
		// has it been too long since we last asked for credentials?
			return false;
		}

	}

	// Public Object constructor
	function __construct()
	{
		global $DB, $preferred_hostname, $baseurl;
		unset($token);

		// if user requests a page via HTTP and claims to be logged in, bump them to HTTPS
		if (!isset($_SERVER['HTTPS']) && (@$_COOKIE['sucssite_loggedin'] == "true")) {
			header("HTTP/1.0 307 Temporary redirect");
			header("Location: https://{$preferred_hostname}{$baseurl}{$_SERVER['PATH_INFO']}"); 
			return;
		}

		// The possible form elements
		$submit = @$_POST['Login'];
		$logout = @$_POST['Logout'];
		$session_user = strtolower(@$_POST['session_user']);
		$session_pass = @$_POST['session_pass'];

		// We havent logged them in yet
		$this->loggedin = FALSE;

		// Time out any old sessions
			"delete from {$this->table} where lastseen < NOW() - '{$this->timeout} minutes'::reltime ".
			"or logintime < NOW() - '{$this->max_session_length} minutes'::reltime ".
			"or (username IS NULL AND lastseen < NOW() - '{$this->anonymous_timeout} minutes'::reltime)"


		// the possible token data passed from a form
		if (isset($_REQUEST['token'])) 
			$token = $_REQUEST['token'];

		// Check if we were handed a specific token identifier
		// Otherwise use the value from the cookie we gave out
		if (!isset($token) && isset($_COOKIE['sucssite_session'])) 
			$token=@$_COOKIE['sucssite_session'];

		if (isset($token)) $this->token = $token;

		// Log them out if they ask
		if ($logout=="Logout") {
			$this->logout();
			return;
		}

		// Okay, so we still dont have a session id
		// so issue a new one and go back to core
		if (!isset($token))  
		{
			$this->newsession();
			return;
		}

		// Is this a login attempt ?
		if ($submit != '' && $session_user != '' && $session_pass != '')
		{
			$this->session_init($session_user, $session_pass);
		}

		// Retrieve session information
		$oldsess=$DB->GetAll("select * from {$this->table} where hash=?", array($this->token));

		if (!$oldsess || count($oldsess) < 1) {
			trigger_error("Session timed out", E_USER_NOTICE);
			$this->newsession();
			return;
		}

		// Extract detail of session for pass-back
		$detail = $oldsess[0];
		$this->data = unserialize((string)$detail['data']);
		$this->lastseen = strtotime($detail['lastseen']);
		$this->logintime = strtotime($detail['logintime']);
		$this->datahash = md5(serialize($this->data));

		// are we actually logged in, fill in more
		if ($detail['username']) {
			// Are we using HTTPS?
			if (!isset($_SERVER['HTTPS'])) {
				trigger_error("Insecure Connection", E_USER_NOTICE);
				$this->loggedin = FALSE;
				return;
			}
			$this->username=$detail['username'];
			$this->fetch_detail($detail['username']);
			$this->loggedin = TRUE;
		}

		// update time stamp
		$DB->Execute( "update {$this->table} set lastseen=NOW() where hash=?", array($this->token));

		// check to see if there any messages stored for this user
		if (isset($this->data['messages'])) {
			global $messages;
			if (is_array($messages)) {
				$messages += $this->data['messages']; 
			} else {
				$messages = $this->data['messages'];
			}
			unset($this->data['messages']);
			$this->save();
		}
	}
	
	// generate a string suitable to be used as a session ID
	private function genSessionID() 
	{
		global $DB;
		$try = 0;

		$tt=date("D M d H:i:s Y");
		$ip = $_SERVER['REMOTE_ADDR'];
		$nonce = rand(); // this should stop session IDs being (easily) guessable by someone with the algorithm

		do {
			$token = md5("$ip$tt$nonce".$try++);
			$old = $DB->GetAll("select hash from {$this->table}  where hash=?", array($token));
		} while ($old);

		return $token;
	}

	// Public function: Store the session data away in the database
	public function save( )
	{
		global $DB;
		$newhash = md5(serialize($this->data));
		if ($newhash == $this->datahash) {
			// no change in data, dont save
			return;
		}

		$DB->Execute("update {$this->table} set data=? where hash=?", array(serialize($this->data),$this->token));

	}

	// Public function: force a logout of the session
	public function logout( )
	{
		global $DB;
		$DB->Execute("delete from {$this->table} where hash=?", array($this->token));
		$this->newsession();
		$this->loggedin = FALSE;
		setcookie("sucssite_loggedin", FALSE, time(), "/");
	}

	// Fill out any extra details we know about the user
	private function fetch_detail( $user )
	{
		if (!($ldap = @ldap_connect("ldap://localhost"))) {
			trigger_error("LDAP connect failed", E_USER_ERROR);
			return FALSE;
		}
		$info = $this->ldap_getuser($ldap, $user);
		if (!$info) return FALSE;

		ldap_close($ldap);

		// Check the user's email status
		$mailstat = @stat("/var/spool/mail/".$user);
		if ($mailstat[size]>0) {
			if ($mailstat[mtime]>$mailstat[atime]) $this->email = 2;
			else $this->email = 1;
		}

		if (file_exists($info['homedirectory'][0]."/.forward")) {
			$forward = file($info['homedirectory'][0]."/.forward");
			$this->email_forward = ereg_replace("\n", "", $forward[0]);
		}

		$this->fullname = $info['cn'][0];
		$this->groups = $info['grouplist'];

	}

	/* check using mod_auth_externals helper 
	private function check_pass($user, $pass)
	{

		if ($fd === FALSE) {
			$this->errormsg = "Auth system error";
			return FALSE;
		}

		fwrite($fd, "$user\n");
		fwrite($fd, "$pass\n");
		$ret = pclose($fd);
		if ($ret == 0) return TRUE;

		$this->autherror = "u='$user' p='$pass' ret=$ret";
		$this->errormsg = "Invalid Username or Password";
		return FALSE;
	}
	*/

	// Get a users full record from ldap
	private function ldap_getuser($ldap, $user)
	{
		// publically bind to find user
		ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
		if (!($bind=@ldap_bind($ldap))) {
			trigger_error("LDAP bind failed", E_USER_ERROR);
			return NULL;
		}
		// find the user
		if (!($search=@ldap_search($ldap, "dc=sucs,dc=org", "(&(uid=$user))"))) {
			trigger_error("LDAP search fail", E_USER_ERROR);
			return NULL;
		}
		$n = ldap_count_entries($ldap, $search);
		if ($n < 1) {
			trigger_error("Username or Password Incorrect", E_USER_WARNING);
			return NULL;
		}
		$info = ldap_get_entries($ldap, $search);

		if (($grpsearch=@ldap_search($ldap, "ou=Group,dc=sucs,dc=org", "memberuid=$user"))) {
			$gn = ldap_count_entries($ldap,$grpsearch);
			$gpile = ldap_get_entries($ldap, $grpsearch);
			$glist=array();
			for ($i=0;$i<$gn;$i++) {
				$glist[  $gpile[$i]['cn'][0] ] = $gpile[$i]['gidnumber'][0];
			}
			$info[0]['grouplist'] = $glist;
		}
		return $info[0];
	}

	/* check using ldap directly */
Denis Walker's avatar
Denis Walker committed
	public function check_pass($user, $pass)
	{
		// Open connection
		if (!($ldap = @ldap_connect("ldap://localhost"))) {
			trigger_error("LDAP connect failed", E_USER_ERROR);
			return FALSE;
		}
		$info = $this->ldap_getuser($ldap, $user);
		if (!$info) return FALSE;

		$real = @ldap_bind($ldap, $info['dn'], $pass);

		ldap_close($ldap);
		if ($real) return TRUE;
		trigger_error("Username or Password Incorrect", E_USER_WARNING);
		return FALSE;

	}

	// Private function: process login form
	private function session_init($user, $pass)
	{
		global $DB, $preferred_hostname;
		// Check that this is a valid session start
		// This prevents replay attacks
		$sess = $DB->GetAll("select * from {$this->table} where hash=? and username is NULL", array($this->token));
		if (!$sess || count($sess)<1) {
			trigger_error("Invalid session, login again.", E_USER_WARNING);
			return;
		}

		if (!$this->check_pass($user, $pass)) return;
		$this->username = $user;

		// the token has likely been used on an insecure connection
		// so generate a new one with the secure flag set
		$oldtoken = $this->token;
		$this->token = $this->genSessionID();
		setcookie("sucssite_session", $this->token, time() + $this->max_session_length * 60, "/", $preferred_hostname, TRUE);

		// set a cookie as a hint that we're logged in
		// this can be checked for to allow redirecting to SSL to get the secure cookie
		setcookie("sucssite_loggedin", "true", time() + $this->max_session_length * 60, "/");
		// Update the session, filling in the blanks
		$DB->Execute("update {$this->table} set hash=?, username=?, logintime='NOW()', lastseen='NOW()', ip=? where hash=?", 
			array($this->token, $this->username, $_SERVER['REMOTE_ADDR'], $oldtoken));

		// Return back to normal session retrieval
	}

} // end of Class