Skip to content
Snippets Groups Projects
session.php 12.6 KiB
Newer Older
  • Learn to ignore specific revisions
  • <?
    // 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 $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")) {
                $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