bd06fc7fbf
- Allows customization through .htaccess - No need to configure or reload a server when adding a site - Content negotiation
161 lines
6.3 KiB
PHP
161 lines
6.3 KiB
PHP
<?php
|
|
const ROOT_PATH = __DIR__;
|
|
define('CONF', parse_ini_file(ROOT_PATH . '/config.ini', true, INI_SCANNER_TYPED));
|
|
|
|
define('DB', new PDO('sqlite:' . ROOT_PATH . '/db/servnest.db'));
|
|
DB->exec('PRAGMA foreign_keys = ON;');
|
|
DB->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
|
|
date_default_timezone_set('UTC');
|
|
|
|
$locale = 'en';
|
|
foreach (explode(',', preg_replace('/[A-Z0-9]|q=|;|-|\./', '', $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? '')) as $client_locale) {
|
|
if (in_array($client_locale, array_diff(scandir(ROOT_PATH . '/locales'), ['..', '.']), true)) {
|
|
$locale = $client_locale;
|
|
break;
|
|
}
|
|
}
|
|
define('LOCALE', $locale);
|
|
setlocale(LC_MESSAGES, 'C.UTF-8');
|
|
bindtextdomain('messages', 'locales/' . LOCALE);
|
|
header('Content-Language: ' . LOCALE);
|
|
|
|
const SERVICES_USER = ['reg', 'ns', 'ht'];
|
|
|
|
const LF = "\n";
|
|
|
|
const PLACEHOLDER_DOMAIN = 'example'; // From RFC2606: Reserved Top Level DNS Names > 2. TLDs for Testing, & Documentation Examples
|
|
const PLACEHOLDER_IPV6 = '2001:db8::3'; // From RFC3849: IPv6 Address Prefix Reserved for Documentation
|
|
const PLACEHOLDER_IPV4 = '203.0.113.42'; // From RFC5737: IPv4 Address Blocks Reserved for Documentation
|
|
|
|
foreach (array_diff(scandir(ROOT_PATH . '/fn'), ['..', '.']) as $file)
|
|
require ROOT_PATH . '/fn/' . $file;
|
|
|
|
require ROOT_PATH . '/pages.php';
|
|
|
|
if ($_SERVER['REQUEST_URI'] === '/sftpgo-auth.php')
|
|
return;
|
|
|
|
$pageAddress = substr($_SERVER['REQUEST_URI'], strlen(CONF['common']['prefix']) + 1);
|
|
if (strpos($pageAddress, '?') !== false) {
|
|
parse_str(substr($pageAddress, strpos($pageAddress, '?') + 1), $_GET);
|
|
$pageAddress = substr($pageAddress, 0, strpos($pageAddress, '?'));
|
|
}
|
|
define('PAGE_URL', $pageAddress);
|
|
define('PAGE_ADDRESS', $pageAddress . ((substr($pageAddress, -1) === '/' OR $pageAddress === '') ? 'index' : ''));
|
|
define('PAGE_LINEAGE', explode('/', PAGE_ADDRESS));
|
|
define('SERVICE', dirname(PAGE_ADDRESS));
|
|
|
|
function getPageInformations($pages, $pageElements) {
|
|
if (!isset($pages['index']) OR $pageElements[0] === 'index')
|
|
return [
|
|
'titles_lineage' => [$pages[$pageElements[0]]['title'] ?? false],
|
|
'page_metadata' => $pages[$pageElements[0]] ?? NULL,
|
|
'terminal' => $pageElements[0] !== 'index'
|
|
];
|
|
$result = $pages['index']['title'];
|
|
if (!isset($pageElements[1]))
|
|
unset($pages['index']);
|
|
else
|
|
$pages = $pages[array_shift($pageElements)] ?? false;
|
|
$results = getPageInformations($pages, $pageElements);
|
|
$results['titles_lineage'][] = $result;
|
|
return $results;
|
|
}
|
|
$pageInformations = getPageInformations(PAGES, PAGE_LINEAGE);
|
|
define('TITLES_LINEAGE', array_reverse($pageInformations['titles_lineage']));
|
|
define('PAGE_METADATA', $pageInformations['page_metadata']);
|
|
define('PAGE_TERMINAL', $pageInformations['terminal']);
|
|
|
|
if (!TITLES_LINEAGE[array_key_last(TITLES_LINEAGE)]) {
|
|
http_response_code(404);
|
|
exit('Page not found.');
|
|
}
|
|
|
|
if (isset($_SERVER['SERVER_NAME']) !== true)
|
|
exit('Missing <code>$_SERVER[\'SERVER_NAME\']</code>');
|
|
if (in_array($_SERVER['SERVER_NAME'], CONF['common']['public_domains'], true) !== true)
|
|
exit('The current <code>$_SERVER[\'SERVER_NAME\']</code> is not allowed in configuration.');
|
|
define('SERVER_NAME', $_SERVER['SERVER_NAME']);
|
|
|
|
const SESSION_COOKIE_NAME = 'servnest-session-key';
|
|
function startSession() {
|
|
session_start([
|
|
'name' => SESSION_COOKIE_NAME,
|
|
'sid_length' => 64,
|
|
'sid_bits_per_character' => 6,
|
|
'cookie_secure' => true,
|
|
'cookie_httponly' => true,
|
|
'cookie_samesite' => 'Strict',
|
|
'cookie_path' => CONF['common']['prefix'] . '/',
|
|
'cookie_lifetime' => 432000, // = 60*60*24*5 = 5 days
|
|
'gc_maxlifetime' => 10800,
|
|
'use_strict_mode' => true,
|
|
'use_cookies' => true,
|
|
'use_only_cookies' => true,
|
|
]);
|
|
}
|
|
if (isset($_COOKIE[SESSION_COOKIE_NAME]))
|
|
startSession(); // Resume session
|
|
|
|
if (isset($_SESSION['id'])) {
|
|
// Decrypt display username
|
|
if (!isset($_COOKIE['display-username-decryption-key']))
|
|
output(403, 'The display username decryption key has not been sent.');
|
|
$decryption_result = htmlspecialchars(sodium_crypto_aead_xchacha20poly1305_ietf_decrypt(
|
|
$_SESSION['display-username-cyphertext'],
|
|
'',
|
|
$_SESSION['display-username-nonce'],
|
|
base64_decode($_COOKIE['display-username-decryption-key'])
|
|
));
|
|
if ($decryption_result === false)
|
|
output(403, 'Unable to decrypt display username.');
|
|
define('DISPLAY_USERNAME', $decryption_result);
|
|
|
|
// Enable not already enabled services for this user
|
|
$user_services = array_filter(explode(',', query('select', 'users', ['id' => $_SESSION['id']], 'services')[0]));
|
|
if (in_array(SERVICE, SERVICES_USER, true) AND !in_array(SERVICE, $user_services, true) AND CONF['common']['services'][SERVICE] === 'enabled') {
|
|
$user_services[] = SERVICE;
|
|
|
|
DB->prepare('UPDATE users SET services = :services WHERE id = :id')
|
|
->execute([':services' => implode(',', $user_services), ':id' => $_SESSION['id']]);
|
|
|
|
if (SERVICE === 'ht')
|
|
htSetupUserFs($_SESSION['id']);
|
|
}
|
|
}
|
|
|
|
function displayFinalMessage($data) {
|
|
if (isset($data['final_message'])) {
|
|
echo $data['final_message'];
|
|
unset($data['final_message']);
|
|
}
|
|
}
|
|
|
|
if ($_POST !== []) {
|
|
if (in_array(SERVICE, SERVICES_USER, true) AND CONF['common']['services'][SERVICE] !== 'enabled')
|
|
output(503, _('This service is currently under maintenance. No action can be taken on it until an administrator finishes repairing it.'));
|
|
|
|
// Protect against cross-site request forgery if a POST request is received
|
|
if (isset($_SERVER['HTTP_SEC_FETCH_SITE']) !== true)
|
|
output(403, 'The <code>Sec-Fetch-Site</code> HTTP header is required when submitting a POST request to prevent Cross-Site Request Forgery (<abbr>CSRF</abbr>).');
|
|
if ($_SERVER['HTTP_SEC_FETCH_SITE'] !== 'same-origin')
|
|
if (!in_array($_SERVER['HTTP_SEC_FETCH_SITE'], ['none', 'same-origin'], true))
|
|
output(403, 'The <code>Sec-Fetch-Site</code> HTTP header must be <code>same-origin</code> or <code>none</code> when submitting a POST request to prevent Cross-Site Request Forgery (<abbr>CSRF</abbr>).');
|
|
|
|
if (PAGE_METADATA['require-login'] ?? true !== false) {
|
|
if (isset($_SESSION['id']) !== true)
|
|
output(403, _('You need to be logged in to do this.'));
|
|
if (isset(query('select', 'users', ['id' => $_SESSION['id']], 'id')[0]) !== true)
|
|
output(403, _('This account doesn\'t exist anymore. Log out to end this ghost session.'));
|
|
}
|
|
|
|
if (file_exists(ROOT_PATH . '/pg-act/' . PAGE_ADDRESS . '.php'))
|
|
require ROOT_PATH . '/pg-act/' . PAGE_ADDRESS . '.php';
|
|
}
|
|
|
|
function displayPage($data) {
|
|
require ROOT_PATH . '/view.php';
|
|
exit();
|
|
}
|
|
displayPage($data ??= NULL);
|