<?php

class security_wrapper {

    public $user;
    public $homedir;

    private $logindb;

    function __construct() {
        session_start();
        $existed = file_exists("/docker/whp/sql/login.db");
        $this->logindb = new SQLite3('/docker/whp/sql/login.db');
        $this->logindb->busyTimeout(6000);
        if (!$existed) {
            $this->logindb->query('CREATE TABLE nonces(nonce STRING, user STRING, ip STRING, last_time String, persist INTEGER DEFAULT 0)');
        } else {
            // Idempotent: add persist column to existing deployments that pre-date Stay-Logged-In.
            $hasPersist = false;
            $cols = $this->logindb->query('PRAGMA table_info(nonces)');
            if ($cols) {
                while ($col = $cols->fetchArray(SQLITE3_ASSOC)) {
                    if ($col['name'] === 'persist') { $hasPersist = true; break; }
                }
            }
            if (!$hasPersist) {
                @$this->logindb->query('ALTER TABLE nonces ADD COLUMN persist INTEGER DEFAULT 0');
            }
        }
    }

    function pam_login($user,$password) {
        $error = '';
        $result = pam_auth( $user, $password, $error );
        if ( $result ) {
            $this->user = $user;
        }
        return $result;
    }

    function get_home_dir($user) {
        // Sanitize user input to prevent command injection
        $user = escapeshellarg($user);
        $homedir = trim(shell_exec("cat /etc/passwd |grep $user |awk -F ':' {'print \$6'}|head -1"));
        
        // If homedir is empty or not found, try to construct it based on username
        if (empty($homedir)) {
            // For root user, use /root
            if ($user === "'root'" || $user === "root") {
                $homedir = '/root';
            } else {
                // For regular users in WHP, use /docker/users/username pattern
                // Remove quotes from escapeshellarg for path construction
                $clean_user = trim($user, "'");
                $homedir = '/docker/users/' . $clean_user;
            }
            error_log("Warning: Could not find home directory for user $user in /etc/passwd, using fallback: $homedir");
        }
        
        $this->homedir = $homedir;
        return $homedir;
    }

    /**
     * Validate a nonce and refresh its window. Caller passes $maxAge in seconds;
     * default 900 (15 min) preserves the historical idle-timeout behavior. When
     * the user logged in with "Stay Logged In", callers pass a larger value
     * (e.g., 7 days) so the nonce stays valid across long idle periods.
     */
    function check_nonce($nonce, $ip, $maxAge = 900) {
        $stmt = $this->logindb->prepare('SELECT user,ip,last_time,persist FROM nonces WHERE nonce=:n');
        $stmt->bindValue(':n', $nonce, SQLITE3_TEXT);
        $result = $stmt->execute();
        if ( $result != FALSE ) {
            $row = $result->fetchArray(SQLITE3_ASSOC);
            if ( $row && $row['ip'] == $ip ) {
                $time_diff = time() - $row['last_time'];
                if ( $time_diff > $maxAge ) {
                    return false;
                } else {
                    $dlte = $this->logindb->prepare('DELETE FROM nonces WHERE nonce=:n');
                    $dlte->bindValue(':n', $nonce, SQLITE3_TEXT);
                    $dlte->execute();
                    return $row;
                }
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

    /**
     * Create a fresh nonce row. $persist=true marks the row as a Stay-Logged-In
     * session so the UI / Active Sessions page can label it.
     */
    function create_nonce($user, $ip, $salt, $persist = false) {
        $last_time = time();
        $nonce = hash("sha1", "$salt.$user.$ip.$last_time");
        $stmt = $this->logindb->prepare("INSERT INTO nonces (nonce, user, ip, last_time, persist) VALUES (:nonce, :user, :ip, :last_time, :persist)");
        $stmt->bindValue(':nonce', $nonce, SQLITE3_TEXT);
        $stmt->bindValue(':user', $user, SQLITE3_TEXT);
        $stmt->bindValue(':ip', $ip, SQLITE3_TEXT);
        $stmt->bindValue(':last_time', $last_time, SQLITE3_INTEGER);
        $stmt->bindValue(':persist', $persist ? 1 : 0, SQLITE3_INTEGER);
        $stmt->execute();
        return $nonce;
    }

    /**
     * Remove a single nonce row. Used by logout and the Active Sessions
     * "terminate" action.
     */
    function delete_nonce($nonce) {
        $stmt = $this->logindb->prepare('DELETE FROM nonces WHERE nonce=:n');
        $stmt->bindValue(':n', $nonce, SQLITE3_TEXT);
        $stmt->execute();
    }

    /**
     * List nonce rows that still represent live sessions. A row counts as live
     * when its last_time is within the lifetime appropriate for its persist
     * flag — 7 days for Stay-Logged-In rows, 900 seconds for standard rows.
     * Expired rows are excluded so the UI doesn't offer terminate buttons for
     * sessions whose next request would already force re-login.
     *
     * Pass $user=null for the root view (every account); otherwise scopes to
     * that user. Pass $include_expired=true to skip the lifetime filter (for
     * an admin "show all" toggle if we ever need it).
     */
    function list_nonces($user = null, $include_expired = false) {
        $now = time();
        $persistThreshold  = $now - (7 * 86400);
        $standardThreshold = $now - 900;

        $select = 'SELECT nonce, user, ip, last_time, persist FROM nonces';
        $where  = [];
        $bind   = [];

        if ($user !== null) {
            $where[] = 'user = :u';
            $bind[':u'] = [$user, SQLITE3_TEXT];
        }
        if (!$include_expired) {
            $where[] = '((persist = 1 AND last_time >= :pt) OR (persist = 0 AND last_time >= :st))';
            $bind[':pt'] = [$persistThreshold,  SQLITE3_INTEGER];
            $bind[':st'] = [$standardThreshold, SQLITE3_INTEGER];
        }

        $sql = $select;
        if (!empty($where)) {
            $sql .= ' WHERE ' . implode(' AND ', $where);
        }
        $sql .= ' ORDER BY last_time DESC';

        $stmt = $this->logindb->prepare($sql);
        foreach ($bind as $key => $pair) {
            $stmt->bindValue($key, $pair[0], $pair[1]);
        }
        $result = $stmt->execute();

        $rows = [];
        if ($result) {
            while ($r = $result->fetchArray(SQLITE3_ASSOC)) {
                $rows[] = $r;
            }
        }
        return $rows;
    }

    /**
     * Bulk-terminate every active session for a given user. Root uses this when
     * a user reports a compromised account.
     */
    function delete_nonces_by_user($user) {
        $stmt = $this->logindb->prepare('DELETE FROM nonces WHERE user=:u');
        $stmt->bindValue(':u', $user, SQLITE3_TEXT);
        $stmt->execute();
    }

    /**
     * Look up a single nonce row (used by the terminate API to authorize the
     * caller before deleting).
     */
    function get_nonce($nonce) {
        $stmt = $this->logindb->prepare('SELECT nonce, user, ip, last_time, persist FROM nonces WHERE nonce=:n');
        $stmt->bindValue(':n', $nonce, SQLITE3_TEXT);
        $result = $stmt->execute();
        if ($result) {
            $row = $result->fetchArray(SQLITE3_ASSOC);
            return $row ?: null;
        }
        return null;
    }

    function __destruct() {
        $this->logindb->close();
    }

}

