($msg === '') ? '' : '

' . _('Success: ') . '' . $msg . '

' . LF, 4 => '

' . _('User error: ') . '' . $msg . '

' . LF, 5 => '

' . _('Server error: ') . '' . $msg . '

' . LF, }; if (is_callable('displayPage')) displayPage(array_merge(['final_message' => $final_message], $data)); echo $final_message; exit(); } function exescape(array $args, array &$output = NULL, int &$result_code = NULL): int { exec('2>&1 ' . implode(' ', array_map('escapeshellarg', $args)), $output, $result_code); return $result_code; } function kdig(string $name, string $type, string $server = NULL): array { exescape([ CONF['dns']['kdig_path'], '+json', '+timeout=5', '+retry=0', '+noidn', '-q', $name, '-t', $type, ...(isset($server) ? ['@' . $server] : []), ], $output, $code); if ($code !== 0) throw new KdigException($name . ' ' . $type . ' resolution failed.'); foreach ($output as &$line) if (str_starts_with($line, ';')) $line = ''; return json_decode(implode(LF, $output), true, flags: JSON_THROW_ON_ERROR); } function insert(string $table, array $values): void { $query = 'INSERT INTO "' . $table . '"('; foreach ($values as $key => $val) { if ($key === array_key_last($values)) $query .= "$key"; else $query .= "$key, "; } $query .= ') VALUES('; foreach ($values as $key => $val) { if ($key === array_key_last($values)) $query .= ":$key"; else $query .= ":$key, "; } $query .= ')'; DB->prepare($query) ->execute($values); } function query(string $action, string $table, array $conditions = [], array $columns = NULL): array { $query = match ($action) { 'select' => 'SELECT ' . implode(',', $columns ?? ['*']), 'delete' => 'DELETE', }; $query .= ' FROM "' . $table . '"'; foreach ($conditions as $key => $val) { if ($key === array_key_first($conditions)) $query .= " WHERE $key = :$key"; else $query .= " AND $key = :$key"; } $stmt = DB->prepare($query); $stmt->execute($conditions); if (count($columns ?? []) === 1) return array_column($stmt->fetchAll(PDO::FETCH_ASSOC), $columns[0]); return $stmt->fetchAll(PDO::FETCH_ASSOC); } function displayIndex(): void { ?> redir.'); header('Location: ' . CONF['common']['prefix'] . '/' . $redir_to); exit(); } // PHP rmdir() only works on empty directories function removeDirectory(string $dir): void { $dirObj = new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS); $files = new RecursiveIteratorIterator($dirObj, RecursiveIteratorIterator::CHILD_FIRST); foreach ($files as $file) $file->isDir() && !$file->isLink() ? rmdir($file->getPathname()) : unlink($file->getPathname()); if (rmdir($dir) !== true) output(500, 'Unable to remove directory.'); } function equalArrays(array $a, array $b): bool { return array_diff($a, $b) === [] AND array_diff($b, $a) === []; } /* This token authenticates the user to the server through a public communication (the DNS). It is therefore also designed to keep private: - the user's id - that a same user used a token multiple times (by using a unique salt for each token) */ if (time() - query('select', 'params', ['name' => 'secret_key_last_change'], ['value'])[0] >= 86400 * 20) { DB->prepare("UPDATE params SET value = :secret_key WHERE name = 'secret_key';") ->execute([':secret_key' => bin2hex(random_bytes(32))]); DB->prepare("UPDATE params SET value = :last_change WHERE name = 'secret_key_last_change';") ->execute([':last_change' => time()]); } define('SECRET_KEY', hex2bin(query('select', 'params', ['name' => 'secret_key'], ['value'])[0])); function getAuthToken(): string { $salt = bin2hex(random_bytes(4)); $hash = hash_hmac('sha256', $salt . ($_SESSION['id'] ?? ''), SECRET_KEY); return $salt . '-' . substr($hash, 0, 32); } function checkAuthToken(string $salt, string $hash): void { $correctProof = substr(hash_hmac('sha256', $salt . $_SESSION['id'], SECRET_KEY), 0, 32); if (hash_equals($correctProof, $hash) !== true) output(403, _('Wrong proof.')); }