<?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;
}