init.php + jobs + job to delete old testing accounts

This commit is contained in:
Miraty 2023-06-08 17:36:44 +02:00
parent f05a55a7fa
commit e4ae765486
15 changed files with 124 additions and 113 deletions

View file

@ -2,6 +2,9 @@
## Program flow ## Program flow
`init.php`
: Initializes common values
`router.php` `router.php`
: Receives every external HTTP request from the web server, executes actions required in any case, executes matching code in `pg-act` if appropriate, and calls `view.php` either way. : Receives every external HTTP request from the web server, executes actions required in any case, executes matching code in `pg-act` if appropriate, and calls `view.php` either way.
@ -21,12 +24,12 @@ The `output` function is used to return success or error messages and stop proce
`fn/` `fn/`
: Functions, grouped by concerned service : Functions, grouped by concerned service
`jobs/`
: CLI scripts
`sftpgo-auth.php` `sftpgo-auth.php`
: When someone tries to log in over SFTP, SFTPGo sends username and password to this script, which queries the database and replies whether authentication succeeded or not. : When someone tries to log in over SFTP, SFTPGo sends username and password to this script, which queries the database and replies whether authentication succeeded or not.
`check.php`
: This file is not part of the normal program execution, it is meant to be run by developers to test that the current setup is working.
`DOCS/` `DOCS/`
: Documentation (some important or standard files may be directly in the root) : Documentation (some important or standard files may be directly in the root)

View file

@ -87,6 +87,39 @@ function setupDisplayUsername($display_username) {
$_SESSION['display-username-cyphertext'] = $cyphertext; $_SESSION['display-username-cyphertext'] = $cyphertext;
} }
function authDeleteUser($user_id) {
$user_services = explode(',', query('select', 'users', ['id' => $user_id], 'services')[0]);
foreach (SERVICES_USER as $service)
if (in_array($service, $user_services, true) AND CONF['common']['services'][$service] !== 'enabled')
output(503, sprintf(_('Your account can\'t be deleted because the %s service is currently unavailable.'), '<em>' . PAGES[$service]['index']['title'] . '</em>'));
if (in_array('reg', $user_services, true))
foreach (query('select', 'registry', ['username' => $user_id], 'domain') as $domain)
regDeleteDomain($domain, $user_id);
if (in_array('ns', $user_services, true))
foreach (query('select', 'zones', ['username' => $user_id], 'zone') as $zone)
nsDeleteZone($zone, $user_id);
if (in_array('ht', $user_services, true)) {
foreach (query('select', 'sites', ['username' => $user_id]) as $site)
htDeleteSite($site['address'], $site['type'], $user_id);
exec(CONF['ht']['sudo_path'] . ' -u ' . CONF['ht']['tor_user'] . ' ' . CONF['ht']['rm_path'] . ' -r ' . CONF['ht']['tor_keys_path'] . '/' . $user_id, result_code: $code);
if ($code !== 0)
output(500, 'Can\'t remove Tor keys directory.');
removeDirectory(CONF['ht']['tor_config_path'] . '/' . $user_id);
exec(CONF['ht']['sudo_path'] . ' -u ' . CONF['ht']['sftpgo_user'] . ' ' . CONF['ht']['rm_path'] . ' -r ' . CONF['ht']['ht_path'] . '/fs/' . $user_id, result_code: $code);
if ($code !== 0)
output(500, 'Can\'t remove user\'s directory.');
}
query('delete', 'users', ['id' => $user_id]);
}
function rateLimit() { function rateLimit() {
if (PAGE_METADATA['tokens_account_cost'] ?? 0 > 0) if (PAGE_METADATA['tokens_account_cost'] ?? 0 > 0)
rateLimitAccount(PAGE_METADATA['tokens_account_cost']); rateLimitAccount(PAGE_METADATA['tokens_account_cost']);

View file

@ -10,7 +10,10 @@ function output($code, $msg = '', $logs = [''], $data = []) {
4 => '<p><output>' . _('<strong>User error</strong>: ') . '<em>' . $msg . '</em></output></p>' . LF, 4 => '<p><output>' . _('<strong>User error</strong>: ') . '<em>' . $msg . '</em></output></p>' . LF,
5 => '<p><output>' . _('<strong>Server error</strong>: ') . '<em>' . $msg . '</em></output></p>' . LF, 5 => '<p><output>' . _('<strong>Server error</strong>: ') . '<em>' . $msg . '</em></output></p>' . LF,
}; };
displayPage(array_merge(['final_message' => $final_message], $data)); if (is_callable('displayPage'))
displayPage(array_merge(['final_message' => $final_message], $data));
echo $final_message;
exit();
} }
function insert($table, $values) { function insert($table, $values) {

View file

@ -76,17 +76,17 @@ function htRelativeSymlink($target, $name) {
output(500, 'Unable to create symlink.'); output(500, 'Unable to create symlink.');
} }
function htDeleteSite($address, $type) { function htDeleteSite($address, $type, $user_id) {
if ($type === 'onion') { if ($type === 'onion') {
$dir = query('select', 'sites', [ $dir = query('select', 'sites', [
'username' => $_SESSION['id'], 'username' => $user_id,
'address' => $address, 'address' => $address,
'type' => $type, 'type' => $type,
], 'site_dir')[0]; ], 'site_dir')[0];
// Delete Tor config // Delete Tor config
if (unlink(CONF['ht']['tor_config_path'] . '/' . $_SESSION['id'] . '/' . $dir) !== true) if (unlink(CONF['ht']['tor_config_path'] . '/' . $user_id . '/' . $dir) !== true)
output(500, 'Failed to delete Tor configuration.'); output(500, 'Failed to delete Tor configuration.');
// Reload Tor // Reload Tor
@ -95,7 +95,7 @@ function htDeleteSite($address, $type) {
output(500, 'Failed to reload Tor.'); output(500, 'Failed to reload Tor.');
// Delete Tor keys // Delete Tor keys
exec(CONF['ht']['sudo_path'] . ' -u ' . CONF['ht']['tor_user'] . ' ' . CONF['ht']['rm_path'] . ' -r ' . CONF['ht']['tor_keys_path'] . '/' . $_SESSION['id'] . '/' . $dir, result_code: $code); exec(CONF['ht']['sudo_path'] . ' -u ' . CONF['ht']['tor_user'] . ' ' . CONF['ht']['rm_path'] . ' -r ' . CONF['ht']['tor_keys_path'] . '/' . $user_id . '/' . $dir, result_code: $code);
if ($code !== 0) if ($code !== 0)
output(500, 'Failed to delete Tor keys.'); output(500, 'Failed to delete Tor keys.');
} }
@ -117,7 +117,7 @@ function htDeleteSite($address, $type) {
output(500, 'Unable to delete symlink.'); output(500, 'Unable to delete symlink.');
query('delete', 'sites', [ query('delete', 'sites', [
'username' => $_SESSION['id'], 'username' => $user_id,
'type' => $type, 'type' => $type,
'address' => $address, 'address' => $address,
]); ]);

View file

@ -48,7 +48,7 @@ function nsCheckZonePossession($zone) {
output(403, 'You don\'t own this zone on the name server.'); output(403, 'You don\'t own this zone on the name server.');
} }
function nsDeleteZone($zone) { function nsDeleteZone($zone, $user_id) {
// Remove from Knot configuration // Remove from Knot configuration
knotcConfExec(["unset 'zone[$zone]'"]); knotcConfExec(["unset 'zone[$zone]'"]);
@ -64,6 +64,6 @@ function nsDeleteZone($zone) {
// Remove from database // Remove from database
query('delete', 'zones', [ query('delete', 'zones', [
'zone' => $zone, 'zone' => $zone,
'username' => $_SESSION['id'], 'username' => $user_id,
]); ]);
} }

View file

@ -13,7 +13,7 @@ function regCheckDomainPossession($domain) {
output(403, 'You don\'t own this domain on the registry.'); output(403, 'You don\'t own this domain on the registry.');
} }
function regDeleteDomain($domain) { function regDeleteDomain($domain, $user_id) {
// Delete domain from registry file // Delete domain from registry file
$path = CONF['reg']['suffixes_path'] . '/' . regParseDomain($domain)['suffix'] . 'zone'; $path = CONF['reg']['suffixes_path'] . '/' . regParseDomain($domain)['suffix'] . 'zone';
$content = file_get_contents($path); $content = file_get_contents($path);
@ -28,7 +28,7 @@ function regDeleteDomain($domain) {
$conditions = [ $conditions = [
'domain' => $domain, 'domain' => $domain,
'username' => $_SESSION['id'], 'username' => $user_id,
]; ];
insert('registry-history', [ insert('registry-history', [

48
init.php Normal file
View file

@ -0,0 +1,48 @@
<?php
umask(0077);
set_error_handler(function ($level, $message, $file = '', $line = 0) {
throw new ErrorException($message, 0, $level, $file, $line);
});
set_exception_handler(function ($e) {
error_log($e);
http_response_code(500);
echo '<h1>Error</h1>An error occured.';
});
register_shutdown_function(function () { // Also catch fatal errors
if (($error = error_get_last()) !== NULL)
throw new ErrorException($error['message'], 0, $error['type'], $error['file'], $error['line']);
});
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');
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 ?? 'en');
putenv('LANG=C.UTF-8');
setlocale(LC_MESSAGES, 'C.UTF-8');
bindtextdomain('messages', ROOT_PATH . '/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';

View file

@ -1,7 +1,6 @@
<?php <?php // Test that the current setup is working
umask(0077);
const ROOT_PATH = __DIR__; require 'init.php';
define('CONF', parse_ini_file(ROOT_PATH . '/config.ini', true, INI_SCANNER_TYPED));
const SFTP = '/usr/bin/sftp'; const SFTP = '/usr/bin/sftp';
const SSHPASS = '/usr/bin/sshpass'; const SSHPASS = '/usr/bin/sshpass';
@ -13,16 +12,10 @@ const SUFFIX = 'test.servnest.test.';
const TOR_PROXY = 'socks5h://127.0.0.1:9050'; const TOR_PROXY = 'socks5h://127.0.0.1:9050';
const LF = "\n";
exec(CONF['dns']['kdig_path'] . ' torproject.org AAAA', $output, $return_code); exec(CONF['dns']['kdig_path'] . ' torproject.org AAAA', $output, $return_code);
if (preg_match('/^;; Flags: qr rd ra ad;/Dm', implode("\n", $output)) !== 1) if (preg_match('/^;; Flags: qr rd ra ad;/Dm', implode("\n", $output)) !== 1)
exit('Unable to do a DNSSEC-validated DNS query.' . LF); exit('Unable to do a DNSSEC-validated DNS query.' . LF);
if (CONF['common']['services']['ns'] === 'rest') {
echo 'a';
}
define('COOKIE_FILE', sys_get_temp_dir() . '/cookie-' . bin2hex(random_bytes(16)) . '.txt'); define('COOKIE_FILE', sys_get_temp_dir() . '/cookie-' . bin2hex(random_bytes(16)) . '.txt');
function curlTest($address, $post = [], $tor = false) { function curlTest($address, $post = [], $tor = false) {

View file

@ -0,0 +1,10 @@
<?php
require 'init.php';
const MAX_TESTING_ACCOUNT_AGE = 86400 * 10;
foreach (query('select', 'users', ['type' => 'testing']) as $account) {
$account_age = time() - date_create_immutable_from_format('Y-m-d H:i:s', $account['registration_date'])->getTimestamp();
if ($account_age > MAX_TESTING_ACCOUNT_AGE)
authDeleteUser($account['id']);
}

View file

@ -6,36 +6,7 @@ if (checkPassword($_SESSION['id'], $_POST['current-password']) !== true)
if (!isset($_POST['delete'])) if (!isset($_POST['delete']))
output(403, _('Account deletion must be confirmed.')); output(403, _('Account deletion must be confirmed.'));
$user_services = explode(',', query('select', 'users', ['id' => $_SESSION['id']], 'services')[0]); authDeleteUser($_SESSION['id']);
foreach (SERVICES_USER as $service)
if (in_array($service, $user_services, true) AND CONF['common']['services'][$service] !== 'enabled')
output(503, sprintf(_('Your account can\'t be deleted because the %s service is currently unavailable.'), '<em>' . PAGES[$service]['index']['title'] . '</em>'));
if (in_array('reg', $user_services, true))
foreach (query('select', 'registry', ['username' => $_SESSION['id']], 'domain') as $domain)
regDeleteDomain($domain);
if (in_array('ns', $user_services, true))
foreach (query('select', 'zones', ['username' => $_SESSION['id']], 'zone') as $zone)
nsDeleteZone($zone);
if (in_array('ht', $user_services, true)) {
foreach (query('select', 'sites', ['username' => $_SESSION['id']]) as $site)
htDeleteSite($site['address'], $site['type']);
exec(CONF['ht']['sudo_path'] . ' -u ' . CONF['ht']['tor_user'] . ' ' . CONF['ht']['rm_path'] . ' -r ' . CONF['ht']['tor_keys_path'] . '/' . $_SESSION['id'], result_code: $code);
if ($code !== 0)
output(500, 'Can\'t remove Tor keys directory.');
removeDirectory(CONF['ht']['tor_config_path'] . '/' . $_SESSION['id']);
exec(CONF['ht']['sudo_path'] . ' -u ' . CONF['ht']['sftpgo_user'] . ' ' . CONF['ht']['rm_path'] . ' -r ' . CONF['ht']['ht_path'] . '/fs/' . $_SESSION['id'], result_code: $code);
if ($code !== 0)
output(500, 'Can\'t remove user\'s directory.');
}
query('delete', 'users', ['id' => $_SESSION['id']]);
logout(); logout();

View file

@ -6,6 +6,6 @@ if (preg_match('/^(?<type>subpath|subdomain|onion|dns):(?<address>[a-z0-9._-]{1,
if (isset(query('select', 'sites', ['username' => $_SESSION['id'], 'address' => $site['address'], 'type' => $site['type']], 'address')[0]) !== true) if (isset(query('select', 'sites', ['username' => $_SESSION['id'], 'address' => $site['address'], 'type' => $site['type']], 'address')[0]) !== true)
output(403, 'Unavailable value for <code>site</code>.'); output(403, 'Unavailable value for <code>site</code>.');
htDeleteSite($site['address'], $site['type']); htDeleteSite($site['address'], $site['type'], $_SESSION['id']);
output(200, _('Access removed.')); output(200, _('Access removed.'));

View file

@ -2,6 +2,6 @@
nsCheckZonePossession($_POST['zone']); nsCheckZonePossession($_POST['zone']);
nsDeleteZone($_POST['zone']); nsDeleteZone($_POST['zone'], $_SESSION['id']);
output(200, _('Zone deleted.')); output(200, _('Zone deleted.'));

View file

@ -2,6 +2,6 @@
regCheckDomainPossession($_POST['domain']); regCheckDomainPossession($_POST['domain']);
regDeleteDomain($_POST['domain']); regDeleteDomain($_POST['domain'], $_SESSION['id']);
output(200, _('Domain unregistered.')); output(200, _('Domain unregistered.'));

View file

@ -1,54 +1,5 @@
<?php <?php
umask(0077); require 'init.php';
set_error_handler(function ($level, $message, $file = '', $line = 0) {
throw new ErrorException($message, 0, $level, $file, $line);
});
set_exception_handler(function ($e) {
error_log($e);
http_response_code(500);
echo '<h1>Error</h1>An error occured.';
});
register_shutdown_function(function () { // Also catch fatal errors
if (($error = error_get_last()) !== NULL)
throw new ErrorException($error['message'], 0, $error['type'], $error['file'], $error['line']);
});
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');
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 ?? 'en');
putenv('LANG=C.UTF-8');
setlocale(LC_MESSAGES, 'C.UTF-8');
bindtextdomain('messages', ROOT_PATH . '/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); $pageAddress = substr($_SERVER['REQUEST_URI'], strlen(CONF['common']['prefix']) + 1);
if (strpos($pageAddress, '?') !== false) { if (strpos($pageAddress, '?') !== false) {
@ -113,6 +64,9 @@ if (isset($_COOKIE[SESSION_COOKIE_NAME]))
startSession(); // Resume session startSession(); // Resume session
if (isset($_SESSION['id'])) { if (isset($_SESSION['id'])) {
if (!isset(query('select', 'users', ['id' => $_SESSION['id']], 'id')[0]))
output(403, _('This account doesn\'t exist anymore. Log out to end this ghost session.'));
// Decrypt display username // Decrypt display username
if (!isset($_COOKIE['display-username-decryption-key'])) if (!isset($_COOKIE['display-username-decryption-key']))
output(403, 'The display username decryption key has not been sent.'); output(403, 'The display username decryption key has not been sent.');
@ -157,12 +111,8 @@ if ($_POST !== []) {
if (!in_array($_SERVER['HTTP_SEC_FETCH_SITE'], ['none', 'same-origin'], true)) 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>).'); 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 (PAGE_METADATA['require-login'] ?? true AND !isset($_SESSION['id']))
if (isset($_SESSION['id']) !== true) output(403, _('You need to be logged in to do this.'));
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')) if (file_exists(ROOT_PATH . '/pg-act/' . PAGE_ADDRESS . '.php'))
require ROOT_PATH . '/pg-act/' . PAGE_ADDRESS . '.php'; require ROOT_PATH . '/pg-act/' . PAGE_ADDRESS . '.php';

View file

@ -3,7 +3,7 @@
const DEBUG = false; const DEBUG = false;
!DEBUG or ob_start(); !DEBUG or ob_start();
require 'router.php'; require 'init.php';
function deny($reason) { function deny($reason) {
!DEBUG or file_put_contents(ROOT_PATH . '/db/debug.txt', ob_get_contents() . $reason . LF); !DEBUG or file_put_contents(ROOT_PATH . '/db/debug.txt', ob_get_contents() . $reason . LF);