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 $printbalance; // printer balance 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() { global $DB, $preferred_hostname, $dbname; $token = $this->genSessionID(); $DB->Execute("insert into {$this->table} (hash, lastseen, ip) values (?,NOW(),?)", array($token, $_SERVER['REMOTE_ADDR'])); setcookie($dbname . "_session", $token, NULL, "/", $preferred_hostname); // delete loggedin cookie if it exists setcookie($dbname . "_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, $dbname; unset($token); // if user requests a page via HTTP and claims to be logged in, bump them to HTTPS if (!isset($_SERVER['HTTPS']) && (@$_COOKIE[$dbname . '_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 $DB->Execute( "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[$dbname . '_session'])) $token = @$_COOKIE[$dbname . '_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, $dbname; $DB->Execute("delete from {$this->table} where hash=?", array($this->token)); $this->newsession(); $this->loggedin = FALSE; setcookie($dbname . "_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; }*/ // a sure-fire way to check to see if the user has any unread email // the bash script returns 0 for no and 1 for yes, takes one arg, username $this->email = shell_exec("../plugins/sucsunreadmail $user"); if (file_exists($info['homedirectory'][0] . "/.forward") && ! is_link($info['homedirectory'][0] . "/.forward")) { $forward = file($info['homedirectory'][0] . "/.forward"); $this->email_forward = preg_replace("/\n/", "", $forward[0]); } $this->fullname = $info['cn'][0]; $this->groups = $info['grouplist']; $db = new SQLite3('/etc/pykota/pykota.db'); $result = $db->query("SELECT balance FROM users WHERE username='$user';"); $this->printbalance = $result->fetchArray()[0]; } /* 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 */ 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, $dbname; // 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($dbname . "_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($dbname . "_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