Merge pull request 'dev' (#9) from dev into main
Reviewed-on: https://code.antopie.org/servnest/servnest/pulls/9
This commit is contained in:
commit
95f376ab00
102 changed files with 1586 additions and 814 deletions
12
db/migrations/006-create-ns-syncs-table.sql
Normal file
12
db/migrations/006-create-ns-syncs-table.sql
Normal file
|
@ -0,0 +1,12 @@
|
|||
BEGIN TRANSACTION;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "ns-syncs" (
|
||||
"username" TEXT NOT NULL,
|
||||
"source" TEXT NOT NULL,
|
||||
"destination" TEXT NOT NULL UNIQUE,
|
||||
UNIQUE("username", "source", "destination"),
|
||||
FOREIGN KEY("username") REFERENCES "users"("id"),
|
||||
FOREIGN KEY("destination") REFERENCES "zones"("zone")
|
||||
);
|
||||
|
||||
COMMIT;
|
|
@ -42,6 +42,13 @@ CREATE TABLE IF NOT EXISTS "zones" (
|
|||
PRIMARY KEY("zone"),
|
||||
FOREIGN KEY("username") REFERENCES "users"("id")
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "ns-syncs" (
|
||||
"username" TEXT NOT NULL,
|
||||
"source" TEXT NOT NULL,
|
||||
"destination" TEXT NOT NULL UNIQUE,
|
||||
FOREIGN KEY("username") REFERENCES "users"("id"),
|
||||
FOREIGN KEY("destination") REFERENCES "zones"("zone")
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "sites" (
|
||||
"username" TEXT NOT NULL,
|
||||
"site_dir" TEXT NOT NULL,
|
||||
|
|
34
fn/auth.php
34
fn/auth.php
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
const USERNAME_REGEX = '^.{1,1024}$';
|
||||
const PASSWORD_REGEX = '^(?=.*[\p{Ll}])(?=.*[\p{Lu}])(?=.*[\p{N}]).{8,1024}|.{10,1024}$';
|
||||
|
@ -14,47 +14,47 @@ const OPTIONS_PASSWORD = [
|
|||
'threads' => 64,
|
||||
];
|
||||
|
||||
function checkUsernameFormat($username) {
|
||||
function checkUsernameFormat(string $username): void {
|
||||
if (preg_match('/' . USERNAME_REGEX . '/Du', $username) !== 1)
|
||||
output(403, 'Username malformed.');
|
||||
}
|
||||
|
||||
function checkPasswordFormat($password) {
|
||||
function checkPasswordFormat(string $password): void {
|
||||
if (preg_match('/' . PASSWORD_REGEX . '/Du', $password) !== 1)
|
||||
output(403, 'Password malformed.');
|
||||
}
|
||||
|
||||
function hashUsername($username) {
|
||||
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($password) {
|
||||
function hashPassword(string $password): string {
|
||||
return password_hash($password, ALGO_PASSWORD, OPTIONS_PASSWORD);
|
||||
}
|
||||
|
||||
function usernameExists($username) {
|
||||
function usernameExists(string $username): bool {
|
||||
return isset(query('select', 'users', ['username' => $username], 'id')[0]);
|
||||
}
|
||||
|
||||
function checkPassword($id, $password) {
|
||||
function checkPassword(string $id, string $password): bool {
|
||||
return password_verify($password, query('select', 'users', ['id' => $id], 'password')[0]);
|
||||
}
|
||||
|
||||
function outdatedPasswordHash($id) {
|
||||
function outdatedPasswordHash(string $id): bool {
|
||||
return password_needs_rehash(query('select', 'users', ['id' => $id], 'password')[0], ALGO_PASSWORD, OPTIONS_PASSWORD);
|
||||
}
|
||||
|
||||
function changePassword($id, $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() {
|
||||
function stopSession(): void {
|
||||
if (session_status() === PHP_SESSION_ACTIVE)
|
||||
session_destroy();
|
||||
}
|
||||
|
||||
function logout() {
|
||||
function logout(): never {
|
||||
stopSession();
|
||||
|
||||
header('Clear-Site-Data: "*"');
|
||||
|
@ -62,7 +62,7 @@ function logout() {
|
|||
redir();
|
||||
}
|
||||
|
||||
function setupDisplayUsername($display_username) {
|
||||
function setupDisplayUsername(string $display_username): void {
|
||||
$nonce = random_bytes(24);
|
||||
$key = sodium_crypto_aead_xchacha20poly1305_ietf_keygen();
|
||||
$cyphertext = sodium_crypto_aead_xchacha20poly1305_ietf_encrypt(
|
||||
|
@ -87,7 +87,7 @@ function setupDisplayUsername($display_username) {
|
|||
$_SESSION['display-username-cyphertext'] = $cyphertext;
|
||||
}
|
||||
|
||||
function authDeleteUser($user_id) {
|
||||
function authDeleteUser(string $user_id): void {
|
||||
$user_services = explode(',', query('select', 'users', ['id' => $user_id], 'services')[0]);
|
||||
|
||||
foreach (SERVICES_USER as $service)
|
||||
|
@ -133,12 +133,14 @@ function authDeleteUser($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() {
|
||||
function rateLimit(): void {
|
||||
if (PAGE_METADATA['tokens_account_cost'] ?? 0 > 0)
|
||||
rateLimitAccount(PAGE_METADATA['tokens_account_cost']);
|
||||
|
||||
|
@ -147,7 +149,7 @@ function rateLimit() {
|
|||
}
|
||||
|
||||
const MAX_ACCOUNT_TOKENS = 86400;
|
||||
function rateLimitAccount($requestedTokens) {
|
||||
function rateLimitAccount(int $requestedTokens): int {
|
||||
// Get
|
||||
$userData = query('select', 'users', ['id' => $_SESSION['id']]);
|
||||
$tokens = $userData[0]['bucket_tokens'];
|
||||
|
@ -174,7 +176,7 @@ function rateLimitAccount($requestedTokens) {
|
|||
return $tokens;
|
||||
}
|
||||
|
||||
function rateLimitInstance($requestedTokens) {
|
||||
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];
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
function output($code, $msg = '', $logs = [''], $data = []) {
|
||||
function output(int $code, string $msg = '', array $logs = [''], array $data = []): never {
|
||||
http_response_code($code);
|
||||
$shortCode = intval($code / 100);
|
||||
if ($shortCode === 5)
|
||||
|
@ -21,7 +21,25 @@ function exescape(array $args, array &$output = NULL, int &$result_code = NULL):
|
|||
return $result_code;
|
||||
}
|
||||
|
||||
function insert($table, $values) {
|
||||
class KdigException extends Exception {};
|
||||
function kdig(string $name, string $type, string $server = NULL): array {
|
||||
exescape([
|
||||
CONF['dns']['kdig_path'],
|
||||
'+json',
|
||||
'+timeout=5',
|
||||
'+retry=0',
|
||||
'-q',
|
||||
$name,
|
||||
'-t',
|
||||
$type,
|
||||
...(isset($server) ? ['@' . $server] : []),
|
||||
], $output, $code);
|
||||
if ($code !== 0)
|
||||
throw new KdigException();
|
||||
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) {
|
||||
|
@ -44,7 +62,7 @@ function insert($table, $values) {
|
|||
->execute($values);
|
||||
}
|
||||
|
||||
function query($action, $table, $conditions = [], $column = NULL) {
|
||||
function query(string $action, string $table, array $conditions = [], string $column = NULL): array {
|
||||
|
||||
$query = match ($action) {
|
||||
'select' => 'SELECT *',
|
||||
|
@ -66,7 +84,7 @@ function query($action, $table, $conditions = [], $column = NULL) {
|
|||
return array_column($stmt->fetchAll(PDO::FETCH_ASSOC), $column);
|
||||
}
|
||||
|
||||
function displayIndex() { ?>
|
||||
function displayIndex(): void { ?>
|
||||
<nav>
|
||||
<dl>
|
||||
<?php foreach (PAGES[SERVICE] as $pageId => $page) {
|
||||
|
@ -82,11 +100,11 @@ function displayIndex() { ?>
|
|||
<?php
|
||||
}
|
||||
|
||||
function redirUrl($pageId) {
|
||||
function redirUrl(string $pageId): string {
|
||||
return CONF['common']['prefix'] . '/' . $pageId . '?redir=' . PAGE_URL;
|
||||
}
|
||||
|
||||
function redir($redir_to = NULL) {
|
||||
function redir(string $redir_to = NULL): never {
|
||||
$redir_to ??= $_GET['redir'] ?? NULL;
|
||||
|
||||
if ($redir_to === NULL) {
|
||||
|
@ -100,7 +118,7 @@ function redir($redir_to = NULL) {
|
|||
}
|
||||
|
||||
// PHP rmdir() only works on empty directories
|
||||
function removeDirectory($dir) {
|
||||
function removeDirectory(string $dir): void {
|
||||
$dirObj = new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS);
|
||||
$files = new RecursiveIteratorIterator($dirObj, RecursiveIteratorIterator::CHILD_FIRST);
|
||||
foreach ($files as $file)
|
||||
|
@ -109,7 +127,7 @@ function removeDirectory($dir) {
|
|||
output(500, 'Unable to remove directory.');
|
||||
}
|
||||
|
||||
function equalArrays($a, $b) {
|
||||
function equalArrays(array $a, array $b): bool {
|
||||
return array_diff($a, $b) === [] AND array_diff($b, $a) === [];
|
||||
}
|
||||
|
||||
|
@ -126,12 +144,12 @@ if (time() - query('select', 'params', ['name' => 'secret_key_last_change'], 'va
|
|||
->execute([':last_change' => time()]);
|
||||
}
|
||||
define('SECRET_KEY', hex2bin(query('select', 'params', ['name' => 'secret_key'], 'value')[0]));
|
||||
function getAuthToken() {
|
||||
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($salt, $hash) {
|
||||
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.'));
|
||||
|
|
33
fn/dns.php
33
fn/dns.php
|
@ -1,16 +1,19 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
function parseZoneFile($zone_content, $types, $filter_domain = false) {
|
||||
function parseZoneFile(string $zone_content, array $types, bool|string $filter_domain = false, bool $filter_include_subdomains = true): array {
|
||||
$parsed_zone_content = [];
|
||||
foreach (explode(LF, $zone_content) as $zone_line) {
|
||||
if ($zone_line === '' OR str_starts_with($zone_line, ';'))
|
||||
continue; // Ignore empty lines and comments
|
||||
$elements = preg_split('/[\t ]+/', $zone_line, 4);
|
||||
if ($filter_domain !== false AND !str_ends_with($elements[0], $filter_domain))
|
||||
if ($filter_domain !== false AND match ($filter_include_subdomains) {
|
||||
true => !str_ends_with($elements[0], $filter_domain),
|
||||
false => $elements[0] !== $filter_domain,
|
||||
})
|
||||
continue; // Ignore records for other domains
|
||||
if (!in_array($elements[2], $types, true))
|
||||
continue; // Ignore records generated by Knot
|
||||
array_push($parsed_zone_content, array_map('htmlspecialchars', $elements));
|
||||
array_push($parsed_zone_content, $elements);
|
||||
}
|
||||
return $parsed_zone_content;
|
||||
}
|
||||
|
@ -46,7 +49,7 @@ function knotcConfExec(array $cmds): void {
|
|||
}
|
||||
}
|
||||
|
||||
function knotcZoneExec(string $zone, array $cmd, string $action = NULL) {
|
||||
function knotcZoneExec(string $zone, array $cmd, string $action = NULL): void {
|
||||
$action = checkAction($action ?? $_POST['action']);
|
||||
|
||||
knotc(['zone-begin', $zone], $output['begin'], $code['begin']);
|
||||
|
@ -66,32 +69,32 @@ function knotcZoneExec(string $zone, array $cmd, string $action = NULL) {
|
|||
}
|
||||
}
|
||||
|
||||
function checkIpFormat($ip) {
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4))
|
||||
return 'A';
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6))
|
||||
return 'AAAA';
|
||||
output(403, _('IP address malformed.'));
|
||||
function checkIpFormat(string $ip): string {
|
||||
return match ($ip) {
|
||||
filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) => 'AAAA',
|
||||
filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) => 'A',
|
||||
default => output(403, _('IP address malformed.')),
|
||||
};
|
||||
}
|
||||
|
||||
function checkAbsoluteDomainFormat($domain) { // If the domain must end with a dot
|
||||
function checkAbsoluteDomainFormat(string $domain): void { // If the domain must end with a dot
|
||||
if (!filter_var($domain, FILTER_VALIDATE_DOMAIN) OR preg_match('/^(?=^.{1,254}$)([a-z0-9_-]{1,63}\.){2,127}$/D', $domain) !== 1)
|
||||
output(403, _('Domain malformed.'));
|
||||
}
|
||||
|
||||
function formatEndWithDot($str) {
|
||||
function formatEndWithDot(string $str): string {
|
||||
if (!str_ends_with($str, '.'))
|
||||
$str .= '.';
|
||||
return $str;
|
||||
}
|
||||
|
||||
function formatAbsoluteDomain($domain) {
|
||||
function formatAbsoluteDomain(string $domain): string {
|
||||
$domain = formatEndWithDot(strtolower($domain));
|
||||
checkAbsoluteDomainFormat($domain);
|
||||
return $domain;
|
||||
}
|
||||
|
||||
function checkAction($action) {
|
||||
function checkAction(string $action): string {
|
||||
return match ($action) {
|
||||
'add' => '',
|
||||
'delete' => 'un',
|
||||
|
|
18
fn/ht.php
18
fn/ht.php
|
@ -1,9 +1,9 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
const SUBPATH_REGEX = '^[a-z0-9-]{4,63}$';
|
||||
const ED25519_PUBKEY_REGEX = '^[a-zA-Z0-9/+]{68}$';
|
||||
|
||||
function htSetupUserFs($id) {
|
||||
function htSetupUserFs(string $id): void {
|
||||
// Setup SFTP directory
|
||||
if (mkdir(CONF['ht']['ht_path'] . '/fs/' . $id, 0000) !== true)
|
||||
output(500, 'Can\'t create user directory.');
|
||||
|
@ -42,19 +42,19 @@ function htSetupUserFs($id) {
|
|||
output(500, 'Can\'t create Tor keys directory.');
|
||||
}
|
||||
|
||||
function checkDomainFormat($domain) {
|
||||
function checkDomainFormat(string $domain): void {
|
||||
// If the domain must end without a dot
|
||||
if (!filter_var($domain, FILTER_VALIDATE_DOMAIN) OR !preg_match('/^(?=^.{1,254}$)([a-z0-9_-]{1,63}\.){1,126}[a-z0-9]{1,63}$/D', $domain))
|
||||
output(403, _('Domain malformed.'));
|
||||
}
|
||||
|
||||
function formatDomain($domain) {
|
||||
function formatDomain(string $domain): string {
|
||||
$domain = rtrim(strtolower($domain), '.');
|
||||
checkDomainFormat($domain);
|
||||
return $domain;
|
||||
}
|
||||
|
||||
function listFsDirs($username) {
|
||||
function listFsDirs(string $username): array {
|
||||
if ($username === '')
|
||||
return [];
|
||||
$absoluteDirs = glob(CONF['ht']['ht_path'] . '/fs/' . $username . '/*/', GLOB_ONLYDIR);
|
||||
|
@ -65,7 +65,7 @@ function listFsDirs($username) {
|
|||
return $dirs;
|
||||
}
|
||||
|
||||
function addSite($username, $siteDir, $address, $type) {
|
||||
function addSite(string $username, string $siteDir, string $address, string $type): void {
|
||||
insert('sites', [
|
||||
'username' => $username,
|
||||
'site_dir' => $siteDir,
|
||||
|
@ -75,7 +75,7 @@ function addSite($username, $siteDir, $address, $type) {
|
|||
]);
|
||||
}
|
||||
|
||||
function dirsStatuses($type) {
|
||||
function dirsStatuses(string $type): array {
|
||||
if (isset($_SESSION['id']) !== true)
|
||||
return [];
|
||||
$dbDirs = query('select', 'sites', [
|
||||
|
@ -88,7 +88,7 @@ function dirsStatuses($type) {
|
|||
return $dirs;
|
||||
}
|
||||
|
||||
function htRelativeSymlink($target, $name) {
|
||||
function htRelativeSymlink(string $target, string $name): void {
|
||||
chdir(pathinfo($name)['dirname']);
|
||||
$symlink = symlink($target, pathinfo($name)['basename']);
|
||||
chdir(ROOT_PATH);
|
||||
|
@ -96,7 +96,7 @@ function htRelativeSymlink($target, $name) {
|
|||
output(500, 'Unable to create symlink.');
|
||||
}
|
||||
|
||||
function htDeleteSite($address, $type, $user_id) {
|
||||
function htDeleteSite(string $address, string $type, string $user_id): void {
|
||||
|
||||
if ($type === 'onion') {
|
||||
$dir = query('select', 'sites', [
|
||||
|
|
34
fn/ns.php
34
fn/ns.php
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
const SOA_VALUES = [
|
||||
const NS_SOA_VALUES = [
|
||||
'ttl' => 10800,
|
||||
'email' => CONF['ns']['public_soa_email'],
|
||||
'refresh' => 10800,
|
||||
|
@ -9,15 +9,17 @@ const SOA_VALUES = [
|
|||
'negative' => 10800,
|
||||
];
|
||||
|
||||
const MIN_TTL = 300;
|
||||
const DEFAULT_TTL = 10800;
|
||||
const MAX_TTL = 1728000;
|
||||
const NS_MIN_TTL = 300;
|
||||
const NS_DEFAULT_TTL = 10800;
|
||||
const NS_MAX_TTL = 1728000;
|
||||
|
||||
const ALLOWED_TYPES = ['AAAA', 'A', 'TXT', 'SRV', 'MX', 'SVCB', 'HTTPS', 'NS', 'DS', 'CAA', 'CNAME', 'DNAME', 'LOC', 'SSHFP', 'TLSA'];
|
||||
const NS_ALLOWED_TYPES = ['AAAA', 'A', 'TXT', 'SRV', 'MX', 'SSHFP', 'TLSA', 'NS', 'DS', 'CSYNC', 'CAA', 'CNAME', 'DNAME', 'SVCB', 'HTTPS', 'LOC'];
|
||||
|
||||
const ZONE_MAX_CHARACTERS = 10000;
|
||||
const NS_TEXTAREA_MAX_CHARACTERS = 10000;
|
||||
|
||||
function nsParseCommonRequirements() {
|
||||
const NS_SYNC_TTL = 10800;
|
||||
|
||||
function nsParseCommonRequirements(): array {
|
||||
nsCheckZonePossession($_POST['zone']);
|
||||
|
||||
if (($_POST['subdomain'] === '') OR ($_POST['subdomain'] === '@'))
|
||||
|
@ -27,28 +29,28 @@ function nsParseCommonRequirements() {
|
|||
|
||||
$values['ttl'] = intval($_POST['ttl-value'] * $_POST['ttl-multiplier']);
|
||||
|
||||
if ($values['ttl'] < MIN_TTL)
|
||||
output(403, sprintf(_('TTLs shorter than %s seconds are forbidden.'), MIN_TTL));
|
||||
if ($values['ttl'] > MAX_TTL)
|
||||
output(403, sprintf(_('TTLs longer than %s seconds are forbidden.'), MAX_TTL));
|
||||
if ($values['ttl'] < NS_MIN_TTL)
|
||||
output(403, sprintf(_('TTLs shorter than %s seconds are forbidden.'), NS_MIN_TTL));
|
||||
if ($values['ttl'] > NS_MAX_TTL)
|
||||
output(403, sprintf(_('TTLs longer than %s seconds are forbidden.'), NS_MAX_TTL));
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
function nsListUserZones() {
|
||||
function nsListUserZones(): array {
|
||||
if (isset($_SESSION['id']))
|
||||
return query('select', 'zones', ['username' => $_SESSION['id']], 'zone');
|
||||
return [];
|
||||
}
|
||||
|
||||
function nsCheckZonePossession($zone) {
|
||||
function nsCheckZonePossession(string $zone): void {
|
||||
checkAbsoluteDomainFormat($zone);
|
||||
|
||||
if (!in_array($zone, nsListUserZones(), true))
|
||||
output(403, 'You don\'t own this zone on the name server.');
|
||||
}
|
||||
|
||||
function nsDeleteZone($zone, $user_id) {
|
||||
function nsDeleteZone(string $zone, string $user_id): void {
|
||||
// Remove from Knot configuration
|
||||
knotcConfExec([['conf-unset', 'zone[' . $zone . ']']]);
|
||||
|
||||
|
@ -71,6 +73,8 @@ function nsDeleteZone($zone, $user_id) {
|
|||
if ($code !== 0)
|
||||
output(500, 'Failed to purge zone data.');
|
||||
|
||||
query('delete', 'ns-syncs', ['destination' => $zone]);
|
||||
|
||||
// Remove from database
|
||||
query('delete', 'zones', [
|
||||
'zone' => $zone,
|
||||
|
|
73
fn/reg.php
73
fn/reg.php
|
@ -1,26 +1,31 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
const SUBDOMAIN_REGEX = '^(?!\-)(?!..\-\-)[a-z0-9-]{4,63}(?<!\-)$';
|
||||
|
||||
function regListUserDomains() {
|
||||
const REG_TEXTAREA_MAX_CHARACTERS = 5000;
|
||||
const REG_ALLOWED_TYPES = ['NS', 'DS', 'AAAA', 'A'];
|
||||
|
||||
function regListUserDomains(): array {
|
||||
if (isset($_SESSION['id']))
|
||||
return query('select', 'registry', ['username' => $_SESSION['id']], 'domain');
|
||||
return [];
|
||||
}
|
||||
|
||||
function regCheckDomainPossession($domain) {
|
||||
function regCheckDomainPossession(string $domain): void {
|
||||
if (in_array($domain, regListUserDomains(), true) !== true)
|
||||
output(403, 'You don\'t own this domain on the registry.');
|
||||
}
|
||||
|
||||
function regDeleteDomain($domain, $user_id) {
|
||||
function regStripDomain(string $domain, string $content): string {
|
||||
return preg_replace('/^(?:[a-z0-9._-]+\.)?' . preg_quote($domain, '/') . '[\t ]+.+$/Dm', '', $content);
|
||||
}
|
||||
|
||||
function regDeleteDomain(string $domain, string $user_id): void {
|
||||
// Delete domain from registry file
|
||||
$path = CONF['reg']['suffixes_path'] . '/' . regParseDomain($domain)['suffix'] . 'zone';
|
||||
$content = file_get_contents($path);
|
||||
if ($content === false)
|
||||
if (($content = file_get_contents($path)) === false)
|
||||
output(500, 'Failed to read current registry file.');
|
||||
$content = preg_replace('/^(?:[a-z0-9._-]+\.)?' . preg_quote($domain, '/') . '[\t ]+.+$/Dm', '', $content);
|
||||
if (file_put_contents($path, $content) === false)
|
||||
if (file_put_contents($path, regStripDomain($domain, $content)) === false)
|
||||
output(500, 'Failed to write new registry file.');
|
||||
|
||||
try {
|
||||
|
@ -46,7 +51,7 @@ function regDeleteDomain($domain, $user_id) {
|
|||
}
|
||||
}
|
||||
|
||||
function regParseDomain($domain) {
|
||||
function regParseDomain(string $domain): array {
|
||||
$parts = explode('.', $domain, 2);
|
||||
$subdomain = $parts[0];
|
||||
$suffix = $parts[1];
|
||||
|
@ -59,3 +64,53 @@ function regParseDomain($domain) {
|
|||
'suffix' => $suffix,
|
||||
];
|
||||
}
|
||||
|
||||
function regParseRecord(string $domain, array $record): array {
|
||||
checkAbsoluteDomainFormat($record['domain']);
|
||||
|
||||
if ($record['domain'] !== $_POST['domain']) {
|
||||
if ($record['type'] !== 'ip')
|
||||
output(403, _('You can only set a NS/DS record for an apex domain.'));
|
||||
else if (!str_ends_with($record['domain'], '.' . $domain))
|
||||
output(403, _('You can\'t set a record for another domain.'));
|
||||
}
|
||||
|
||||
$new_rec = [
|
||||
$record['domain'],
|
||||
CONF['reg']['ttl'],
|
||||
$record['type'],
|
||||
];
|
||||
if ($record['type'] === 'DS') {
|
||||
if (!in_array($record['algo'], ['8', '13', '14', '15', '16'], true))
|
||||
output(403, 'Wrong value for <code>algo</code>.');
|
||||
|
||||
if ((preg_match('/^[0-9]{1,6}$/D', $record['keytag'])) !== 1 OR !($record['keytag'] >= 1) OR !($record['keytag'] <= 65535))
|
||||
output(403, 'Wrong value for <code>keytag</code>.');
|
||||
|
||||
if ($record['dt'] !== '2' AND $record['dt'] !== '4')
|
||||
output(403, 'Wrong value for <code>dt</code>.');
|
||||
|
||||
if (preg_match('/^(?:[0-9a-fA-F]{64}|[0-9a-fA-F]{96})$/D', $record['key']) !== 1)
|
||||
output(403, 'Wrong value for <code>key</code>.');
|
||||
|
||||
return [
|
||||
...$new_rec,
|
||||
$record['keytag'],
|
||||
$record['algo'],
|
||||
$record['dt'],
|
||||
$record['key'],
|
||||
];
|
||||
}
|
||||
if ($record['type'] === 'NS')
|
||||
return [
|
||||
...$new_rec,
|
||||
formatAbsoluteDomain($record['ns']),
|
||||
];
|
||||
if ($record['type'] === 'ip')
|
||||
return [
|
||||
$record['domain'],
|
||||
CONF['reg']['ttl'],
|
||||
checkIpFormat($record['ip']),
|
||||
$record['ip'],
|
||||
];
|
||||
}
|
||||
|
|
6
init.php
6
init.php
|
@ -1,13 +1,13 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
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);
|
||||
error_log((string) $e);
|
||||
http_response_code(500);
|
||||
echo '<h1>Error</h1>An error occured.';
|
||||
echo '<h1>Error</h1><p>An error occured.<p>';
|
||||
});
|
||||
register_shutdown_function(function () { // Also catch fatal errors
|
||||
if (($error = error_get_last()) !== NULL)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<?php // Test that the current setup is working
|
||||
<?php declare(strict_types=1);
|
||||
// Test that the current setup is working
|
||||
|
||||
require 'init.php';
|
||||
require __DIR__ . '/../init.php';
|
||||
|
||||
const SFTP = '/usr/bin/sftp';
|
||||
const SSHPASS = '/usr/bin/sshpass';
|
||||
|
@ -18,7 +19,7 @@ if (preg_match('/^;; Flags: qr rd ra ad;/Dm', implode("\n", $output)) !== 1)
|
|||
|
||||
define('COOKIE_FILE', sys_get_temp_dir() . '/cookie-' . bin2hex(random_bytes(16)) . '.txt');
|
||||
|
||||
function curlTest($address, $post = [], $tor = false) {
|
||||
function curlTest(string $address, array $post = [], bool $tor = false): string {
|
||||
$req = curl_init();
|
||||
curl_setopt($req, CURLOPT_RETURNTRANSFER, true);
|
||||
|
||||
|
@ -47,7 +48,7 @@ function curlTest($address, $post = [], $tor = false) {
|
|||
if ($status_code >= 400 OR $result === false) {
|
||||
var_dump($result);
|
||||
var_dump(curl_error($req));
|
||||
exit($address . ' test failed with status code ' . $status_code . LF);
|
||||
throw new Exception($address . ' test failed with status code ' . $status_code);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
@ -60,13 +61,6 @@ curlTest('/auth/register', [
|
|||
'password' => $password,
|
||||
]);
|
||||
|
||||
curlTest('/auth/logout', []);
|
||||
|
||||
curlTest('/auth/login', [
|
||||
'username' => $username,
|
||||
'password' => $password,
|
||||
]);
|
||||
|
||||
$new_password = bin2hex(random_bytes(16));
|
||||
curlTest('/auth/password', [
|
||||
'current-password' => $password,
|
||||
|
@ -74,9 +68,20 @@ curlTest('/auth/password', [
|
|||
]);
|
||||
$password = $new_password;
|
||||
|
||||
curlTest('/auth/register', [
|
||||
'username' => $username . '2',
|
||||
'password' => $password,
|
||||
]);
|
||||
curlTest('/auth/logout', []);
|
||||
|
||||
curlTest('/auth/login', [
|
||||
'username' => $username,
|
||||
'password' => $password,
|
||||
]);
|
||||
|
||||
curlTest('/auth/username', [
|
||||
'current-password' => $password,
|
||||
'new-username' => $username . '2',
|
||||
'new-username' => $username . '3',
|
||||
]);
|
||||
curlTest('/auth/username', [
|
||||
'current-password' => $password,
|
||||
|
@ -85,7 +90,9 @@ curlTest('/auth/username', [
|
|||
|
||||
echo 'Created account with username "' . $username . '" and password "' . $password . '".' . LF;
|
||||
|
||||
function testReg() {
|
||||
function testReg(): string {
|
||||
global $username, $password;
|
||||
|
||||
$subdomain = bin2hex(random_bytes(16));
|
||||
|
||||
curlTest('/reg/register', [
|
||||
|
@ -116,10 +123,43 @@ function testReg() {
|
|||
'ns' => 'ns1.servnest.invalid.',
|
||||
]);
|
||||
|
||||
{ // Domain transfer
|
||||
curlTest('/auth/logout', []);
|
||||
curlTest('/auth/login', [
|
||||
'username' => $username . '2',
|
||||
'password' => $password,
|
||||
]);
|
||||
preg_match('#\<code\>(?<token>[0-9a-z-]{16,128}\._transfer-verification\.' . preg_quote(CORE_DOMAIN, '#') . '\.)\</code\>#', curlTest('/reg/transfer', []), $matches);
|
||||
|
||||
curlTest('/auth/logout', []);
|
||||
curlTest('/auth/login', [
|
||||
'username' => $username,
|
||||
'password' => $password,
|
||||
]);
|
||||
curlTest('/reg/ns', [
|
||||
'action' => 'add',
|
||||
'domain' => $domain,
|
||||
'ns' => $matches['token'],
|
||||
]);
|
||||
|
||||
curlTest('/auth/logout', []);
|
||||
curlTest('/auth/login', [
|
||||
'username' => $username . '2',
|
||||
'password' => $password,
|
||||
]);
|
||||
curlTest('/reg/transfer', [
|
||||
'subdomain' => $subdomain,
|
||||
'suffix' => SUFFIX,
|
||||
'ns' => $matches['token'],
|
||||
]);
|
||||
|
||||
$username = $username . '2';
|
||||
}
|
||||
|
||||
return $domain;
|
||||
}
|
||||
|
||||
function testNs($domain) {
|
||||
function testNs(string $domain): void {
|
||||
foreach (CONF['ns']['servers'] as $ns)
|
||||
curlTest('/reg/ns', [
|
||||
'action' => 'add',
|
||||
|
@ -164,8 +204,8 @@ function testNs($domain) {
|
|||
exit('Error: /ns/caa: CAA record not set' . LF);
|
||||
|
||||
curlTest('/ns/edit', [
|
||||
'zone' => $domain,
|
||||
'zone-content' => 'aaaa.' . $domain . ' 3600 AAAA ' . CONF['ht']['ipv6_address'] . "\r\n"
|
||||
'domain' => $domain,
|
||||
'records' => 'aaaa.' . $domain . ' 3600 AAAA ' . CONF['ht']['ipv6_address'] . "\r\n"
|
||||
. '@ 86400 NS ' . CONF['ns']['servers'][0] . "\r\n",
|
||||
]);
|
||||
exescape([
|
||||
|
@ -178,7 +218,7 @@ function testNs($domain) {
|
|||
exit('Error: /ns/edit: AAAA record not set' . LF);
|
||||
}
|
||||
|
||||
function testHt($username, $password) {
|
||||
function testHt(string $username, string $password): void {
|
||||
define('TEST_CONTENT', 'test-' . bin2hex(random_bytes(16)));
|
||||
|
||||
file_put_contents(sys_get_temp_dir() . '/index.html', TEST_CONTENT);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
require 'init.php';
|
||||
<?php declare(strict_types=1);
|
||||
require __DIR__ . '/../init.php';
|
||||
|
||||
const MAX_TESTING_ACCOUNT_AGE = 86400 * 10;
|
||||
|
||||
|
|
42
jobs/ns-sync.php
Normal file
42
jobs/ns-sync.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php declare(strict_types=1);
|
||||
require __DIR__ . '/../init.php';
|
||||
|
||||
foreach (query('select', 'ns-syncs') as $sync) {
|
||||
$zone_raw = file_get_contents(CONF['ns']['knot_zones_path'] . '/' . $sync['destination'] . 'zone');
|
||||
if ($zone_raw === false)
|
||||
output(403, 'Unable to read zone file.');
|
||||
|
||||
foreach (['AAAA', 'A', 'CAA'] as $type) {
|
||||
// Get source/distant records
|
||||
try {
|
||||
$results = kdig(name: $sync['source'], type: $type);
|
||||
} catch (KdigException) {
|
||||
fwrite(STDERR, $sync['source'] . ' resolution failed.' . LF);
|
||||
break;
|
||||
}
|
||||
if ($results['AD'] !== 1) {
|
||||
fwrite(STDERR, $sync['source'] . ' skipped: not signed using DNSSEC.' . LF);
|
||||
continue;
|
||||
}
|
||||
$source_records = array_column($results['answerRRs'] ?? [], 'rdata' . $type);
|
||||
|
||||
// Get destination/local records
|
||||
$dest_records = array_column(parseZoneFile($zone_raw, [$type], $sync['destination'], false), 3);
|
||||
|
||||
// Add source records that are not yet in destination
|
||||
foreach (array_diff($source_records, $dest_records) as $value_to_add)
|
||||
knotcZoneExec($sync['destination'], [
|
||||
$sync['destination'],
|
||||
SYNC_TTL,
|
||||
$type,
|
||||
$value_to_add,
|
||||
], 'add');
|
||||
// Delete destination records that are not part of source anymore
|
||||
foreach (array_diff($dest_records, $source_records) as $value_to_delete)
|
||||
knotcZoneExec($sync['destination'], [
|
||||
$sync['destination'],
|
||||
$type,
|
||||
$value_to_delete,
|
||||
], 'delete');
|
||||
}
|
||||
}
|
57
jobs/reg-cds.php
Normal file
57
jobs/reg-cds.php
Normal file
|
@ -0,0 +1,57 @@
|
|||
<?php declare(strict_types=1);
|
||||
/*
|
||||
RFC 7344: Automating DNSSEC Delegation Trust Maintenance
|
||||
RFC 8078: Managing DS Records from the Parent via CDS/CDNSKEY
|
||||
*/
|
||||
require __DIR__ . '/../init.php';
|
||||
|
||||
foreach (query('select', 'registry') as $entry) {
|
||||
$suffix = regParseDomain($entry['domain'])['suffix'];
|
||||
|
||||
// Get child/distant records
|
||||
try {
|
||||
$results = kdig(name: $entry['domain'], type: 'CDS');
|
||||
} catch (KdigException) {
|
||||
fwrite(STDERR, $entry['domain'] . ' resolution failed.' . LF);
|
||||
continue;
|
||||
}
|
||||
if ($results['AD'] !== 1) // No DNSSEC
|
||||
continue;
|
||||
$cds_records = array_column($results['answerRRs'] ?? [], 'rdataCDS');
|
||||
|
||||
// Skip if no updates
|
||||
if ($cds_records === [])
|
||||
continue;
|
||||
|
||||
// Get parent/local CDS records
|
||||
$zone_raw = file_get_contents(CONF['reg']['suffixes_path'] . '/' . $suffix . 'zone');
|
||||
if ($zone_raw === false)
|
||||
output(403, 'Unable to read zone file.');
|
||||
$pds_records = array_column(parseZoneFile($zone_raw, ['DS'], $entry['domain'], false), 3);
|
||||
|
||||
if ($cds_records === ['0 0 0 0'])
|
||||
// Delete every parent DS records
|
||||
foreach ($pds_records as $value_to_delete)
|
||||
knotcZoneExec($suffix, [
|
||||
$entry['domain'],
|
||||
'DS',
|
||||
$value_to_delete,
|
||||
], 'delete');
|
||||
else {
|
||||
// Add child DS records that are not yet in parent
|
||||
foreach (array_diff($cds_records, $pds_records) as $value_to_add)
|
||||
knotcZoneExec($suffix, [
|
||||
$entry['domain'],
|
||||
CONF['reg']['ttl'],
|
||||
'DS',
|
||||
$value_to_add,
|
||||
], 'add');
|
||||
// Delete parent DS records that are not part of child anymore
|
||||
foreach (array_diff($pds_records, $cds_records) as $value_to_delete)
|
||||
knotcZoneExec($suffix, [
|
||||
$entry['domain'],
|
||||
'DS',
|
||||
$value_to_delete,
|
||||
], 'delete');
|
||||
}
|
||||
}
|
61
jobs/reg-csync.php
Normal file
61
jobs/reg-csync.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php declare(strict_types=1);
|
||||
/*
|
||||
RFC 7477: Child-to-Parent Synchronization in DNS
|
||||
*/
|
||||
require __DIR__ . '/../init.php';
|
||||
|
||||
foreach (query('select', 'registry') as $entry) {
|
||||
$suffix = regParseDomain($entry['domain'])['suffix'];
|
||||
|
||||
// Get child/distant CSYNC records
|
||||
try {
|
||||
$results = kdig(name: $entry['domain'], type: 'CSYNC');
|
||||
} catch (KdigException) {
|
||||
fwrite(STDERR, $entry['domain'] . ' CSYNC resolution failed.' . LF);
|
||||
continue;
|
||||
}
|
||||
if ($results['AD'] !== 1)
|
||||
continue;
|
||||
if (count($results['answerRRs'] ?? []) !== 1) // Parental agents MUST ignore a child's CSYNC RDATA set if multiple CSYNC resource records are found; only a single CSYNC record should ever be present.
|
||||
continue;
|
||||
list($serial, $flags, $types) = explode(' ', $results['answerRRs'][0]['rdataCSYNC'], 3);
|
||||
if ($flags !== '1')
|
||||
continue; // Skip unsupported flags
|
||||
if ($types !== 'NS')
|
||||
continue; // Skip unsupported types
|
||||
|
||||
// Get child/distant NS records
|
||||
try {
|
||||
$results = kdig(name: $entry['domain'], type: 'NS');
|
||||
} catch (KdigException) {
|
||||
fwrite(STDERR, $entry['domain'] . ' NS resolution failed.' . LF);
|
||||
continue;
|
||||
}
|
||||
if ($results['AD'] !== 1)
|
||||
continue;
|
||||
$child_ns_records = array_column($results['answerRRs'] ?? [], 'rdataNS');
|
||||
if (count($child_ns_records) === []) // Parental agents MUST NOT perform NS updates if there are no NS records returned in a query, as verified by DNSSEC denial-of-existence protection.
|
||||
continue;
|
||||
|
||||
// Get parent/local NS records
|
||||
$zone_raw = file_get_contents(CONF['reg']['suffixes_path'] . '/' . $suffix . 'zone');
|
||||
if ($zone_raw === false)
|
||||
output(403, 'Unable to read zone file.');
|
||||
$parent_ns_records = array_column(parseZoneFile($zone_raw, ['NS'], $entry['domain'], false), 3);
|
||||
|
||||
// Add child NS records that are not yet in parent
|
||||
foreach (array_diff($child_ns_records, $parent_ns_records) as $value_to_add)
|
||||
knotcZoneExec($suffix, [
|
||||
$entry['domain'],
|
||||
CONF['reg']['ttl'],
|
||||
'NS',
|
||||
$value_to_add,
|
||||
], 'add');
|
||||
// Delete parent NS records that are not part of child anymore
|
||||
foreach (array_diff($parent_ns_records, $child_ns_records) as $value_to_delete)
|
||||
knotcZoneExec($suffix, [
|
||||
$entry['domain'],
|
||||
'NS',
|
||||
$value_to_delete,
|
||||
], 'delete');
|
||||
}
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
12
pages.php
12
pages.php
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
define('PAGES', [
|
||||
'index' => [
|
||||
|
@ -63,6 +63,11 @@ define('PAGES', [
|
|||
'description' => _('Print every record related to a domain and served by the registry'),
|
||||
'tokens_account_cost' => 60,
|
||||
],
|
||||
'edit' => [
|
||||
'title' => _('Edit records'),
|
||||
'description' => _('Set registry records to delegate a domain to chosen name servers'),
|
||||
'tokens_account_cost' => 900,
|
||||
],
|
||||
'ns' => [
|
||||
'title' => sprintf(_('%s records'), '<abbr title="Name Server">NS</abbr>'),
|
||||
'description' => sprintf(_('Indicate the name servers of a %s subdomain'), '<code>' . key(CONF['reg']['suffixes']) . '</code>'),
|
||||
|
@ -163,6 +168,11 @@ define('PAGES', [
|
|||
'description' => _('Store geographic coordinates'),
|
||||
'tokens_account_cost' => 120,
|
||||
],
|
||||
'sync' => [
|
||||
'title' => sprintf(_('Synchronized records')),
|
||||
'description' => _('Regularly fetch distant records and update them to a local zone'),
|
||||
'tokens_account_cost' => 900,
|
||||
],
|
||||
],
|
||||
'ht' => [
|
||||
'index' => [
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
if ($_SESSION['type'] !== 'testing')
|
||||
output(403, _('This account is already approved.'));
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
checkPasswordFormat($_POST['password']);
|
||||
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
<?php
|
||||
|
||||
logout();
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
checkPasswordFormat($_POST['new-password']);
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
if (CONF['common']['services']['auth'] !== 'enabled')
|
||||
output(403, _('Registrations are currently closed on this installation.'));
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
if (checkPassword($_SESSION['id'], $_POST['current-password']) !== true)
|
||||
output(403, _('Wrong current password.'));
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
checkUsernameFormat($_POST['new-username']);
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
$_POST['domain'] = formatDomain($_POST['domain']);
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
if (dirsStatuses('onion')[$_POST['dir']] !== false)
|
||||
output(403, 'Wrong value for <code>dir</code>.');
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
if (dirsStatuses('subdomain')[$_POST['dir']] !== false)
|
||||
output(403, 'Wrong value for <code>dir</code>.');
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
if (dirsStatuses('subpath')[$_POST['dir']] !== false)
|
||||
output(403, 'Wrong value for <code>dir</code>.');
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
if (preg_match('/^(?<type>subpath|subdomain|onion|dns):(?<address>[a-z0-9._-]{1,256})$/D', $_POST['site'], $site) !== 1)
|
||||
output(403, 'Malformed value for <code>site</code>.');
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
$el_nb = count($_POST['keys']);
|
||||
if ($el_nb < 1 OR $el_nb > 8)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
$values = nsParseCommonRequirements();
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
$values = nsParseCommonRequirements();
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
$values = nsParseCommonRequirements();
|
||||
|
||||
|
|
|
@ -1,60 +1,62 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
nsCheckZonePossession($_POST['zone']);
|
||||
nsCheckZonePossession($_POST['domain']);
|
||||
|
||||
if (isset($_POST['zone-content'])) { // Update zone
|
||||
$path = CONF['ns']['knot_zones_path'] . '/' . $_POST['domain'] . 'zone';
|
||||
|
||||
if (isset($_POST['records'])) {
|
||||
|
||||
// Get current SOA record
|
||||
$current_zone_content = file_get_contents(CONF['ns']['knot_zones_path'] . '/' . $_POST['zone'] . 'zone');
|
||||
$current_zone_content = file_get_contents($path);
|
||||
if ($current_zone_content === false)
|
||||
output(500, 'Unable to read zone file.');
|
||||
if (preg_match('/^(?<soa>' . preg_quote($_POST['zone'], '/') . '[\t ]+[0-9]{1,16}[\t ]+SOA[\t ]+.+)$/Dm', $current_zone_content, $matches) !== 1)
|
||||
if (preg_match('/^(?<soa>' . preg_quote($_POST['domain'], '/') . '[\t ]+[0-9]{1,16}[\t ]+SOA[\t ]+.+)$/Dm', $current_zone_content, $matches) !== 1)
|
||||
output(500, 'Unable to get current SOA record from zone file.');
|
||||
|
||||
// Generate new zone content
|
||||
// Generate new content
|
||||
$new_zone_content = $matches['soa'] . LF;
|
||||
if (strlen($_POST['zone-content']) > ZONE_MAX_CHARACTERS)
|
||||
output(403, sprintf(_('The zone is limited to %s characters.'), ZONE_MAX_CHARACTERS));
|
||||
foreach (explode("\r\n", $_POST['zone-content']) as $line) {
|
||||
if ($line === '') continue;
|
||||
if (preg_match('/^(?<domain>[a-z0-9@._-]{1,256})(?:[\t ]+(?<ttl>[0-9]{1,16}))?(?:[\t ]+IN)?[\t ]+(?<type>[A-Z]{1,16})[\t ]+(?<value>.+)$/D', $line, $matches) !== 1)
|
||||
output(403, _('The following line does not match the expected format: ') . '<code>' . htmlspecialchars($line) . '</code>');
|
||||
if (in_array($matches['type'], ALLOWED_TYPES, true) !== true)
|
||||
if (strlen($_POST['records']) > NS_TEXTAREA_MAX_CHARACTERS)
|
||||
output(403, sprintf(_('The zone is limited to %s characters.'), NS_TEXTAREA_MAX_CHARACTERS));
|
||||
foreach (explode("\r\n", $_POST['records']) as $record) {
|
||||
if ($record === '') continue;
|
||||
if (preg_match('/^(?<domain>[a-z0-9@._-]{1,256})(?:[\t ]+(?<ttl>[0-9]{1,16}))?(?:[\t ]+IN)?[\t ]+(?<type>[A-Z]{1,16})[\t ]+(?<value>.+)$/D', $record, $matches) !== 1)
|
||||
output(403, _('The following line does not match the expected format: ') . '<code>' . htmlspecialchars($record) . '</code>');
|
||||
if (in_array($matches['type'], NS_ALLOWED_TYPES, true) !== true)
|
||||
output(403, sprintf(_('The %s type is not allowed.'), '<code>' . $matches['type'] . '</code>'));
|
||||
if ($matches['ttl'] !== '' AND $matches['ttl'] < MIN_TTL)
|
||||
output(403, sprintf(_('TTLs shorter than %s seconds are forbidden.'), MIN_TTL));
|
||||
if ($matches['ttl'] !== '' AND $matches['ttl'] > MAX_TTL)
|
||||
output(403, sprintf(_('TTLs longer than %s seconds are forbidden.'), MAX_TTL));
|
||||
$new_zone_content .= $matches['domain'] . ' ' . (($matches['ttl'] === '') ? DEFAULT_TTL : $matches['ttl']) . ' ' . $matches['type'] . ' ' . $matches['value'] . LF;
|
||||
if ($matches['ttl'] !== '' AND $matches['ttl'] < NS_MIN_TTL)
|
||||
output(403, sprintf(_('TTLs shorter than %s seconds are forbidden.'), NS_MIN_TTL));
|
||||
if ($matches['ttl'] !== '' AND $matches['ttl'] > NS_MAX_TTL)
|
||||
output(403, sprintf(_('TTLs longer than %s seconds are forbidden.'), NS_MAX_TTL));
|
||||
$new_zone_content .= $matches['domain'] . ' ' . (($matches['ttl'] === '') ? NS_DEFAULT_TTL : $matches['ttl']) . ' ' . $matches['type'] . ' ' . $matches['value'] . LF;
|
||||
}
|
||||
|
||||
// Send the zone content to kzonecheck's stdin
|
||||
$process = proc_open(CONF['ns']['kzonecheck_path'] . ' --origin ' . $_POST['zone'] . ' --dnssec off -', [0 => ['pipe', 'r']], $pipes);
|
||||
$process = proc_open(CONF['ns']['kzonecheck_path'] . ' --origin ' . escapeshellarg($_POST['domain']) . ' --dnssec off -', [0 => ['pipe', 'r']], $pipes);
|
||||
if (is_resource($process) !== true)
|
||||
output(500, 'Can\'t spawn kzonecheck.');
|
||||
fwrite($pipes[0], $new_zone_content);
|
||||
fclose($pipes[0]);
|
||||
if (proc_close($process) !== 0)
|
||||
output(403, _('Sent zone content is not correct (according to <code>kzonecheck</code>).'));
|
||||
output(403, _('Sent content is not correct (according to <code>kzonecheck</code>).'));
|
||||
|
||||
ratelimit();
|
||||
|
||||
knotc(['zone-freeze', $_POST['zone']], $output, $return_code);
|
||||
knotc(['zone-freeze', $_POST['domain']], $output, $return_code);
|
||||
if ($return_code !== 0)
|
||||
output(500, 'Failed to freeze zone file.', $output);
|
||||
|
||||
knotc(['zone-flush', $_POST['zone']], $output, $return_code);
|
||||
knotc(['zone-flush', $_POST['domain']], $output, $return_code);
|
||||
if ($return_code !== 0)
|
||||
output(500, 'Failed to flush zone file.', $output);
|
||||
|
||||
if (file_put_contents(CONF['ns']['knot_zones_path'] . '/' . $_POST['zone'] . 'zone', $new_zone_content) === false)
|
||||
if (file_put_contents($path, $new_zone_content) === false)
|
||||
output(500, 'Failed to write zone file.');
|
||||
|
||||
knotc(['zone-reload', $_POST['zone']], $output, $return_code);
|
||||
knotc(['zone-reload', $_POST['domain']], $output, $return_code);
|
||||
if ($return_code !== 0)
|
||||
output(500, 'Failed to reload zone file.', $output);
|
||||
|
||||
knotc(['zone-thaw', $_POST['zone']], $output, $return_code);
|
||||
knotc(['zone-thaw', $_POST['domain']], $output, $return_code);
|
||||
if ($return_code !== 0)
|
||||
output(500, 'Failed to thaw zone file.', $output);
|
||||
|
||||
|
@ -63,18 +65,17 @@ if (isset($_POST['zone-content'])) { // Update zone
|
|||
|
||||
// Display zone
|
||||
|
||||
$zone_content = file_get_contents(CONF['ns']['knot_zones_path'] . '/' . $_POST['zone'] . 'zone');
|
||||
if ($zone_content === false)
|
||||
if (($records = file_get_contents($path)) === false)
|
||||
output(500, 'Unable to read zone file.');
|
||||
|
||||
$data['zone_content'] = '';
|
||||
foreach (explode(LF, $zone_content) as $zone_line) {
|
||||
$data['records'] = '';
|
||||
foreach (explode(LF, $records) as $zone_line) {
|
||||
if (empty($zone_line) OR str_starts_with($zone_line, ';'))
|
||||
continue;
|
||||
if (preg_match('/^(?:(?:[a-z0-9_-]{1,63}\.){1,127})?' . preg_quote($_POST['zone'], '/') . '[\t ]+[0-9]{1,8}[\t ]+(?<type>[A-Z]{1,16})[\t ]+.+$/D', $zone_line, $matches)) {
|
||||
if (in_array($matches['type'], ALLOWED_TYPES, true) !== true)
|
||||
if (preg_match('/^(?:(?:[a-z0-9_-]{1,63}\.){1,127})?' . preg_quote($_POST['domain'], '/') . '[\t ]+[0-9]{1,8}[\t ]+(?<type>[A-Z]{1,16})[\t ]+.+$/D', $zone_line, $matches)) {
|
||||
if (in_array($matches['type'], NS_ALLOWED_TYPES, true) !== true)
|
||||
continue;
|
||||
$data['zone_content'] .= $zone_line . LF;
|
||||
$data['records'] .= $zone_line . LF;
|
||||
}
|
||||
}
|
||||
$data['zone_content'] .= LF;
|
||||
$data['records'] .= LF;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
$values = nsParseCommonRequirements();
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
$values = nsParseCommonRequirements();
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
$values = nsParseCommonRequirements();
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
$values = nsParseCommonRequirements();
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
nsCheckZonePossession($_POST['zone']);
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
$values = nsParseCommonRequirements();
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
$values = nsParseCommonRequirements();
|
||||
|
||||
|
|
41
pg-act/ns/sync.php
Normal file
41
pg-act/ns/sync.php
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
$el_nb = count($_POST['syncs']);
|
||||
if ($el_nb < 1 OR $el_nb > 8)
|
||||
output(403, 'Wrong elements number.');
|
||||
|
||||
foreach ($_POST['syncs'] as $i => $sync) {
|
||||
if (($sync['source'] ?? '') === '') {
|
||||
unset($_POST['syncs'][$i]);
|
||||
continue;
|
||||
}
|
||||
$sync['source'] = formatAbsoluteDomain($sync['source']);
|
||||
nsCheckZonePossession($sync['destination']);
|
||||
}
|
||||
$syncs = array_values($_POST['syncs']);
|
||||
|
||||
$destinations = array_column($syncs, 'destination');
|
||||
if (count($destinations) !== count(array_unique($destinations)))
|
||||
output(403, _('Multiple source domains can\'t be applied to the same target domain.'));
|
||||
|
||||
rateLimit();
|
||||
|
||||
try {
|
||||
DB->beginTransaction();
|
||||
|
||||
query('delete', 'ns-syncs', ['username' => $_SESSION['id']]);
|
||||
|
||||
foreach ($syncs as $sync)
|
||||
insert('ns-syncs', [
|
||||
'username' => $_SESSION['id'],
|
||||
'source' => $sync['source'],
|
||||
'destination' => $sync['destination'],
|
||||
]);
|
||||
|
||||
DB->commit();
|
||||
} catch (Exception $e) {
|
||||
DB->rollback();
|
||||
output(500, 'Database error.', [$e->getMessage()]);
|
||||
}
|
||||
|
||||
output(200, _('Synchronized records updated.'));
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
$values = nsParseCommonRequirements();
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
$values = nsParseCommonRequirements();
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
$_POST['domain'] = formatAbsoluteDomain($_POST['domain']);
|
||||
|
||||
|
@ -41,18 +41,19 @@ insert('zones', [
|
|||
$knotZonePath = CONF['ns']['knot_zones_path'] . '/' . $_POST['domain'] . 'zone';
|
||||
$knotZone = implode(' ', [
|
||||
$_POST['domain'],
|
||||
SOA_VALUES['ttl'],
|
||||
NS_SOA_VALUES['ttl'],
|
||||
'SOA',
|
||||
CONF['ns']['servers'][0],
|
||||
SOA_VALUES['email'],
|
||||
NS_SOA_VALUES['email'],
|
||||
1,
|
||||
SOA_VALUES['refresh'],
|
||||
SOA_VALUES['retry'],
|
||||
SOA_VALUES['expire'],
|
||||
SOA_VALUES['negative'],
|
||||
NS_SOA_VALUES['refresh'],
|
||||
NS_SOA_VALUES['retry'],
|
||||
NS_SOA_VALUES['expire'],
|
||||
NS_SOA_VALUES['negative'],
|
||||
]) . LF;
|
||||
foreach (CONF['ns']['servers'] as $server)
|
||||
$knotZone .= $_POST['domain'] . ' 86400 NS ' . $server . LF;
|
||||
$knotZone .= $_POST['domain'] . ' 86400 CSYNC 0 1 NS' . LF;
|
||||
if (is_int(file_put_contents($knotZonePath, $knotZone)) !== true)
|
||||
output(500, 'Failed to write new zone file.');
|
||||
if (chmod($knotZonePath, 0660) !== true)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
nsCheckZonePossession($_POST['zone']);
|
||||
|
||||
|
|
|
@ -1,30 +1,12 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
if (!in_array($_POST['algo'], ['8', '13', '14', '15', '16'], true))
|
||||
output(403, 'Wrong value for <code>algo</code>.');
|
||||
|
||||
$_POST['keytag'] = intval($_POST['keytag']);
|
||||
if ((preg_match('/^[0-9]{1,6}$/D', $_POST['keytag'])) !== 1 OR !($_POST['keytag'] >= 1) OR !($_POST['keytag'] <= 65535))
|
||||
output(403, 'Wrong value for <code>keytag</code>.');
|
||||
|
||||
if ($_POST['dt'] !== '2' AND $_POST['dt'] !== '4')
|
||||
output(403, 'Wrong value for <code>dt</code>.');
|
||||
|
||||
if (preg_match('/^(?:[0-9a-fA-F]{64}|[0-9a-fA-F]{96})$/D', $_POST['key']) !== 1)
|
||||
output(403, 'Wrong value for <code>key</code>.');
|
||||
|
||||
regCheckDomainPossession($_POST['zone']);
|
||||
regCheckDomainPossession($_POST['domain']);
|
||||
|
||||
rateLimit();
|
||||
|
||||
knotcZoneExec(regParseDomain($_POST['zone'])['suffix'], [
|
||||
$_POST['zone'],
|
||||
CONF['reg']['ttl'],
|
||||
'DS',
|
||||
$_POST['keytag'],
|
||||
$_POST['algo'],
|
||||
$_POST['dt'],
|
||||
$_POST['key']
|
||||
]);
|
||||
knotcZoneExec(regParseDomain($_POST['domain'])['suffix'], regParseRecord($_POST['domain'], [
|
||||
'type' => 'DS',
|
||||
...$_POST,
|
||||
]));
|
||||
|
||||
output(200, _('Modification done.'));
|
||||
|
|
97
pg-act/reg/edit.php
Normal file
97
pg-act/reg/edit.php
Normal file
|
@ -0,0 +1,97 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
regCheckDomainPossession($_POST['domain']);
|
||||
|
||||
$suffix = regParseDomain($_POST['domain'])['suffix'];
|
||||
|
||||
$path = CONF['reg']['suffixes_path'] . '/' . $suffix . 'zone';
|
||||
|
||||
if (isset($_POST['records'])) {
|
||||
|
||||
// Generate new content
|
||||
$new_records = '';
|
||||
if (strlen($_POST['records']) > REG_TEXTAREA_MAX_CHARACTERS)
|
||||
output(403, sprintf(_('The zone is limited to %s characters.'), REG_TEXTAREA_MAX_CHARACTERS));
|
||||
foreach (explode("\r\n", $_POST['records']) as $record) {
|
||||
if ($record === '') continue;
|
||||
if (preg_match('/^(?<domain>[a-z0-9@._-]{1,256})(?:[\t ]+(?<ttl>[0-9]{1,16}))?(?:[\t ]+IN)?[\t ]+(?<type>[A-Z]{1,16})[\t ]+(?<value>.+)$/D', $record, $matches) !== 1)
|
||||
output(403, _('The following line does not match the expected format: ') . '<code>' . htmlspecialchars($record) . '</code>');
|
||||
if (in_array($matches['type'], REG_ALLOWED_TYPES, true) !== true)
|
||||
output(403, sprintf(_('The %s type is not allowed.'), '<code>' . $matches['type'] . '</code>'));
|
||||
if ($matches['type'] === 'DS' AND count($record_values = explode(' ', $matches['value'])) !== 4)
|
||||
output(403, _('A DS record expects 4 arguments.'));
|
||||
$new_records .= implode(' ', regParseRecord($_POST['domain'], [
|
||||
'domain' => $matches['domain'],
|
||||
'type' => match ($matches['type']) {
|
||||
'NS', 'DS' => $matches['type'],
|
||||
'AAAA', 'A' => 'ip',
|
||||
default => output(403, sprintf(_('The %s type is not allowed.'), '<code>' . $matches['type'] . '</code>')),
|
||||
},
|
||||
...match ($matches['type']) {
|
||||
'NS' => ['ns' => $matches['value']],
|
||||
'AAAA', 'A' => ['ip' => $matches['value']],
|
||||
'DS' => array_combine([
|
||||
'keytag',
|
||||
'algo',
|
||||
'dt',
|
||||
'key',
|
||||
], $record_values),
|
||||
},
|
||||
])) . LF;
|
||||
}
|
||||
|
||||
// Send the zone content to kzonecheck's stdin
|
||||
$process = proc_open(CONF['ns']['kzonecheck_path'] . ' --origin ' . escapeshellarg($suffix) . ' --dnssec off -', [0 => ['pipe', 'r']], $pipes);
|
||||
if (is_resource($process) !== true)
|
||||
output(500, 'Can\'t spawn kzonecheck.');
|
||||
$new = $suffix . ' 10800 SOA invalid. invalid. 0 21600 7200 3628800 3600' . LF . $suffix . ' 10800 NS invalid.' . LF . $new_records;
|
||||
fwrite($pipes[0], $new);
|
||||
fclose($pipes[0]);
|
||||
if (proc_close($process) !== 0)
|
||||
output(403, _('Sent content is not correct (according to <code>kzonecheck</code>).'));
|
||||
|
||||
ratelimit();
|
||||
|
||||
knotc(['zone-freeze', $suffix], $output, $return_code);
|
||||
if ($return_code !== 0)
|
||||
output(500, 'Failed to freeze zone file.', $output);
|
||||
|
||||
knotc(['zone-flush', $suffix], $output, $return_code);
|
||||
if ($return_code !== 0)
|
||||
output(500, 'Failed to flush zone file.', $output);
|
||||
|
||||
if (($zone_content = file_get_contents($path)) === false)
|
||||
output(500, 'Unable to read zone file.');
|
||||
|
||||
$zone_content = regStripDomain($_POST['domain'], $zone_content);
|
||||
|
||||
if (file_put_contents($path, $zone_content . LF . $new_records) === false)
|
||||
output(500, 'Failed to write zone file.');
|
||||
|
||||
knotc(['zone-reload', $suffix], $output, $return_code);
|
||||
if ($return_code !== 0)
|
||||
output(500, 'Failed to reload zone file.', $output);
|
||||
|
||||
knotc(['zone-thaw', $suffix], $output, $return_code);
|
||||
if ($return_code !== 0)
|
||||
output(500, 'Failed to thaw zone file.', $output);
|
||||
|
||||
usleep(1000000);
|
||||
}
|
||||
|
||||
// Display zone
|
||||
|
||||
if (($records = file_get_contents($path)) === false)
|
||||
output(500, 'Unable to read zone file.');
|
||||
|
||||
$data['records'] = '';
|
||||
foreach (explode(LF, $records) as $zone_line) {
|
||||
if (empty($zone_line) OR str_starts_with($zone_line, ';'))
|
||||
continue;
|
||||
if (preg_match('/^(?:(?:[a-z0-9_-]{1,63}\.){1,127})?' . preg_quote($_POST['domain'], '/') . '[\t ]+[0-9]{1,8}[\t ]+(?<type>[A-Z]{1,16})[\t ]+.+$/D', $zone_line, $matches)) {
|
||||
if (in_array($matches['type'], REG_ALLOWED_TYPES, true) !== true)
|
||||
continue;
|
||||
$data['records'] .= $zone_line . LF;
|
||||
}
|
||||
}
|
||||
$data['records'] .= LF;
|
|
@ -1,14 +1,13 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
regCheckDomainPossession($_POST['suffix']);
|
||||
regCheckDomainPossession($_POST['domain']);
|
||||
|
||||
rateLimit();
|
||||
|
||||
knotcZoneExec(regParseDomain($_POST['suffix'])['suffix'], [
|
||||
formatAbsoluteDomain(formatEndWithDot($_POST['subdomain']) . $_POST['suffix']),
|
||||
CONF['reg']['ttl'],
|
||||
checkIpFormat($_POST['ip']),
|
||||
$_POST['ip']
|
||||
]);
|
||||
knotcZoneExec(regParseDomain($_POST['domain'])['suffix'], regParseRecord($_POST['domain'], [
|
||||
'type' => 'ip',
|
||||
'domain' => formatAbsoluteDomain(formatEndWithDot($_POST['subdomain']) . $_POST['domain']),
|
||||
...$_POST,
|
||||
]));
|
||||
|
||||
output(200, _('Modification done.'));
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
regCheckDomainPossession($_POST['domain']);
|
||||
|
||||
rateLimit();
|
||||
|
||||
knotcZoneExec(regParseDomain($_POST['domain'])['suffix'], [
|
||||
$_POST['domain'],
|
||||
CONF['reg']['ttl'],
|
||||
'NS',
|
||||
formatAbsoluteDomain($_POST['ns'])
|
||||
]);
|
||||
knotcZoneExec(regParseDomain($_POST['domain'])['suffix'], regParseRecord($_POST['domain'], [
|
||||
'type' => 'NS',
|
||||
...$_POST,
|
||||
]));
|
||||
|
||||
output(200, _('Modification done.'));
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
regCheckDomainPossession($_POST['domain']);
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
if (preg_match('/' . SUBDOMAIN_REGEX . '/D', $_POST['subdomain']) !== 1)
|
||||
output(403, _('This format of subdomain is not allowed.'));
|
||||
|
@ -53,7 +53,7 @@ if ($blocked OR $registration_data !== [])
|
|||
if ($_POST['action'] !== 'register')
|
||||
message($message . ' ✔️ ' . _('This domain is open to registration!'));
|
||||
|
||||
function message($message) {
|
||||
function message(string $message): never {
|
||||
output(200, data: [
|
||||
'message' => '<p>' . $message . '</p>',
|
||||
'domain' => htmlspecialchars($_POST['subdomain']),
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
if (preg_match('/' . SUBDOMAIN_REGEX . '/D', $_POST['subdomain']) !== 1)
|
||||
output(403, _('This format of subdomain is not allowed.'));
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
regCheckDomainPossession($_POST['domain']);
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<p>
|
||||
<?= _('This form allows to use an approval key to validate your account. Approval keys are distributed by an administrator upon request.') ?>
|
||||
</p>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<?php displayIndex(); ?>
|
||||
|
||||
<h2 id="type"><?= _('Account type') ?></h2>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<p><?= _('New?') ?> <a href="register"><?= _('Create an account') ?></a></p>
|
||||
|
||||
<form method="post">
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
logout();
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<form method="post">
|
||||
<label for="current-password"><?= _('Current password') ?></label><br>
|
||||
<input required="" autocomplete="current-password" minlength="8" maxlength="1024" pattern="<?= PASSWORD_REGEX ?>" id="current-password" name="current-password" type="password" placeholder="<?= PLACEHOLDER_PASSWORD ?>"><br>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<p><?= _('Already have an account?') ?> <a href="login"><?= _('Log in') ?></a></p>
|
||||
|
||||
<?= (CONF['common']['services']['auth'] !== 'enabled') ? '<p><strong>' . _('Registrations are currently closed on this installation.') . '</strong></p>' : '' ?>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<p>
|
||||
<?= _('This will delete every resource managed by the current account, including registered domains, hosted DNS records, websites files and cryptographic keys for Onion services and DNSSEC.') ?>
|
||||
</p>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<form method="post">
|
||||
<label for="current-password"><?= _('Current password') ?></label><br>
|
||||
<input required="" autocomplete="current-password" minlength="8" maxlength="1024" pattern="<?= PASSWORD_REGEX ?>" id="current-password" name="current-password" type="password" placeholder="<?= PLACEHOLDER_PASSWORD ?>"><br>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<p>
|
||||
<?= _('A Let\'s Encrypt certificate will be obtained.') ?>
|
||||
</p>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<form method="post">
|
||||
<label for="dir"><?= _('Target directory') ?></label><br>
|
||||
<select required="" name="dir" id="dir">
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<p>
|
||||
<?= sprintf(_('The subdomain can only contain %1$s, %2$s and %3$s, and must be between 4 and 63 characters. It can\'t have an hyphen (%3$s) in first, last or both third and fourth position.'), '<abbr title="abcdefghijklmnopqrstuvwxyz"><code>a</code>-<code>z</code></abbr>', '<abbr title="0123456789"><code>0</code>-<code>9</code></abbr>', '<code>-</code>') ?>
|
||||
</p>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<p>
|
||||
<?= sprintf(_('The path can only contain %1$s, %2$s and %3$s, and must be between 4 and 63 characters.'), '<abbr title="abcdefghijklmnopqrstuvwxyz"><code>a</code>-<code>z</code></abbr>', '<abbr title="0123456789"><code>0</code>-<code>9</code></abbr>', '<code>-</code>') ?>
|
||||
</p>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<form method="post">
|
||||
<label for="site"><?= _('Access to delete') ?></label><br>
|
||||
<select required="" name="site" id="site">
|
||||
|
@ -10,7 +11,7 @@ foreach (query('select', 'sites', ['username' => $_SESSION['id'] ?? '']) as $sit
|
|||
'onion' => 'http://' . $site['address'] . '/',
|
||||
'dns' => 'https://' . $site['address'] . '/',
|
||||
};
|
||||
echo ' <option value="' . $site['type'] . ':' . $site['address'] . '">' . $url . ' vers /' . $site['site_dir'] . '</option>' . LF;
|
||||
echo ' <option value="' . $site['type'] . ':' . $site['address'] . '">' . ' ' . sprintf(_('%1$s to %2$s'), $url, '/' . $site['site_dir']) . '</option>' . LF;
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<p>
|
||||
<?= _('This service allows you to send files on the server using SFTP, and to make them publicly available with HTTP.') ?>
|
||||
</p>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<p>
|
||||
<?= _('In addition to your password, you can also access your SFTP space using Ed25519 SSH keys. A key can be granted modification rights to the full space (<code>/</code>) or to any arbitrary subdirectory. A key is always allowed to list any directory content.') ?>
|
||||
</p>
|
||||
|
@ -16,12 +17,12 @@ foreach (array_slice(array_merge(query('select', 'ssh-keys', ['username' => $_SE
|
|||
<fieldset>
|
||||
<legend><?= ($ssh_key['key'] === '') ? _('Add new SSH key access') : _('SSH key access') ?></legend>
|
||||
<div>
|
||||
<label for="public-key"><?= _('Public key') ?></label><br>
|
||||
<code>ssh-ed15519 <input pattern="<?= ED25519_PUBKEY_REGEX ?>" placeholder="AAAAC3NzaC1lZDI1NTE5AAAAI<?= substr(base64_encode(random_bytes(32)), 0, 43) ?>" id="public-key" name="keys[<?= $i ?>][public-key]" value="<?= $ssh_key['key'] ?>" type="text"></code>
|
||||
<label for="public-key<?= $i ?>"><?= _('Public key') ?></label><br>
|
||||
<code>ssh-ed15519 <input pattern="<?= ED25519_PUBKEY_REGEX ?>" placeholder="AAAAC3NzaC1lZDI1NTE5AAAAI<?= substr(base64_encode(random_bytes(32)), 0, 43) ?>" id="public-key<?= $i ?>" name="keys[<?= $i ?>][public-key]" value="<?= $ssh_key['key'] ?>" type="text"></code>
|
||||
</div>
|
||||
<div>
|
||||
<label for="dir"><?= _('Allowed directory') ?></label><br>
|
||||
<input list="dirs" placeholder="/" value="<?= htmlspecialchars($ssh_key['directory']) ?>" id="dir" name="keys[<?= $i ?>][dir]" type="text">
|
||||
<label for="dir<?= $i ?>"><?= _('Allowed directory') ?></label><br>
|
||||
<input list="dirs" placeholder="/" value="<?= htmlspecialchars($ssh_key['directory']) ?>" id="dir<?= $i ?>" name="keys[<?= $i ?>][dir]" type="text">
|
||||
</div>
|
||||
</fieldset>
|
||||
<?php
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<nav>
|
||||
<p>
|
||||
<span aria-hidden="true">➡️ </span><em><a href="<?= CONF['common']['about_url'] ?>"><?= _('About this installation') ?></a></em>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<form method="post">
|
||||
<?php require ROOT_PATH . '/pg-view/ns/form.ns.php'; ?>
|
||||
<label for="flag"><?= _('Flag') ?></label>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<form method="post">
|
||||
<?php require ROOT_PATH . '/pg-view/ns/form.ns.php'; ?>
|
||||
<label for="cname"><?= _('Canonical name') ?></label>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<form method="post">
|
||||
<?php require ROOT_PATH . '/pg-view/ns/form.ns.php'; ?>
|
||||
<label for="dname"><?= _('Delegation name') ?></label>
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<form method="post">
|
||||
<label for="zone"><?= _('Zone to be changed') ?></label>
|
||||
<label for="domain"><?= _('Zone to be changed') ?></label>
|
||||
<br>
|
||||
<select required="" name="zone" id="zone">
|
||||
<select required="" name="domain" id="domain">
|
||||
<option value="" disabled="" selected="">-</option>
|
||||
<?php
|
||||
foreach (nsListUserZones() as $zone)
|
||||
echo ' <option value="' . $zone . '">' . $zone . '</option>' . LF;
|
||||
foreach (nsListUserZones() as $domain)
|
||||
echo ' <option value="' . $domain . '">' . $domain . '</option>' . LF;
|
||||
?>
|
||||
</select>
|
||||
<br>
|
||||
|
@ -14,15 +15,15 @@ foreach (nsListUserZones() as $zone)
|
|||
|
||||
<?php
|
||||
|
||||
if (isset($data['zone_content'])) { // Display zone
|
||||
if (isset($data['records'])) { // Display zone
|
||||
|
||||
?>
|
||||
<form method="post">
|
||||
<input type="hidden" name="zone" value="<?= $_POST['zone'] ?>">
|
||||
<input type="hidden" name="domain" value="<?= $_POST['domain'] ?>">
|
||||
|
||||
<label for="zone-content"><?= sprintf(_('New content of the %s zone'), '<code><strong>' . $_POST['zone'] . '</strong></code>') ?></label>
|
||||
<label for="zone-content"><?= sprintf(_('Authoritative records for %s'), '<code><strong>' . $_POST['domain'] . '</strong></code>') ?></label>
|
||||
<br>
|
||||
<textarea id="zone-content" name="zone-content" wrap="off" rows="<?= substr_count($data['zone_content'], LF) + 1 ?>"><?= htmlspecialchars($data['zone_content']) ?></textarea>
|
||||
<textarea id="records" name="records" wrap="off" rows="<?= substr_count($data['records'], LF) + 1 ?>"><?= htmlspecialchars($data['records']) ?></textarea>
|
||||
<br>
|
||||
<input type="submit" value="<?= _('Replace') ?>">
|
||||
</form>
|
||||
|
@ -37,21 +38,21 @@ displayFinalMessage($data);
|
|||
|
||||
<h2><?= _('Default values') ?></h2>
|
||||
|
||||
<p><?= sprintf(_('If the TTL is omitted, it will default to %s seconds.'), '<code><time datetime="PT' . DEFAULT_TTL . 'S">' . DEFAULT_TTL . '</time></code>') ?></p>
|
||||
<p><?= sprintf(_('If the TTL is omitted, it will default to %s seconds.'), '<code><time datetime="PT' . NS_DEFAULT_TTL . 'S">' . NS_DEFAULT_TTL . '</time></code>') ?></p>
|
||||
|
||||
<p><?= _('Precising the class (<code>IN</code>) is optional.') ?></p>
|
||||
|
||||
<h2><?= _('Allowed values') ?></h2>
|
||||
|
||||
<p><?= sprintf(_('Submitted zone content is limited to %s characters.'), ZONE_MAX_CHARACTERS) ?></p>
|
||||
<p><?= sprintf(_('Submitted field content is limited to %s characters.'), NS_TEXTAREA_MAX_CHARACTERS) ?></p>
|
||||
|
||||
<p><?= sprintf(_('TTLs must last between %1$s and %2$s seconds.'), '<code><time datetime="PT' . MIN_TTL . 'S">' . MIN_TTL . '</time></code>', '<code><time datetime="PT' . MAX_TTL . 'S">' . MAX_TTL . '</time></code>') ?></p>
|
||||
<p><?= sprintf(_('TTLs must last between %1$s and %2$s seconds.'), '<code><time datetime="PT' . NS_MIN_TTL . 'S">' . NS_MIN_TTL . '</time></code>', '<code><time datetime="PT' . NS_MAX_TTL . 'S">' . NS_MAX_TTL . '</time></code>') ?></p>
|
||||
|
||||
<p><?= _('The only types that can be defined are:') ?></p>
|
||||
<p><?= _('The only types that can be defined here are:') ?></p>
|
||||
|
||||
<ul>
|
||||
<?php
|
||||
foreach (ALLOWED_TYPES as $allowed_type)
|
||||
foreach (NS_ALLOWED_TYPES as $allowed_type)
|
||||
echo ' <li><code>' . $allowed_type . '</code></li>';
|
||||
?>
|
||||
</ul>
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<label for="action"><?= _('Action') ?></label>
|
||||
<select name="action" id="action">
|
||||
<option value="add"><?= _('Add') ?></option>
|
||||
<option value="delete"><?= _('Delete') ?></option>
|
||||
<option value="add"<?= ($_POST['action'] ?? NULL) === 'add' ? ' selected=""' : '' ?>><?= _('Add') ?></option>
|
||||
<option value="delete"<?= ($_POST['action'] ?? NULL) === 'delete' ? ' selected=""' : '' ?>><?= _('Delete') ?></option>
|
||||
</select>
|
||||
|
||||
<fieldset>
|
||||
|
@ -9,18 +10,19 @@
|
|||
<div>
|
||||
<label for="subdomain"><?= _('Subdomain') ?></label>
|
||||
<br>
|
||||
<input id="subdomain" size="16" placeholder="www" pattern="^(([a-z0-9_-]{1,63}\.?){1,127})|(@){1}$" name="subdomain" type="text">
|
||||
<input id="subdomain" size="16" placeholder="www" pattern="^(([a-z0-9_-]{1,63}\.?){1,127})|(@){1}$" name="subdomain" type="text" value="<?= htmlspecialchars($_POST['subdomain'] ?? '') ?>">
|
||||
</div>
|
||||
<div>
|
||||
<label for="zone"><?= _('Zone') ?></label>
|
||||
<br>
|
||||
<select required="" name="zone" id="zone">
|
||||
<option value="" disabled="" selected="">-</option>
|
||||
<?php
|
||||
foreach (nsListUserZones() as $zone)
|
||||
echo "<option value='" . $zone . "'>" . $zone . "</option>";
|
||||
$user_zones = nsListUserZones();
|
||||
if (!in_array($_POST['zone'] ?? NULL, $user_zones, true))
|
||||
echo ' <option value="" disabled="" selected="">—</option>' . LF;
|
||||
foreach ($user_zones as $zone)
|
||||
echo ' <option value="' . $zone . '"' . (($_POST['zone'] ?? NULL) === $zone ? ' selected=""' : '') . '>.' . $zone . '</option>' . LF;
|
||||
?>
|
||||
|
||||
</select>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
@ -30,7 +32,7 @@ foreach (nsListUserZones() as $zone)
|
|||
<div>
|
||||
<label for="ttl-value"><?= _('Value') ?></label>
|
||||
<br>
|
||||
<input required="" id="ttl-value" list="ttls" name="ttl-value" size="6" type="number" min="1" max="432000" value="<?= DEFAULT_TTL ?>" placeholder="<?= DEFAULT_TTL ?>">
|
||||
<input required="" id="ttl-value" list="ttls" name="ttl-value" size="6" type="number" min="1" max="432000" value="<?= $_POST['ttl-value'] ?? NS_DEFAULT_TTL ?>" placeholder="<?= NS_DEFAULT_TTL ?>">
|
||||
<datalist id="ttls">
|
||||
<option value="900">
|
||||
<option value="1800">
|
||||
|
@ -45,10 +47,10 @@ foreach (nsListUserZones() as $zone)
|
|||
<label for="ttl-multiplier"><?= _('Unit') ?></label>
|
||||
<br>
|
||||
<select required="" name="ttl-multiplier" id="ttl-multiplier">
|
||||
<option value="1"><?= _('second') ?></option>
|
||||
<option value="60"><?= _('minute') ?></option>
|
||||
<option value="3600"><?= _('hour') ?></option>
|
||||
<option value="86400"><?= _('day') ?></option>
|
||||
<option value="1"<?= ($_POST['ttl-multiplier'] ?? NULL) === '1' ? ' selected=""' : '' ?>><?= _('second') ?></option>
|
||||
<option value="60"<?= ($_POST['ttl-multiplier'] ?? NULL) === '60' ? ' selected=""' : '' ?>><?= _('minute') ?></option>
|
||||
<option value="3600"<?= ($_POST['ttl-multiplier'] ?? NULL) === '3600' ? ' selected=""' : '' ?>><?= _('hour') ?></option>
|
||||
<option value="86400"<?= ($_POST['ttl-multiplier'] ?? NULL) === '86400' ? ' selected=""' : '' ?>><?= _('day') ?></option>
|
||||
</select>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<p>
|
||||
<?= _('This service allows to host and manage DNS records inside a DNS zone.') ?>
|
||||
</p>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<form method="post">
|
||||
<?php require ROOT_PATH . '/pg-view/ns/form.ns.php'; ?>
|
||||
<label for="ip"><?= _('IP address') ?></label><br>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<form method="post">
|
||||
<?php require ROOT_PATH . '/pg-view/ns/form.ns.php'; ?>
|
||||
<fieldset>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<form method="post">
|
||||
<?php require ROOT_PATH . '/pg-view/ns/form.ns.php'; ?>
|
||||
<label for="priority"><?= _('Priority') ?></label>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<form method="post">
|
||||
<?php require ROOT_PATH . '/pg-view/ns/form.ns.php'; ?>
|
||||
<label for="ns"><?= _('Name server') ?></label>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<form method="post">
|
||||
<input type="radio" name="print" id="table" value="table" checked="">
|
||||
<label for="table"><?= _('Records table') ?></label>
|
||||
|
@ -38,7 +39,7 @@ if (isset($data['zone-table'])) { ?>
|
|||
foreach ($data['zone-table'] as $zone_line) {
|
||||
echo ' <tr>' . LF;
|
||||
foreach ($zone_line as $element)
|
||||
echo ' <td><code>' . $element . '</code></td>' . LF;
|
||||
echo ' <td><code>' . htmlspecialchars($element) . '</code></td>' . LF;
|
||||
echo ' </tr>' . LF;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<form method="post">
|
||||
<?php require ROOT_PATH . '/pg-view/ns/form.ns.php'; ?>
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<form method="post">
|
||||
<?php require ROOT_PATH . '/pg-view/ns/form.ns.php'; ?>
|
||||
|
||||
|
|
39
pg-view/ns/sync.php
Normal file
39
pg-view/ns/sync.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<p>
|
||||
<?= sprintf(_('AAAA, A and CAA records are regularly copied from the source domain to the target domain. Their TTLs are set to %s seconds.'), SYNC_TTL) ?>
|
||||
</p>
|
||||
<p>
|
||||
<?= _('Source domains that are not signed with DNSSEC are not synchronized. Synchronizations that remain broken may be deleted.') ?>
|
||||
</p>
|
||||
<p>
|
||||
<?= _('This is meant to be used for apex domains, where CNAME records are not allowed. For non-apex domains, CNAME records should be used instead.') ?>
|
||||
</p>
|
||||
|
||||
<form method="post">
|
||||
<?php
|
||||
foreach (array_slice(array_merge(query('select', 'ns-syncs', ['username' => $_SESSION['id'] ?? '']), [['source' => '', 'destination' => '—']]), 0, 8) as $i => $sync) {
|
||||
?>
|
||||
<fieldset>
|
||||
<legend><?= ($sync['source'] === '') ? _('Add new domain records to be synchronized') : _('Synchronized domain') ?></legend>
|
||||
<div>
|
||||
<label for="source<?= $i ?>"><?= _('Source domain') ?></label><br>
|
||||
<input placeholder="provider.<?= PLACEHOLDER_DOMAIN ?>." id="source<?= $i ?>" name="syncs[<?= $i ?>][source]" value="<?= $sync['source'] ?>" type="text">
|
||||
</div>
|
||||
<div>
|
||||
<label for="destination<?= $i ?>"><?= _('Target domain') ?></label>
|
||||
<br>
|
||||
<select required="" name="syncs[<?= $i ?>][destination]" id="destination<?= $i ?>">
|
||||
<option <?= (($sync['destination'] === '') ? 'value="" disabled=""' : 'value="' . $sync['destination'] . '"') ?> selected=""><?= $sync['destination'] ?></option>
|
||||
<?php
|
||||
foreach (array_diff(nsListUserZones(), query('select', 'ns-syncs', ['username' => $_SESSION['id'] ?? ''], 'destination')) as $zone)
|
||||
echo "<option value='" . $zone . "'>" . $zone . "</option>";
|
||||
?>
|
||||
|
||||
</select>
|
||||
</div>
|
||||
</fieldset>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
<input type="submit" value="<?= _('Update') ?>">
|
||||
</form>
|
|
@ -1,3 +1,4 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<form method="post">
|
||||
<?php require ROOT_PATH . '/pg-view/ns/form.ns.php'; ?>
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<form method="post">
|
||||
<?php require ROOT_PATH . '/pg-view/ns/form.ns.php'; ?>
|
||||
<label for="txt"><?= _('Text') ?></label>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<p>
|
||||
<?= sprintf(_('To prove that you own this domain, it must have a NS record equal to %s when the form is being processed.'), '<code>' . getAuthToken() . '._domain-verification.' . SERVER_NAME . '.</code>') ?>
|
||||
</p>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<form method="post">
|
||||
<label for="zone"><?= _('Zone') ?></label>
|
||||
<select required="" name="zone" id="zone">
|
||||
|
|
|
@ -1,19 +1,8 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<form method="post">
|
||||
<label for="action"><?= _('Action') ?></label>
|
||||
<select name="action" id="action">
|
||||
<option value="add"><?= _('Add') ?></option>
|
||||
<option value="delete"><?= _('Delete') ?></option>
|
||||
</select>
|
||||
<?php require ROOT_PATH . '/pg-view/reg/select-action.inc.php'; ?>
|
||||
<br>
|
||||
<label for="zone"><?= _('Domain') ?></label>
|
||||
<br>
|
||||
<select required="" name="zone" id="zone">
|
||||
<option value="" disabled="" selected="">—</option>
|
||||
<?php
|
||||
foreach (regListUserDomains() as $domain)
|
||||
echo ' <option value="' . $domain . '">' . $domain . '</option>' . LF;
|
||||
?>
|
||||
</select>
|
||||
<?php require ROOT_PATH . '/pg-view/reg/select-domain.inc.php'; ?>
|
||||
<br>
|
||||
<label for="keytag"><?= _('Key tag') ?></label>
|
||||
<br>
|
||||
|
|
54
pg-view/reg/edit.php
Normal file
54
pg-view/reg/edit.php
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<form method="post">
|
||||
<label for="domain"><?= _('Domain to be changed') ?></label>
|
||||
<br>
|
||||
<select required="" name="domain" id="domain">
|
||||
<option value="" disabled="" selected="">-</option>
|
||||
<?php
|
||||
foreach (regListUserDomains() as $domain)
|
||||
echo ' <option value="' . $domain . '">' . $domain . '</option>' . LF;
|
||||
?>
|
||||
</select>
|
||||
<br>
|
||||
<input type="submit" value="<?= _('Display') ?>">
|
||||
</form>
|
||||
|
||||
<?php
|
||||
|
||||
if (isset($data['records'])) { // Display zone
|
||||
|
||||
?>
|
||||
<form method="post">
|
||||
<input type="hidden" name="domain" value="<?= $_POST['domain'] ?>">
|
||||
|
||||
<label for="records"><?= sprintf(_('Delegation records for %s'), '<code><strong>' . $_POST['domain'] . '</strong></code>') ?></label>
|
||||
<br>
|
||||
<textarea id="records" name="records" wrap="off" rows="<?= substr_count($data['records'], LF) + 1 ?>"><?= htmlspecialchars($data['records']) ?></textarea>
|
||||
<br>
|
||||
<input type="submit" value="<?= _('Replace') ?>">
|
||||
</form>
|
||||
|
||||
<?php
|
||||
|
||||
}
|
||||
|
||||
displayFinalMessage($data);
|
||||
|
||||
?>
|
||||
|
||||
<h2><?= _('Input values') ?></h2>
|
||||
|
||||
<p><?= _('Precising the class (<code>IN</code>) is optional.') ?></p>
|
||||
|
||||
<p><?= sprintf(_('Submitted field content is limited to %s characters.'), REG_TEXTAREA_MAX_CHARACTERS) ?></p>
|
||||
|
||||
<p><?= sprintf(_('TTL values are ignored and always set to %s seconds.'), '<code><time datetime="PT' . CONF['reg']['ttl'] . 'S">' . CONF['reg']['ttl'] . '</time></code>') ?></p>
|
||||
|
||||
<p><?= _('The only types that can be defined here are:') ?></p>
|
||||
|
||||
<ul>
|
||||
<?php
|
||||
foreach (REG_ALLOWED_TYPES as $allowed_type)
|
||||
echo ' <li><code>' . $allowed_type . '</code></li>';
|
||||
?>
|
||||
</ul>
|
|
@ -1,9 +1,6 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<form method="post">
|
||||
<label for="action"><?= _('Action') ?></label>
|
||||
<select name="action" id="action">
|
||||
<option value="add"><?= _('Add') ?></option>
|
||||
<option value="delete"><?= _('Delete') ?></option>
|
||||
</select>
|
||||
<?php require ROOT_PATH . '/pg-view/reg/select-action.inc.php'; ?>
|
||||
<fieldset>
|
||||
<legend><?= _('Domain') ?></legend>
|
||||
<div>
|
||||
|
@ -12,15 +9,7 @@
|
|||
<input required="" id="subdomain" placeholder="ns1" name="subdomain" type="text">
|
||||
</div>
|
||||
<div>
|
||||
<label for="suffix"><?= _('Domain') ?></label>
|
||||
<br>
|
||||
<select required="" name="suffix" id="suffix">
|
||||
<option value="" disabled="" selected="">—</option>
|
||||
<?php
|
||||
foreach (regListUserDomains() as $suffix)
|
||||
echo ' <option value="' . $suffix . '">' . $suffix . '</option>' . LF;
|
||||
?>
|
||||
</select>
|
||||
<?php require ROOT_PATH . '/pg-view/reg/select-domain.inc.php'; ?>
|
||||
</div>
|
||||
</fieldset>
|
||||
<label for="ip"><?= _('IP address') ?></label><br>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<p>
|
||||
<?= sprintf(_('This domain name registry allows to register domains ending with <code>%1$s</code>, for instance <code><em>domain</em>%1$s</code>.'), '.' . key(CONF['reg']['suffixes'])) ?>
|
||||
</p>
|
||||
|
@ -30,7 +31,9 @@ else {
|
|||
<?php
|
||||
foreach (CONF['reg']['suffixes'] as $suffix => $condition)
|
||||
if ($condition === 'all')
|
||||
echo '<li><code>' . $suffix . ' </code></li>';
|
||||
echo '<li><code>' . $suffix . ' </code></li>' . LF;
|
||||
if (!in_array('all', CONF['reg']['suffixes'], true))
|
||||
echo '<li>∅</li>' . LF;
|
||||
?>
|
||||
</ul>
|
||||
</dd>
|
||||
|
@ -41,7 +44,9 @@ foreach (CONF['reg']['suffixes'] as $suffix => $condition)
|
|||
<?php
|
||||
foreach (CONF['reg']['suffixes'] as $suffix => $condition)
|
||||
if ($condition === 'approved')
|
||||
echo '<li><code>' . $suffix . ' </code></li>';
|
||||
echo '<li><code>' . $suffix . ' </code></li>' . LF;
|
||||
if (!in_array('approved', CONF['reg']['suffixes'], true))
|
||||
echo '<li>∅</li>' . LF;
|
||||
?>
|
||||
</ul>
|
||||
</dd>
|
||||
|
@ -52,9 +57,23 @@ foreach (CONF['reg']['suffixes'] as $suffix => $condition)
|
|||
<?php
|
||||
foreach (CONF['reg']['suffixes'] as $suffix => $condition)
|
||||
if ($condition === 'none')
|
||||
echo '<li><code>' . $suffix . ' </code></li>';
|
||||
echo '<li><code>' . $suffix . ' </code></li>' . LF;
|
||||
if (!in_array('none', CONF['reg']['suffixes'], true))
|
||||
echo '<li>∅</li>' . LF;
|
||||
?>
|
||||
</ul>
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2><?= _('Automatic updates from child zone') ?></h2>
|
||||
<h3><?= _('CSYNC records') ?></h3>
|
||||
<p>
|
||||
<?= _('The registry can synchronize NS records from the child zone if a CSYNC record is present at the apex of the child zone, has flags <code>1</code> and type bit map <code>NS</code>, and can be DNSSEC-validated. Others values are not supported.') ?>
|
||||
</p>
|
||||
<h3><?= _('DNSSEC and DS records') ?></h3>
|
||||
<p>
|
||||
<?= _('Once DNSSEC has been manually enabled through the current interface, the delegated zone can publish a CDS record in order to update the DS record in the registry. A single CDS record with value <code>0 0 0 0</code> tells the registry to disable DNSSEC. Using a CDS record to enable DNSSEC is not supported.') ?>
|
||||
</p>
|
||||
</section>
|
||||
|
|
|
@ -1,19 +1,8 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<form method="post">
|
||||
<label for="action"><?= _('Action') ?></label>
|
||||
<select name="action" id="action">
|
||||
<option value="add"><?= _('Add') ?></option>
|
||||
<option value="delete"><?= _('Delete') ?></option>
|
||||
</select>
|
||||
<?php require ROOT_PATH . '/pg-view/reg/select-action.inc.php'; ?>
|
||||
<br>
|
||||
<label for="domain"><?= _('Domain') ?></label>
|
||||
<br>
|
||||
<select required="" name="domain" id="domain">
|
||||
<option value="" disabled="" selected="">—</option>
|
||||
<?php
|
||||
foreach (regListUserDomains() as $domain)
|
||||
echo ' <option value="' . $domain . '">' . $domain . '</option>' . LF;
|
||||
?>
|
||||
</select>
|
||||
<?php require ROOT_PATH . '/pg-view/reg/select-domain.inc.php'; ?>
|
||||
<br>
|
||||
<label for="ns"><?= _('Name server') ?></label>
|
||||
<br>
|
||||
|
|
|
@ -1,12 +1,6 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<form method="post">
|
||||
<label for="domain"><?= _('Domain') ?></label>
|
||||
<select required="" name="domain" id="domain">
|
||||
<option value="" disabled="" selected="">-</option>
|
||||
<?php
|
||||
foreach (regListUserDomains() as $domain)
|
||||
echo ' <option value="' . $domain . '">' . $domain . '</option>' . LF;
|
||||
?>
|
||||
</select>
|
||||
<?php require ROOT_PATH . '/pg-view/reg/select-domain.inc.php'; ?>
|
||||
<br>
|
||||
<input type="submit" value="<?= _('Display') ?>">
|
||||
</form>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<p>
|
||||
<?= _('Register a new domain on your account.') ?>
|
||||
</p>
|
||||
|
|
6
pg-view/reg/select-action.inc.php
Normal file
6
pg-view/reg/select-action.inc.php
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<label for="action"><?= _('Action') ?></label>
|
||||
<select name="action" id="action">
|
||||
<option value="add"<?= ($_POST['action'] ?? NULL) === 'add' ? ' selected=""' : '' ?>><?= _('Add') ?></option>
|
||||
<option value="delete"<?= ($_POST['action'] ?? NULL) === 'delete' ? ' selected=""' : '' ?>><?= _('Delete') ?></option>
|
||||
</select>
|
12
pg-view/reg/select-domain.inc.php
Normal file
12
pg-view/reg/select-domain.inc.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<label for="domain"><?= _('Domain') ?></label>
|
||||
<br>
|
||||
<select required="" name="domain" id="domain">
|
||||
<?php
|
||||
$user_domains = regListUserDomains();
|
||||
if (!in_array($_POST['domain'] ?? NULL, $user_domains, true))
|
||||
echo ' <option value="" disabled="" selected="">—</option>' . LF;
|
||||
foreach ($user_domains as $domain)
|
||||
echo ' <option value="' . $domain . '"' . (($_POST['domain'] ?? NULL === $domain) ? ' selected=""' : '') . '>' . $domain . '</option>' . LF;
|
||||
?>
|
||||
</select>
|
|
@ -1,3 +1,4 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<p>
|
||||
<?= sprintf(_('To prove that you are allowed to receive the domain by its current owner, the domain must have an NS record equal to %s when the form is being processed. The NS record will be automatically deleted once validated.'), '<code>' . getAuthToken() . '._transfer-verification.' . SERVER_NAME . '.</code>') ?>
|
||||
</p>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<?php declare(strict_types=1); ?>
|
||||
<p>
|
||||
<?= _('This will unregister the domain, making it registerable by anyone again (after a delay of 1 year plus half the registration period, with a maximum of 8 years).') ?>
|
||||
</p>
|
||||
|
|
10
router.php
10
router.php
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
require 'init.php';
|
||||
|
||||
$pageAddress = substr($_SERVER['REQUEST_URI'], strlen(CONF['common']['prefix']) + 1);
|
||||
|
@ -11,7 +11,7 @@ define('PAGE_ADDRESS', $pageAddress . ((substr($pageAddress, -1) === '/' OR $pag
|
|||
define('PAGE_LINEAGE', explode('/', PAGE_ADDRESS));
|
||||
define('SERVICE', dirname(PAGE_ADDRESS));
|
||||
|
||||
function getPageInformations($pages, $pageElements) {
|
||||
function getPageInformations(array $pages, array $pageElements): array {
|
||||
if (!isset($pages['index']) OR $pageElements[0] === 'index')
|
||||
return [
|
||||
'titles_lineage' => [$pages[$pageElements[0]]['title'] ?? false],
|
||||
|
@ -44,7 +44,7 @@ if (in_array($_SERVER['SERVER_NAME'], CONF['common']['public_domains'], true) !=
|
|||
define('SERVER_NAME', $_SERVER['SERVER_NAME']);
|
||||
|
||||
const SESSION_COOKIE_NAME = 'servnest-session-key';
|
||||
function startSession() {
|
||||
function startSession(): void {
|
||||
session_start([
|
||||
'name' => SESSION_COOKIE_NAME,
|
||||
'sid_length' => 64,
|
||||
|
@ -94,7 +94,7 @@ if (isset($_SESSION['id'])) {
|
|||
}
|
||||
}
|
||||
|
||||
function displayFinalMessage($data) {
|
||||
function displayFinalMessage(?array $data): void {
|
||||
if (isset($data['final_message'])) {
|
||||
echo $data['final_message'];
|
||||
unset($data['final_message']);
|
||||
|
@ -118,7 +118,7 @@ if ($_POST !== []) {
|
|||
require ROOT_PATH . '/pg-act/' . PAGE_ADDRESS . '.php';
|
||||
}
|
||||
|
||||
function displayPage($data) {
|
||||
function displayPage(?array $data): never {
|
||||
require ROOT_PATH . '/view.php';
|
||||
exit();
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue