<?php
/*
 * PUQ wrapper for net2ftp's index.php.
 *
 * At install time we rename upstream `index.php` → `_n2f_main.php` and put
 * this wrapper in its place. The wrapper enforces SSO-only access:
 *
 *   • $_SESSION['puq_n2f_sso'] set by _puq_launch.php → forward to net2ftp
 *   • Otherwise (direct GET, manual POST, scanner probes) → landing page
 *
 * Manual login POST is BLOCKED — even if someone hand-crafts a POST with
 * ftpserver/username/password, no SSO session = no access. This is the
 * "lock down all login paths" pattern, mirroring FileGator's PuqHostingAuth.
 *
 * Net2ftp's internal navigation after login generates more requests at
 * index.php — those carry the PHPSESSID cookie (puq_n2f_sso still set),
 * so they pass through transparently.
 *
 * Why a wrapper vs editing net2ftp's index.php: keeps upstream files
 * unmodified — re-install just overwrites _n2f_main.php cleanly.
 */

if (session_status() !== PHP_SESSION_ACTIVE) {
    session_start();
}

// Auth model — we maintain our OWN session flag because net2ftp doesn't
// expose a reliable "is-logged-in" key (it keeps creds in cookies, not
// in PHP session). Flow:
//
//   1. _puq_launch.php sets puq_n2f_sso=true, puq_n2f_root=<docroot>
//   2. Wrapper sees POST + sso marker → consumes marker, promotes to
//      puq_n2f_auth=true → forwards into net2ftp
//   3. Subsequent navigation has puq_n2f_auth → wrapper forwards if
//      the `directory` parameter stays inside puq_n2f_root
//   4. Any attempt to navigate ABOVE root (`..`, manual path edit,
//      net2ftp's "up" button climbing past root) → session destroyed
//      + landing page (force re-login via WHMCS)

// ---------- Path normalization helper ---------------------------------------
// Resolves `..` and `.`, collapses repeated slashes, returns an absolute
// path without trailing slash. Used to compare a requested directory
// against the session root. Pure string operation — does NOT touch FS.
$normalizePath = static function (string $p): string {
    if ($p === '') return '';
    $isAbs = $p[0] === '/';
    $parts = array_filter(explode('/', $p), static fn($x) => $x !== '');
    $stack = [];
    foreach ($parts as $part) {
        if ($part === '.') continue;
        if ($part === '..') { array_pop($stack); continue; }
        $stack[] = $part;
    }
    $joined = implode('/', $stack);
    return ($isAbs ? '/' : '') . $joined;
};

// ---------- Phase 1: auth gating ------------------------------------------
$method      = $_SERVER['REQUEST_METHOD'] ?? 'GET';
$state       = (string) ($_POST['state'] ?? $_GET['state'] ?? '');
$hasSsoMark  = !empty($_SESSION['puq_n2f_sso']);
$hasAuth     = !empty($_SESSION['puq_n2f_auth']);

$allow = false;
if ($method === 'POST' && $hasSsoMark) {
    unset($_SESSION['puq_n2f_sso']);
    $_SESSION['puq_n2f_auth'] = true;
    $allow = true;
} elseif ($hasAuth) {
    if ($state !== '' && $state !== 'login' && $state !== 'main') {
        $allow = true;
    } elseif ($state === 'main' && !empty($_POST['ftpserver'])) {
        $allow = true;
    }
}

if (!$allow) {
    @error_log(sprintf(
        '[PuqHostingN2F] blocked auth: method=%s state=%s sso=%d auth=%d ip=%s',
        $method, $state, (int) $hasSsoMark, (int) $hasAuth,
        $_SERVER['REMOTE_ADDR'] ?? '?'
    ));
    goto SHOW_LANDING;
}

// ---------- Phase 2: directory containment --------------------------------
// If the request includes a `directory` param AND we have a session root,
// the requested directory must be exactly the root OR a child of it.
// Otherwise → kick the user out: destroy session, show landing.
$requestedDir = (string) ($_POST['directory'] ?? $_GET['directory'] ?? '');
$root         = (string) ($_SESSION['puq_n2f_root'] ?? '');
if ($requestedDir !== '' && $root !== '') {
    $reqNorm  = $normalizePath($requestedDir);
    $rootNorm = rtrim($normalizePath($root), '/');
    if ($rootNorm === '') $rootNorm = '/';
    // Containment: exactly the root, or a child of it. Guard the root='/'
    // edge — `strpos($reqNorm, '/') === 0` would match ANY absolute path and
    // void the chroot. A real session root is always the user's home dir, so
    // require rootNorm !== '/' for the prefix match (root='/' then only allows
    // the root itself, never arbitrary children).
    $inside = ($reqNorm === $rootNorm)
           || ($rootNorm !== '/' && strpos($reqNorm, $rootNorm . '/') === 0);
    if (!$inside) {
        @error_log(sprintf(
            '[PuqHostingN2F] escape attempt: requested=%s normalized=%s root=%s ip=%s — destroying session',
            $requestedDir, $reqNorm, $rootNorm,
            $_SERVER['REMOTE_ADDR'] ?? '?'
        ));
        $_SESSION = [];
        if (ini_get('session.use_cookies')) {
            $p = session_get_cookie_params();
            setcookie(session_name(), '', time() - 42000,
                $p['path'], $p['domain'], $p['secure'], $p['httponly']);
        }
        @session_destroy();
        goto SHOW_LANDING;
    }
}

// Allowed → forward to the real net2ftp index.
require __DIR__ . '/_n2f_main.php';
return;

SHOW_LANDING:
$landing = __DIR__ . '/_puq_landing.html';
if (is_file($landing)) {
    header('Content-Type: text/html; charset=utf-8');
    header('Cache-Control: no-store');
    readfile($landing);
    exit;
}
http_response_code(403);
echo 'File Manager — open via WHMCS client area.';
exit;
