65536, 'time_cost' => 4, 'threads' => 64, ]; function checkUsernameFormat(string $username): void { if (preg_match('/' . USERNAME_REGEX . '/Du', $username) !== 1) output(403, 'Username malformed.'); } function checkPasswordFormat(string $password): void { if (preg_match('/' . PASSWORD_REGEX . '/Du', $password) !== 1) output(403, 'Password malformed.'); } function hashUsername(string $username): string { return base64_encode(sodium_crypto_pwhash(32, $username, hex2bin(query('select', 'params', ['name' => 'username_salt'], ['value'])[0]), 2**10, 2**14, SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13)); } function hashPassword(string $password): string { return password_hash($password, ALGO_PASSWORD, OPTIONS_PASSWORD); } function usernameExists(string $username): bool { return isset(query('select', 'users', ['username' => $username], ['id'])[0]); } function checkPassword(string $id, string $password): bool { return password_verify($password, query('select', 'users', ['id' => $id], ['password'])[0]); } function outdatedPasswordHash(string $id): bool { return password_needs_rehash(query('select', 'users', ['id' => $id], ['password'])[0], ALGO_PASSWORD, OPTIONS_PASSWORD); } function changePassword(string $id, string $password): void { DB->prepare('UPDATE users SET password = :password WHERE id = :id') ->execute([':password' => hashPassword($password), ':id' => $id]); } function stopSession(): void { if (session_status() === PHP_SESSION_ACTIVE) session_destroy(); } function logout(): never { stopSession(); header('Clear-Site-Data: "*"'); redir(); } function setupDisplayUsername(string $display_username): void { $nonce = random_bytes(24); $key = sodium_crypto_aead_xchacha20poly1305_ietf_keygen(); $cyphertext = sodium_crypto_aead_xchacha20poly1305_ietf_encrypt( htmlspecialchars($display_username), '', $nonce, $key ); $_SESSION['display-username-nonce'] = $nonce; setcookie( 'display-username-decryption-key', base64_encode($key), [ 'expires' => time() + 432000, 'path' => CONF['common']['prefix'] . '/', 'secure' => true, 'httponly' => true, 'samesite' => 'Strict' ] ); $_SESSION['display-username-cyphertext'] = $cyphertext; } function authDeleteUser(string $user_id): void { $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.'), '' . PAGES[$service]['index']['title'] . '')); 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); exescape([ 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); exescape([ 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', 'ssh-keys', ['username' => $user_id]); } query('delete', 'users', ['id' => $user_id]); } function rateLimit(): void { if ((PAGE_METADATA['tokens_account_cost'] ?? 0) > 0) rateLimitAccount(PAGE_METADATA['tokens_account_cost']); if ((PAGE_METADATA['tokens_instance_cost'] ?? 0) > 0) rateLimitInstance(PAGE_METADATA['tokens_instance_cost']); } const MAX_ACCOUNT_TOKENS = 86400; function rateLimitAccount(int $requestedTokens): int { // Get $userData = query('select', 'users', ['id' => $_SESSION['id']]); $tokens = $userData[0]['bucket_tokens']; $bucketLastUpdate = $userData[0]['bucket_last_update']; // Compute $tokens = min(MAX_ACCOUNT_TOKENS, $tokens + (time() - $bucketLastUpdate)); if ($requestedTokens > 0) { if ($requestedTokens > $tokens) output(453, _('Account rate limit reached, try again later.')); $tokens -= $requestedTokens; // Update DB->prepare('UPDATE users SET bucket_tokens = :bucket_tokens, bucket_last_update = :bucket_last_update WHERE id = :id') ->execute([ ':bucket_tokens' => $tokens, ':bucket_last_update' => time(), ':id' => $_SESSION['id'] ]); } return $tokens; } function rateLimitInstance(int $requestedTokens): void { // Get $tokens = query('select', 'params', ['name' => 'instance_bucket_tokens'], ['value'])[0]; $bucketLastUpdate = query('select', 'params', ['name' => 'instance_bucket_last_update'], ['value'])[0]; // Compute $tokens = min(86400, $tokens + (time() - $bucketLastUpdate)); if ($requestedTokens > $tokens) output(453, _('Global rate limit reached, try again later.')); $tokens -= $requestedTokens; // Update DB->prepare("UPDATE params SET value = :bucket_tokens WHERE name = 'instance_bucket_tokens';") ->execute([':bucket_tokens' => $tokens]); DB->prepare("UPDATE params SET value = :bucket_last_update WHERE name = 'instance_bucket_last_update';") ->execute([':bucket_last_update' => time()]); }