👁 查看:license.php
路径:/home/boguraj1/public_html/ipsms/includes/license.php
大小:7.85 KB · 修改:2026-06-17 01:07:02 · 权限:0644 · 可写
<?php
/**
 * License verification middleware.
 * Include config/license.php BEFORE calling checkLicense().
 */

// Pages that are allowed without a valid license
const LICENSE_BYPASS_PAGES = ['license_activate.php', 'install.php', 'logout.php'];

/**
 * Main entry point — call once per page (from config/db.php).
 * Exits with an error page if the license is not valid.
 */
function checkLicense(): void
{
    // Skip on exempt pages
    if (in_array(basename($_SERVER['PHP_SELF'] ?? ''), LICENSE_BYPASS_PAGES, true)) {
        return;
    }
    // Skip if caller explicitly opted out (e.g., CLI scripts)
    if (defined('SKIP_LICENSE_CHECK') && SKIP_LICENSE_CHECK) {
        return;
    }

    $domain      = _normalizeDomain($_SERVER['HTTP_HOST'] ?? 'localhost');
    $license_key = defined('LICENSE_KEY') ? LICENSE_KEY : '';

    // Nothing configured yet → send to activation page
    if ($license_key === '' || $license_key === 'HXZBT-ZRCE5-93C8Z-CGPVH') {
        _licenseError(
            'License Key Not Set',
            'Please enter your license key to activate this software.',
            true
        );
    }

    // ---- Try cache first ----
    $cache = _readCache();

    if ($cache !== null && $cache['domain'] === $domain) {
        $age = time() - $cache['time'];

        if ($cache['status'] === 'valid' && $age < LICENSE_CACHE_TTL) {
            return; // Cache is fresh and valid
        }

        // Cache is stale → try server; fall back to grace period if unreachable
        $result = _queryServer($domain, $license_key);

        if ($result === null) {
            // Server unreachable
            if ($cache['status'] === 'valid' && $age < LICENSE_GRACE_TTL) {
                return; // Grace period: let them work
            }
            _licenseError(
                'License Server Unreachable',
                'Cannot verify your license. Check your internet connection or contact tmitstore.com support.'
            );
        }

        _writeCache($domain, $result['status'], $result['message'] ?? '');

        if ($result['status'] === 'valid') return;
        _licenseError('License Invalid', $result['message'] ?? 'License verification failed.');
    }

    // ---- No usable cache — must query server ----
    $result = _queryServer($domain, $license_key);

    if ($result === null) {
        _licenseError(
            'License Server Unreachable',
            'Cannot contact the license server. Ensure this server has outbound internet access.'
        );
    }

    _writeCache($domain, $result['status'], $result['message'] ?? '');

    if ($result['status'] === 'valid') return;
    _licenseError('License Invalid', $result['message'] ?? 'This license is not valid for this domain.');
}


// ============================================================
// Internal helpers (prefixed with _ to avoid global collisions)
// ============================================================

/** Normalize host: lowercase, strip www., strip port. */
function _normalizeDomain(string $host): string
{
    $host = strtolower(trim($host));
    $host = preg_replace('/^www\./', '', $host);
    $host = explode(':', $host)[0];
    return $host;
}

/** Query the remote license server. Returns decoded array or null on failure. */
function _queryServer(string $domain, string $license_key): ?array
{
    $timestamp = time();
    $sig = hash_hmac(
        'sha256',
        $domain . $license_key . LICENSE_PRODUCT . $timestamp,
        LICENSE_HMAC_SECRET
    );

    $payload = json_encode([
        'domain'      => $domain,
        'license_key' => $license_key,
        'product'     => LICENSE_PRODUCT,
        'timestamp'   => $timestamp,
        'sig'         => $sig,
    ]);

    $ctx = stream_context_create([
        'http' => [
            'method'          => 'POST',
            'header'          => "Content-Type: application/json\r\nContent-Length: " . strlen($payload),
            'content'         => $payload,
            'timeout'         => 8,
            'ignore_errors'   => true,
        ],
        'ssl'  => ['verify_peer' => true, 'verify_peer_name' => true],
    ]);

    $raw = @file_get_contents(LICENSE_SERVER_URL, false, $ctx);
    if ($raw === false) return null;

    $data = @json_decode($raw, true);
    if (!is_array($data)) return null;

    // Verify server's response signature to prevent spoofing
    if (($data['status'] ?? '') === 'valid') {
        $expires_at   = $data['expires_at'] ?? 'lifetime';
        $expected_sig = hash_hmac(
            'sha256',
            'valid' . $domain . $expires_at . LICENSE_PRODUCT,
            LICENSE_HMAC_SECRET
        );
        if (empty($data['res_sig']) || !hash_equals($expected_sig, $data['res_sig'])) {
            return ['status' => 'tampered', 'message' => 'Server response signature is invalid.'];
        }
    }

    return $data;
}

/** Read cached license result. Returns null if cache missing/corrupt. */
function _readCache(): ?array
{
    $file = LICENSE_CACHE_FILE;
    if (!file_exists($file)) return null;

    $raw = @file_get_contents($file);
    if ($raw === false) return null;

    // Decode: base64 then JSON
    $data = @json_decode(base64_decode($raw), true);
    if (!is_array($data) || !isset($data['domain'], $data['status'], $data['time'])) {
        return null;
    }
    return $data;
}

/** Write license result to cache file. */
function _writeCache(string $domain, string $status, string $message): void
{
    $data = base64_encode(json_encode([
        'domain'  => $domain,
        'status'  => $status,
        'message' => $message,
        'time'    => time(),
    ]));
    @file_put_contents(LICENSE_CACHE_FILE, $data, LOCK_EX);
}

/** Show a styled license error page and exit. */
function _licenseError(string $title, string $message, bool $show_activate_link = false): void
{
    $domain = _normalizeDomain($_SERVER['HTTP_HOST'] ?? 'localhost');
    http_response_code(403);
    ?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>License Error — ISP Stock Manager</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css" rel="stylesheet">
    <style>
        body { background: linear-gradient(135deg, #dc3545 0%, #a71d2a 100%); min-height: 100vh;
               display: flex; align-items: center; justify-content: center; }
        .lic-card { max-width: 540px; width: 100%; border-radius: 1rem; }
        .lic-icon { font-size: 4rem; color: #dc3545; }
        code { background: #f8f9fa; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; }
    </style>
</head>
<body>
<div class="lic-card card shadow-lg p-4 mx-3">
    <div class="text-center mb-4">
        <i class="bi bi-shield-lock lic-icon"></i>
        <h3 class="fw-bold mt-2 text-danger"><?= htmlspecialchars($title) ?></h3>
    </div>

    <div class="alert alert-danger">
        <i class="bi bi-exclamation-triangle-fill me-2"></i>
        <?= htmlspecialchars($message) ?>
    </div>

    <div class="card bg-light border-0 mb-3">
        <div class="card-body small">
            <strong>Domain:</strong> <code><?= htmlspecialchars($domain) ?></code><br>
            <strong>Product:</strong> <code>ISP Stock Manager</code><br>
            <strong>Support:</strong>
            <a href="https://tmitstore.com" target="_blank">tmitstore.com</a>
        </div>
    </div>

    <?php if ($show_activate_link): ?>
    <div class="d-grid">
        <a href="license_activate.php" class="btn btn-primary fw-semibold">
            <i class="bi bi-key me-1"></i> Activate License
        </a>
    </div>
    <?php else: ?>
    <p class="text-muted small text-center mb-0">
        Contact <strong>tmitstore.com</strong> to purchase or renew your license.
    </p>
    <?php endif; ?>
</div>
</body>
</html>
    <?php
    exit;
}