Split pages/ between pg-act/ and pg-view/

This commit is contained in:
Miraty 2022-12-20 21:17:03 +01:00
parent 9e27e479a2
commit 73c137aaba
95 changed files with 1486 additions and 1365 deletions

View file

@ -49,9 +49,13 @@ function changePassword($id, $password) {
->execute([':password' => hashPassword($password), ':id' => $id]);
}
function logout() {
function stopSession() {
if (session_status() === PHP_SESSION_ACTIVE)
session_destroy();
}
function logout() {
stopSession();
header('Clear-Site-Data: "*"');

View file

@ -1,37 +1,16 @@
<?php
$final_message = NULL;
function output($code, $msg = '', $logs = ['']) {
global $final_message;
http_response_code($code);
$shortCode = $code / 100 % 10;
if ($shortCode === 5)
error_log('Niver internal error: ' . strip_tags($msg) . implode(LF, $logs));
$final_message = match ($shortCode) {
2 => ($msg === '') ? '' : '<p><output><strong>Succès</strong> : <em>' . $msg . '</em></output></p>' . LF,
4 => '<p><output><strong>Erreur utilisataire</strong> : <em>' . $msg . '</em></output></p>' . LF,
5 => '<p><output><strong>Server error</strong>: The server encountered an error: <em>' . $msg . '</em></output></p>' . LF,
};
http_response_code($code);
if ($shortCode === 5)
error_log('Niver internal error: ' . strip_tags($msg) . implode(LF, $logs));
if ($code !== 200)
executePage();
}
function processForm($requireLogin = true) {
if (http_response_code() !== 200)
return false;
if ($_POST === []) {
if ($requireLogin AND !isset($_SESSION['id']))
echo '<p>Ce formulaire ne sera pas accepté car il faut <a class="auth" href="' . redirUrl('auth/login') . '">se connecter</a> avant.</p>';
return false;
}
if ($requireLogin) {
if (isset($_SESSION['id']) !== true)
output(403, 'Vous devez être connecté·e à un compte pour effectuer cette action.');
if (isset(query('select', 'users', ['id' => $_SESSION['id']], 'id')[0]) !== true)
output(403, 'Ce compte n\'existe plus. Déconnectez-vous pour terminer cette session fantôme.');
}
return true;
displayPage(['final_message' => $final_message]);
}
function insert($table, $values) {

View file

@ -1,5 +1,19 @@
<?php
function parseZoneFile($zone_content, $types, $filter_domain = false) {
$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))
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));
}
return $parsed_zone_content;
}
function knotcConfExec($cmds) {
exec(CONF['dns']['knotc_path'] . ' conf-begin', $output['begin'], $code['begin']);
if ($code['begin'] !== 0)

View file

@ -44,8 +44,10 @@ function nsParseCommonRequirements() {
return $values;
}
function nsListUserZones($username) {
return query('select', 'zones', ['username' => $username], 'zone');
function nsListUserZones() {
if (isset($_SESSION['id']))
return query('select', 'zones', ['username' => $_SESSION['id']], 'zone');
return [];
}
function nsCheckZonePossession($zone) {

View file

@ -2,12 +2,14 @@
const SUBDOMAIN_REGEX = '^[a-z0-9]{4,63}$';
function regListUserDomains($username) {
return query('select', 'registry', ['username' => $username], 'domain');
function regListUserDomains() {
if (isset($_SESSION['id']))
return query('select', 'registry', ['username' => $_SESSION['id']], 'domain');
return [];
}
function regCheckDomainPossession($domain) {
if (in_array($domain, regListUserDomains($_SESSION['id']), true) !== true)
if (in_array($domain, regListUserDomains(), true) !== true)
output(403, 'You don\'t own this domain.');
}

View file

@ -18,9 +18,8 @@
<select required="" name="zone" id="zone">
<option value="" disabled="" selected="">-</option>
<?php
if (isset($_SESSION['id']))
foreach (nsListUserZones($_SESSION['id']) as $zone)
echo "<option value='" . $zone . "'>" . $zone . "</option>";
foreach (nsListUserZones() as $zone)
echo "<option value='" . $zone . "'>" . $zone . "</option>";
?>
</select>

View file

@ -12,10 +12,12 @@ define('PAGES', [
'login' => [
'title' => 'Se connecter',
'description' => 'Démarrer une nouvelle session avec un compte existant',
'require-login' => false,
],
'register' => [
'title' => 'Créer un compte',
'description' => 'Créer un nouveau compte Niver',
'require-login' => false,
'tokens_instance_cost' => 7200,
],
'unregister' => [

View file

@ -1,36 +0,0 @@
<?php
insert('approval-keys', ['key' => bin2hex(random_bytes(16))]);
if (processForm()) {
if ($_SESSION['type'] !== 'testing')
output(403, 'Approbation impossible : votre compte est déjà approuvé.');
if (isset(query('select', 'approval-keys', ['key' => $_POST['key']], 'key')[0]) !== true)
output(403, 'Approbation impossible : cette clé d\'approbation n\'est pas disponible. Elle a été mal saisie, a expiré ou a déjà été utilisée pour un autre compte.');
query('delete', 'approval-keys', ['key' => $_POST['key']]);
DB->prepare('UPDATE users SET type = "approved" WHERE id = :id')
->execute([':id' => $_SESSION['id']]);
$_SESSION['type'] = 'approved';
insert('approval-keys', ['key' => bin2hex(random_bytes(16))]);
output(200, 'Compte approuvé.');
}
?>
<p>
Ce formulaire permet d'utiliser une clé d'approbation pour valider son compte. Une clé d'approbation est distribuée par l'administrataire sur demande.
</p>
<form method="post">
<label for="key">Clé d'approbation</label><br>
<input required="" id="key" size="33" name="key" type="text" placeholder="27b81fbd8277b11ed1cf03d476cec503">
<br>
<input type="submit" value="Utiliser">
</form>

View file

@ -1,42 +0,0 @@
<?php
if (processForm(false)) {
checkPasswordFormat($_POST['password']);
checkUsernameFormat($_POST['username']);
$username = hashUsername($_POST['username']);
if (usernameExists($username) !== true)
output(403, 'Connexion impossible : ce compte n\'existe pas.');
$id = query('select', 'users', ['username' => $username], 'id')[0];
if (checkPassword($id, $_POST['password']) !== true)
output(403, 'Connexion impossible : clé de passe invalide.');
$_SESSION['id'] = $id;
$_SESSION['display-username'] = htmlspecialchars($_POST['username']);
$_SESSION['type'] = query('select', 'users', ['id' => $id], 'type')[0];
if (outdatedPasswordHash($id))
changePassword($id, $_POST['password']);
redir();
}
?>
<p>Pas de compte ? <a class="auth" href="register">En créer un</a></p>
<form method="post">
<label for="username">Identifiant</label><br>
<input required="" minlength="1" maxlength="1024" pattern="<?= USERNAME_REGEX ?>" id="username" name="username" type="text" placeholder="<?= PLACEHOLDER_USERNAME ?>">
<br>
<label for="password">Clé de passe</label><br>
<input required="" autocomplete="current-password" minlength="8" maxlength="1024" pattern="<?= PASSWORD_REGEX ?>" id="password" name="password" type="password" placeholder="<?= PLACEHOLDER_PASSWORD ?>">
<br>
<input type="submit">
</form>

View file

@ -1,69 +0,0 @@
<?php
if (processForm(false)) {
checkPasswordFormat($_POST['password']);
checkUsernameFormat($_POST['username']);
$username = hashUsername($_POST['username']);
if (usernameExists($username) !== false)
output(403, 'Ce nom de compte est déjà utilisé.');
rateLimit();
$id = hash('sha256', random_bytes(32));
insert('users', [
'id' => $id,
'username' => $username,
'password' => hashPassword($_POST['password']),
'registration_date' => date('Y-m-d H:i:s'),
'bucket_tokens' => 0,
'bucket_last_update' => 0,
'type' => 'testing',
]);
// Setup SFTP directory
umask(0002);
if (mkdir(CONF['ht']['ht_path'] . '/' . $id, 0775) !== true)
output(500, 'Can\'t create user directory.');
exec(CONF['ht']['sudo_path'] . ' ' . CONF['ht']['chgrp_path'] . ' ' . CONF['ht']['sftpgo_group'] . ' ' . CONF['ht']['ht_path'] . '/' . $id . ' --no-dereference', result_code: $code);
if ($code !== 0)
output(500, 'Can\'t change user directory group.');
// Setup Tor config directory
if (mkdir(CONF['ht']['tor_config_path'] . '/' . $id, 0755) !== true)
output(500, 'Can\'t create Tor config directory.');
// Setup Tor keys directory
exec(CONF['ht']['sudo_path'] . ' -u ' . CONF['ht']['tor_user'] . ' ' . CONF['ht']['mkdir_path'] . ' --mode=0700 ' . CONF['ht']['tor_keys_path'] . '/' . $id, result_code: $code);
if ($code !== 0)
output(500, 'Can\'t create Tor keys directory.');
$_SESSION['id'] = $id;
$_SESSION['display-username'] = htmlspecialchars($_POST['username']);
$_SESSION['type'] = 'testing';
redir();
}
?>
<p>Déjà un compte ? <a class="auth" href="login">Se connecter</a></p>
<form method="post">
<label for="username">Identifiant</label>
<br>
<input id="username" minlength="1" maxlength="1024" pattern="<?= USERNAME_REGEX ?>" required="" name="username" type="text" placeholder="<?= PLACEHOLDER_USERNAME ?>"><br>
<details>
<summary><label for="password">Clé de passe</label></summary>
<p>Une clé de passe sécurisée est trop compliquée à deviner pour une attaque qui testerait automatiquement plein de clés de passe tout en connaissant d'autres informations et secrets sur vous.</p>
<p>Minimum 8 caractères si elle contient minuscule, majuscule et chiffre, ou minimum 10 caractères sinon.</p>
</details>
<input autocomplete="new-password" id="password" minlength="8" maxlength="1024" pattern="<?= PASSWORD_REGEX ?>" required="" name="password" type="password" placeholder="<?= PLACEHOLDER_PASSWORD ?>">
<br>
<input type="submit">
</form>

View file

@ -1,62 +0,0 @@
<?php
if (processForm()) {
if (!isset($_POST['delete']))
output(403, 'Il faut confirmer la suppression du compte');
foreach (query('select', 'registry', ['username' => $_SESSION['id']], 'domain') as $domain)
regDeleteDomain($domain);
foreach (query('select', 'zones', ['username' => $_SESSION['id']], 'zone') as $zone)
nsDeleteZone($zone);
foreach (query('select', 'sites', [
'username' => $_SESSION['id'],
'domain_type' => 'onion',
'protocol' => 'http',
], 'site_dir') as $dir)
htDeleteSite($dir, domainType: 'onion', protocol: 'http');
foreach (query('select', 'sites', [
'username' => $_SESSION['id'],
'domain_type' => 'dns',
'protocol' => 'http',
], 'site_dir') as $dir)
htDeleteSite($dir, domainType: 'dns', protocol: 'http');
exec(CONF['ht']['sudo_path'] . ' -u ' . CONF['ht']['tor_user'] . ' ' . CONF['ht']['rm_path'] . ' --recursive ' . CONF['ht']['tor_keys_path'] . '/' . $_SESSION['id'], result_code: $code);
if ($code !== 0)
output(500, 'Can\'t remove Tor keys directory.');
removeDirectory(CONF['ht']['tor_config_path'] . '/' . $_SESSION['id']);
exec(CONF['ht']['sudo_path'] . ' -u ' . CONF['ht']['sftpgo_user'] . ' ' . CONF['ht']['rm_path'] . ' --recursive ' . CONF['ht']['ht_path'] . '/' . $_SESSION['id'], result_code: $code);
if ($code !== 0)
output(500, 'Can\'t remove user\'s directory.');
query('delete', 'users', ['id' => $_SESSION['id']]);
logout();
output(200, 'Compte supprimé.');
}
?>
<p>
Cette action supprimera toutes les données appartenant à ce compte, y compris :
</p>
<ul>
<li>la possession et la réservation des domaines dans le registre</li>
<li>les enregistrements DNS des zones hébergées sur le serveur de noms</li>
<li>le contenu des sites</li>
<li>les paires de clés des services Onion</li>
</ul>
<form method="post">
<input type="checkbox" name="delete" id="delete" required="">
<label for="delete">Supprimer mon compte et toutes ses données</label>
<br>
<input type="submit">
</form>

View file

@ -1,30 +0,0 @@
<?php
if (processForm()) {
checkUsernameFormat($_POST['new-username']);
$username = hashUsername($_POST['new-username']);
if (usernameExists($username) !== false)
output(403, 'Ce nom de compte est déjà utilisé.');
DB->prepare('UPDATE users SET username = :username WHERE id = :id')
->execute([':username' => $username, ':id' => $_SESSION['id']]);
$_SESSION['display-username'] = htmlspecialchars($_POST['new-username']);
output(200, 'Identifiant changé.');
}
?>
<p>
Vous pouvez ici changer l'identifiant permettant d'accéder à votre compte Niver.
</p>
<form method="post">
<label for="new-username">Nouvel identifiant</label><br>
<input required="" autocomplete="new-username" minlength="1" maxlength="1024" pattern="<?= USERNAME_REGEX ?>" id="new-username" name="new-username" type="text" placeholder="<?= PLACEHOLDER_USERNAME ?>"><br>
<input type="submit">
</form>

View file

@ -1,105 +0,0 @@
<?php
if (processForm()) {
$_POST['domain'] = formatDomain($_POST['domain']);
if (dirsStatuses('dns', 'http')[$_POST['dir']] !== false)
output(403, 'Wrong value for <code>dir</code>.');
if (query('select', 'sites', ['domain' => $_POST['domain']], 'domain') !== [])
output(403, 'Ce domaine existe déjà sur ce service.');
$remoteAaaaRecords = dns_get_record($_POST['domain'], DNS_AAAA);
if (is_array($remoteAaaaRecords) !== true)
output(500, 'Erreur lors de la récupération de l\'enregistrement AAAA.');
if (equalArrays([CONF['ht']['ipv6_address']], array_column($remoteAaaaRecords, 'ipv6')) !== true)
output(403, 'Ce domaine doit avoir pour unique enregistrement AAAA <code>' . CONF['ht']['ipv6_address'] . '</code>.');
$remoteARecords = dns_get_record($_POST['domain'], DNS_A);
if (is_array($remoteARecords) !== true)
output(500, 'Erreur lors de la récupération de l\'enregistrement A.');
if (equalArrays([CONF['ht']['ipv4_address']], array_column($remoteARecords, 'ip')) !== true)
output(403, 'Ce domaine doit avoir pour unique enregistrement A <code>' . CONF['ht']['ipv4_address'] . '</code>.');
$remoteTXTRecords = dns_get_record($_POST['domain'], DNS_TXT);
if (is_array($remoteTXTRecords) !== true)
output(500, 'Erreur lors de la récupération de l\'enregistrement TXT.');
if (preg_match('/^' . preg_quote(SERVER_NAME, '/') . '_domain-verification=([0-9a-f]{8})-([0-9a-f]{32})$/Dm', implode(LF, array_column($remoteTXTRecords, 'txt')), $matches) !== 1)
output(403, 'Aucun enregistrement TXT au format correct trouvé.');
checkAuthToken($matches[1], $matches[2]);
rateLimit();
addSite($_SESSION['id'], $_POST['dir'], $_POST['domain'], 'dns', 'http');
exec('2>&1 ' . CONF['ht']['sudo_path'] . ' ' . CONF['ht']['certbot_path'] . ' certonly' . (($_SESSION['type'] === 'approved') ? '' : ' --test-cert') . ' --key-type rsa --rsa-key-size 3072 --webroot --webroot-path /srv/niver/acme --domain ' . $_POST['domain'], $output, $returnCode);
if ($returnCode !== 0)
output(500, 'Certbot failed to get a Let\'s Encrypt certificate.', $output);
$nginxConf = 'server {
listen [' . CONF['ht']['ipv6_listen_address'] . ']:' . CONF['ht']['https_port'] . ' ssl http2;
listen ' . CONF['ht']['ipv4_listen_address'] . ':' . CONF['ht']['https_port'] . ' ssl http2;
server_name ' . $_POST['domain'] . ';
root ' . CONF['ht']['ht_path'] . '/' . $_SESSION['id'] . '/' . $_POST['dir'] . ';
ssl_certificate /etc/letsencrypt/live/' . $_POST['domain'] . '/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/' . $_POST['domain'] . '/privkey.pem;
include inc/ht-tls.conf;
}
';
if (file_put_contents(CONF['ht']['nginx_config_path'] . '/' . $_POST['domain'] . '.conf', $nginxConf) === false)
output(500, 'Failed to write Nginx configuration.');
// Reload Nginx
exec(CONF['ht']['sudo_path'] . ' ' . CONF['ht']['nginx_reload_cmd'], result_code: $code);
if ($code !== 0)
output(500, 'Failed to reload Nginx.');
output(200, 'Accès HTTP par domaine ajouté sur ce dossier !');
}
$dirsStatuses = dirsStatuses('onion', 'http');
$proof = getAuthToken();
?>
<p>
Ajouter sur un dossier de site un accès <?= linkToDocs('http', 'HTTP') ?> par <?= linkToDocs('dns', 'DNS') ?> et <?= linkToDocs('tls', 'TLS') ?> <?= linkToDocs('ca', 'authentifié par <em>Let\'s Encrypt</em>') ?>.
</p>
<p>
La présence des enregistrements ci-après sera vérifiée lors du traitement de ce formulaire.
</p>
<dl>
<dt><code>AAAA</code></dt>
<dd>
<code><?= CONF['ht']['ipv6_address'] ?></code>
</dd>
<dt><code>A</code></dt>
<dd>
<code><?= CONF['ht']['ipv4_address'] ?></code>
</dd>
<dt><code>TXT</code></dt>
<dd>
<code><?= SERVER_NAME ?>_domain-verification=<?= $proof ?></code>
</dd>
</dl>
<form method="post">
<label for="domain">Domaine sur lequel répondre</label><br>
<input required="" placeholder="site.<?= PLACEHOLDER_DOMAIN ?>" id="domain" name="domain" type="text"><br>
<label for="dir">Dossier ciblé</label><br>
<select required="" name="dir" id="dir">
<option value="" disabled="" selected="">---</option>
<?php
foreach ($dirsStatuses as $dir => $alreadyEnabled)
echo ' <option' . ($alreadyEnabled ? ' disabled=""' : '') . ' value="' . $dir . '">' . $dir . '</option>' . LF;
?>
</select>
<br>
<input value="Valider" type="submit">
</form>

View file

@ -1,70 +0,0 @@
<?php
if (processForm()) {
if (dirsStatuses('onion', 'http')[$_POST['dir']] !== false)
output(403, 'Wrong value for <code>dir</code>.');
rateLimit();
// Add Tor config
$torConf = 'HiddenServiceDir ' . CONF['ht']['tor_keys_path'] . '/' . $_SESSION['id'] . '/' . $_POST['dir'] . '/
HiddenServicePort 80 [::1]:' . CONF['ht']['internal_onion_http_port'] . '
';
if (file_put_contents(CONF['ht']['tor_config_path'] . '/' . $_SESSION['id'] . '/' . $_POST['dir'], $torConf) === false)
output(500, 'Failed to write new Tor configuration.');
// Reload Tor
exec(CONF['ht']['sudo_path'] . ' ' . CONF['ht']['tor_reload_cmd'], $output, $code);
if ($code !== 0)
output(500, 'Failed to reload Tor.');
// Get the address generated by Tor
exec(CONF['ht']['sudo_path'] . ' -u ' . CONF['ht']['tor_user'] . ' ' . CONF['ht']['cat_path'] . ' ' . CONF['ht']['tor_keys_path'] . '/' . $_SESSION['id'] . '/' . $_POST['dir'] . '/hostname', $output);
$onion = $output[0];
if (preg_match('/^[0-9a-z]{56}\.onion$/D', $onion) !== 1)
output(500, 'No onion address found.');
// Store it in the database
addSite($_SESSION['id'], $_POST['dir'], $onion, 'onion', 'http');
// Add Nginx config
$nginxConf = 'server {
listen [::1]:' . CONF['ht']['internal_onion_http_port'] . ';
server_name ' . $onion . ';
root ' . CONF['ht']['ht_path'] . '/' . $_SESSION['id'] . '/' . $_POST['dir'] . ';
include inc/ht-onion.conf;
}
';
if (file_put_contents(CONF['ht']['nginx_config_path'] . '/' . $onion . '.conf', $nginxConf) === false)
output(500, 'Failed to write Nginx configuration.');
// Reload Nginx
exec(CONF['ht']['sudo_path'] . ' ' . CONF['ht']['nginx_reload_cmd'], result_code: $code);
if ($code !== 0)
output(500, 'Failed to reload Nginx.');
// Tell the user their site address
output(200, 'L\'adresse de votre service Onion HTTP est : <a href="http://' . $onion . '/"><code>http://' . $onion . '/</code></a>');
}
$dirsStatuses = dirsStatuses('onion', 'http');
?>
<p>
Ajouter un accès en .onion sur un dossier
</p>
<form method="post">
<label for="dir">Dossier ciblé</label><br>
<select required="" name="dir" id="dir">
<option value="" disabled="" selected="">---</option>
<?php
foreach ($dirsStatuses as $dir => $alreadyEnabled)
echo ' <option' . ($alreadyEnabled ? ' disabled=""' : '') . ' value="' . $dir . '">' . $dir . '</option>' . LF;
?>
</select>
<br>
<input value="Valider" type="submit">
</form>

View file

@ -1,96 +0,0 @@
<form method="post">
<input type="radio" name="print" id="table" value="table" checked="">
<label for="table">Tableau de mes enregistrements</label>
<br>
<input type="radio" name="print" id="ds" value="ds">
<label for="ds">Enregistrement DS</label>
<br>
<input type="radio" name="print" id="raw" value="raw">
<label for="raw">Fichier de zone brut</label>
<br>
<label for="zone">Zone</label>
<select required="" name="zone" id="zone">
<option value="" disabled="" selected="">-</option>
<?php
if (isset($_SESSION['id']))
foreach (nsListUserZones($_SESSION['id']) as $zone)
echo ' <option value="' . $zone . '">' . $zone . '</option>' . LF;
?>
</select>
<br>
<input value="Afficher" type="submit">
</form>
<?php
if (processForm()) {
nsCheckZonePossession($_POST['zone']);
$zoneContent = file_get_contents(CONF['ns']['knot_zones_path'] . '/' . $_POST['zone'] . 'zone');
if ($zoneContent === false)
output(500, 'Unable to read zone file.');
if ($_POST['print'] === 'raw') {
echo '<pre>' . htmlspecialchars($zoneContent) . '</pre>';
output(200);
}
if ($_POST['print'] === 'table') { ?>
<table>
<tr>
<th>Domaine</th>
<th>TTL</th>
<th>Type</th>
<th>Contenu</th>
</tr>
<?php
foreach(explode(LF, $zoneContent) as $zoneLine) {
if (str_starts_with($zoneLine, ';')) continue; // Ignore comments
if (empty($zoneLine)) continue;
$elements = preg_split('/[\t ]+/', $zoneLine, 4);
if (!in_array($elements[2], ALLOWED_TYPES, true)) continue; // Ignore records generated by Knot
echo ' <tr>';
foreach ($elements as $element)
echo ' <td><code>' . htmlspecialchars($element) . '</code></td>';
echo ' </tr>';
}
echo '</table>';
}
if ($_POST['print'] === 'ds') {
$found = preg_match('/^' . preg_quote($_POST['zone'], '/') . '[\t ]+0[\t ]+CDS[\t ]+(?<tag>[0-9]{1,5})[\t ]+(?<algo>[0-9]{1,2})[\t ]+(?<digest_type>[0-9])[\t ]+(?<digest>[0-9A-F]{64})$/Dm', $zoneContent, $matches);
if ($found !== 1)
output(500, 'Unable to get public key record from zone file.');
?>
<dl>
<dt>Zone</dt>
<dd>
<code><?= $_POST['zone'] ?></code>
</dd>
<dt>Tag</dt>
<dd>
<code><?= $matches['tag'] ?></code>
</dd>
<dt>Algorithme</dt>
<dd>
<code><?= $matches['algo'] ?></code><?php if ($matches['algo'] === '15') echo ' (Ed25519)'; ?>
</dd>
<dt>Type de condensat</dt>
<dd>
<code><?= $matches['digest_type'] ?></code><?php if ($matches['digest_type'] === '2') echo ' (SHA-256)'; ?>
</dd>
<dt>Condensat</dt>
<dd>
<code><?= $matches['digest'] ?></code>
</dd>
</dl>
<?php
output(200);
}
}

View file

@ -1,78 +0,0 @@
<?php
if (processForm()) {
$_POST['domain'] = formatAbsoluteDomain($_POST['domain']);
if (query('select', 'zones', ['zone' => $_POST['domain']], 'zone') !== [])
output(403, 'Cette zone existe déjà sur ce service.');
exec(CONF['dns']['kdig_path'] . ' ' . ltrim(strstr($_POST['domain'], '.'), '.') . ' NS +short', $parentAuthoritatives);
if ($parentAuthoritatives === [])
output(403, 'Serveurs de noms de la zone parente introuvables');
foreach ($parentAuthoritatives as $parentAuthoritative)
checkAbsoluteDomainFormat($parentAuthoritative);
exec(CONF['dns']['kdig_path'] . ' ' . $_POST['domain'] . ' NS @' . $parentAuthoritatives[0] . ' +noidn', $results);
if (preg_match('/^' . preg_quote($_POST['domain'], '/') . '[\t ]+[0-9]{1,8}[\t ]+IN[\t ]+NS[\t ]+(?<salt>[0-9a-f]{8})-(?<hash>[0-9a-f]{32})\._domain-verification\.' . preg_quote(SERVER_NAME, '/') . '\.$/Dm', implode(LF, $results), $matches) !== 1)
output(403, 'Enregistrement d\'authentification introuvable');
checkAuthToken($matches['salt'], $matches['hash']);
rateLimit();
insert('zones', [
'zone' => $_POST['domain'],
'username' => $_SESSION['id'],
]);
$knotZonePath = CONF['ns']['knot_zones_path'] . '/' . $_POST['domain'] . 'zone';
$knotZone = implode(' ', [
$_POST['domain'],
SOA_VALUES['ttl'],
'SOA',
CONF['ns']['servers'][0],
SOA_VALUES['email'],
1,
SOA_VALUES['refresh'],
SOA_VALUES['retry'],
SOA_VALUES['expire'],
SOA_VALUES['negative'],
]) . LF;
foreach (CONF['ns']['servers'] as $server)
$knotZone .= $_POST['domain'] . ' 86400 NS ' . $server . LF;
if (is_int(file_put_contents($knotZonePath, $knotZone)) !== true)
output(500, 'Failed to write new zone file.');
if (chmod($knotZonePath, 0660) !== true)
output(500, 'Failed to chmod new zone file.');
knotcConfExec([
"set 'zone[" . $_POST['domain'] . "]'",
"set 'zone[" . $_POST['domain'] . "].template' 'niver'",
]);
output(200, 'La zone a été créée.');
}
$proof = getAuthToken();
?>
<p>
Pour prouver que vous possédez bien ce domaine, il doit posséder un <?= linkToDocs('ns-record', 'enregistrement NS') ?> égal à <code><?= $proof ?>._domain-verification.<?= SERVER_NAME ?>.</code> lors du traitement de ce formulaire.
</p>
<p>
La zone sera servie par ces serveurs de noms :
<ul>
<?php
foreach (CONF['ns']['servers'] as $server)
echo ' <li><code>' . $server . '</code></li>';
?>
</ul>
</p>
<form method="post">
<label for="domain">Domaine</label><br>
<input required="" placeholder="domain.<?= PLACEHOLDER_DOMAIN ?>." id="domain" name="domain" type="text"><br>
<input value="Ajouter" type="submit">
</form>

View file

@ -1,25 +0,0 @@
<?php
if (processForm()) {
nsCheckZonePossession($_POST['zone']);
nsDeleteZone($_POST['zone']);
output(200, 'La zone a été supprimée.');
}
?>
<form method="post">
<label for="zone">Zone</label>
<select required="" name="zone" id="zone">
<option value="" disabled="" selected="">-</option>
<?php
if (isset($_SESSION['id']))
foreach (nsListUserZones($_SESSION['id']) as $zone)
echo ' <option value="' . $zone . '">' . $zone . '</option>' . LF;
?>
</select>
<br>
<input value="Supprimer toutes les données liées à cette zone" type="submit">
</form>

View file

@ -1,49 +0,0 @@
<form method="post">
<label for="domain">Domaine</label>
<select required="" name="domain" id="domain">
<option value="" disabled="" selected="">-</option>
<?php
if (isset($_SESSION['id']))
foreach (regListUserDomains($_SESSION['id']) as $domain)
echo ' <option value="' . $domain . '">' . $domain . '</option>' . LF;
?>
</select>
<br>
<input value="Afficher" type="submit">
</form>
<?php
if (processForm()) {
regCheckDomainPossession($_POST['domain']);
$zoneContent = file_get_contents(CONF['reg']['registry_file']);
if ($zoneContent === false)
output(500, 'Unable to read registry file.');
?>
<table>
<tr>
<th>Domaine</th>
<th>TTL</th>
<th>Type</th>
<th>Contenu</th>
</tr>
<?php
foreach(explode(LF, $zoneContent) as $zoneLine) {
if (str_starts_with($zoneLine, ';')) continue; // Ignore comments
if (empty($zoneLine)) continue;
$elements = preg_split('/[\t ]+/', $zoneLine, 4);
if (!str_ends_with($elements[0], $_POST['domain'])) continue; // Ignore records for other domains
if (!in_array($elements[2], ['A', 'AAAA', 'NS', 'DS'], true)) continue; // Ignore records generated by Knot
echo ' <tr>' . LF;
foreach ($elements as $element)
echo ' <td><code>' . htmlspecialchars($element) . '</code></td>' . LF;
echo ' </tr>' . LF;
}
echo '</table>';
output(200);
}

View file

@ -1,38 +0,0 @@
<?php
if (processForm()) {
if (preg_match('/' . SUBDOMAIN_REGEX . '/D', $_POST['subdomain']) !== 1)
output(403, 'Le nom de domaine doit être composé uniquement d\'entre 4 et 63 lettres minuscules ou chiffre (a-z et 0-9)');
$domain = formatAbsoluteDomain($_POST['subdomain'] . '.' . CONF['reg']['registry']);
if (query('select', 'registry', ['domain' => $domain], 'domain') !== [])
output(403, 'Ce domaine n\'est pas disponible à l\'enregistrement. Il est déjà enregistré.');
if (in_array($_POST['subdomain'], explode(LF, file_get_contents(CONF['common']['root_path'] . '/pages/reg/reserved.txt'))))
output(403, 'Ce domaine n\'est pas disponible à l\'enregistrement. Il est réservé.');
rateLimit();
insert('registry', [
'domain' => $domain,
'username' => $_SESSION['id'],
'last_renewal' => date('Y-m-d H:i:s'),
]);
output(200, 'Domaine ajouté au registre.');
}
?>
<p>
Enregistrer un nouveau domaine sur son compte. Ce domaine doit être composé uniquement d'au moins 4 lettres latines non accentuées (a-z).
</p>
<form method="post">
<label for="subdomain">Sous-domaine</label>
<br>
<code><input id="subdomain" pattern="<?= SUBDOMAIN_REGEX ?>" required="" placeholder="niver" name="subdomain" type="text">.<?= CONF['reg']['registry'] ?></code>
<br>
<input value="Enregistrer" type="submit">
</form>

View file

@ -1,44 +0,0 @@
<?php
if (processForm()) {
if (preg_match('/' . SUBDOMAIN_REGEX . '/D', $_POST['domain']) !== 1)
output(403, 'Le nom de domaine semble incorrect');
$domain = $_POST['domain'] . '.' . CONF['reg']['registry'];
if (query('select', 'registry', ['username' => $_SESSION['id'], 'domain' => $domain], 'domain') !== [])
output(403, 'Le compte présent possède déjà ce domaine.');
exec(CONF['dns']['kdig_path'] . ' ' . $domain . ' NS @' . CONF['reg']['address'] . ' +noidn', $results);
if (preg_match('/^' . preg_quote($domain, '/') . '[\t ]+[0-9]{1,8}[\t ]+IN[\t ]+NS[\t ]+(?<salt>[0-9a-f]{8})-(?<hash>[0-9a-f]{32})\._transfer-verification\.' . preg_quote(SERVER_NAME, '/') . '\.$/Dm', implode(LF, $results), $matches) !== 1)
output(403, 'Enregistrement d\'authentification introuvable');
checkAuthToken($matches['salt'], $matches['hash']);
DB->prepare('UPDATE registry SET username = :username WHERE domain = :domain')
->execute([':username' => $_SESSION['id'], ':domain' => $domain]);
knotcZoneExec(CONF['reg']['registry'], [
$domain,
'NS',
$matches['salt'] . '-' . $matches['hash'] . '._transfer-verification.' . SERVER_NAME . '.'
], 'delete');
output(200, 'Le domaine a été transféré vers le compte présent, l\'enregistrement d\'authentification a été automatiquement retiré.');
}
$proof = getAuthToken();
?>
<p>
Pour prouver que vous êtes autorisé à recevoir le domaine par san possessaire actuele, ledit domaine doit posséder un <?= linkToDocs('ns-record', 'enregistrement NS') ?> égal à <code><?= $proof ?>._transfer-verification.<?= SERVER_NAME ?>.</code> lors du traitement de ce formulaire. Cet enregistrement sera automatiquement retiré une fois validé.
</p>
<form method="post">
<label for="subdomain">Sous-domaine à recevoir</label>
<br>
<code><input required="" placeholder="subdomain" id="subdomain" name="subdomain" type="text">.<?= CONF['reg']['registry'] ?></code>
<br>
<input value="Recevoir ce domaine" type="submit">
</form>

18
pg-act/auth/approval.php Normal file
View file

@ -0,0 +1,18 @@
<?php
if ($_SESSION['type'] !== 'testing')
output(403, 'Approbation impossible : votre compte est déjà approuvé.');
if (isset(query('select', 'approval-keys', ['key' => $_POST['key']], 'key')[0]) !== true)
output(403, 'Approbation impossible : cette clé d\'approbation n\'est pas disponible. Elle a été mal saisie, a expiré ou a déjà été utilisée pour un autre compte.');
query('delete', 'approval-keys', ['key' => $_POST['key']]);
DB->prepare('UPDATE users SET type = "approved" WHERE id = :id')
->execute([':id' => $_SESSION['id']]);
$_SESSION['type'] = 'approved';
insert('approval-keys', ['key' => bin2hex(random_bytes(16))]);
output(200, 'Compte approuvé.');

28
pg-act/auth/login.php Normal file
View file

@ -0,0 +1,28 @@
<?php
checkPasswordFormat($_POST['password']);
checkUsernameFormat($_POST['username']);
$username = hashUsername($_POST['username']);
if (usernameExists($username) !== true)
output(403, 'Connexion impossible : ce compte n\'existe pas.');
$id = query('select', 'users', ['username' => $username], 'id')[0];
if (checkPassword($id, $_POST['password']) !== true)
output(403, 'Connexion impossible : clé de passe invalide.');
if (outdatedPasswordHash($id))
changePassword($id, $_POST['password']);
stopSession();
startSession();
$_SESSION['id'] = $id;
$_SESSION['display-username'] = htmlspecialchars($_POST['username']);
$_SESSION['type'] = query('select', 'users', ['id' => $id], 'type')[0];
redir();

10
pg-act/auth/password.php Normal file
View file

@ -0,0 +1,10 @@
<?php
checkPasswordFormat($_POST['new-password']);
if (checkPassword($_SESSION['id'], $_POST['current-password']) !== true)
output(403, 'Changement impossible : clé de passe invalide.');
changePassword($_SESSION['id'], $_POST['new-password']);
output(200, 'Clé de passe changée.');

50
pg-act/auth/register.php Normal file
View file

@ -0,0 +1,50 @@
<?php
checkPasswordFormat($_POST['password']);
checkUsernameFormat($_POST['username']);
$username = hashUsername($_POST['username']);
if (usernameExists($username) !== false)
output(403, 'Ce nom de compte est déjà utilisé.');
rateLimit();
$id = hash('sha256', random_bytes(32));
insert('users', [
'id' => $id,
'username' => $username,
'password' => hashPassword($_POST['password']),
'registration_date' => date('Y-m-d H:i:s'),
'bucket_tokens' => 0,
'bucket_last_update' => 0,
'type' => 'testing',
]);
// Setup SFTP directory
umask(0002);
if (mkdir(CONF['ht']['ht_path'] . '/' . $id, 0775) !== true)
output(500, 'Can\'t create user directory.');
exec(CONF['ht']['sudo_path'] . ' ' . CONF['ht']['chgrp_path'] . ' ' . CONF['ht']['sftpgo_group'] . ' ' . CONF['ht']['ht_path'] . '/' . $id . ' --no-dereference', result_code: $code);
if ($code !== 0)
output(500, 'Can\'t change user directory group.');
// Setup Tor config directory
if (mkdir(CONF['ht']['tor_config_path'] . '/' . $id, 0755) !== true)
output(500, 'Can\'t create Tor config directory.');
// Setup Tor keys directory
exec(CONF['ht']['sudo_path'] . ' -u ' . CONF['ht']['tor_user'] . ' ' . CONF['ht']['mkdir_path'] . ' --mode=0700 ' . CONF['ht']['tor_keys_path'] . '/' . $id, result_code: $code);
if ($code !== 0)
output(500, 'Can\'t create Tor keys directory.');
stopSession();
startSession();
$_SESSION['id'] = $id;
$_SESSION['display-username'] = htmlspecialchars($_POST['username']);
$_SESSION['type'] = 'testing';
redir();

View file

@ -0,0 +1,40 @@
<?php
if (!isset($_POST['delete']))
output(403, 'Il faut confirmer la suppression du compte');
foreach (query('select', 'registry', ['username' => $_SESSION['id']], 'domain') as $domain)
regDeleteDomain($domain);
foreach (query('select', 'zones', ['username' => $_SESSION['id']], 'zone') as $zone)
nsDeleteZone($zone);
foreach (query('select', 'sites', [
'username' => $_SESSION['id'],
'domain_type' => 'onion',
'protocol' => 'http',
], 'site_dir') as $dir)
htDeleteSite($dir, domainType: 'onion', protocol: 'http');
foreach (query('select', 'sites', [
'username' => $_SESSION['id'],
'domain_type' => 'dns',
'protocol' => 'http',
], 'site_dir') as $dir)
htDeleteSite($dir, domainType: 'dns', protocol: 'http');
exec(CONF['ht']['sudo_path'] . ' -u ' . CONF['ht']['tor_user'] . ' ' . CONF['ht']['rm_path'] . ' --recursive ' . CONF['ht']['tor_keys_path'] . '/' . $_SESSION['id'], result_code: $code);
if ($code !== 0)
output(500, 'Can\'t remove Tor keys directory.');
removeDirectory(CONF['ht']['tor_config_path'] . '/' . $_SESSION['id']);
exec(CONF['ht']['sudo_path'] . ' -u ' . CONF['ht']['sftpgo_user'] . ' ' . CONF['ht']['rm_path'] . ' --recursive ' . CONF['ht']['ht_path'] . '/' . $_SESSION['id'], result_code: $code);
if ($code !== 0)
output(500, 'Can\'t remove user\'s directory.');
query('delete', 'users', ['id' => $_SESSION['id']]);
logout();
output(200, 'Compte supprimé.');

15
pg-act/auth/username.php Normal file
View file

@ -0,0 +1,15 @@
<?php
checkUsernameFormat($_POST['new-username']);
$username = hashUsername($_POST['new-username']);
if (usernameExists($username) !== false)
output(403, 'Ce nom de compte est déjà utilisé.');
DB->prepare('UPDATE users SET username = :username WHERE id = :id')
->execute([':username' => $username, ':id' => $_SESSION['id']]);
$_SESSION['display-username'] = htmlspecialchars($_POST['new-username']);
output(200, 'Identifiant changé.');

View file

@ -0,0 +1,59 @@
<?php
$_POST['domain'] = formatDomain($_POST['domain']);
if (dirsStatuses('dns', 'http')[$_POST['dir']] !== false)
output(403, 'Wrong value for <code>dir</code>.');
if (query('select', 'sites', ['domain' => $_POST['domain']], 'domain') !== [])
output(403, 'Ce domaine existe déjà sur ce service.');
$remoteAaaaRecords = dns_get_record($_POST['domain'], DNS_AAAA);
if (is_array($remoteAaaaRecords) !== true)
output(500, 'Erreur lors de la récupération de l\'enregistrement AAAA.');
if (equalArrays([CONF['ht']['ipv6_address']], array_column($remoteAaaaRecords, 'ipv6')) !== true)
output(403, 'Ce domaine doit avoir pour unique enregistrement AAAA <code>' . CONF['ht']['ipv6_address'] . '</code>.');
$remoteARecords = dns_get_record($_POST['domain'], DNS_A);
if (is_array($remoteARecords) !== true)
output(500, 'Erreur lors de la récupération de l\'enregistrement A.');
if (equalArrays([CONF['ht']['ipv4_address']], array_column($remoteARecords, 'ip')) !== true)
output(403, 'Ce domaine doit avoir pour unique enregistrement A <code>' . CONF['ht']['ipv4_address'] . '</code>.');
$remoteTXTRecords = dns_get_record($_POST['domain'], DNS_TXT);
if (is_array($remoteTXTRecords) !== true)
output(500, 'Erreur lors de la récupération de l\'enregistrement TXT.');
if (preg_match('/^' . preg_quote(SERVER_NAME, '/') . '_domain-verification=([0-9a-f]{8})-([0-9a-f]{32})$/Dm', implode(LF, array_column($remoteTXTRecords, 'txt')), $matches) !== 1)
output(403, 'Aucun enregistrement TXT au format correct trouvé.');
checkAuthToken($matches[1], $matches[2]);
rateLimit();
addSite($_SESSION['id'], $_POST['dir'], $_POST['domain'], 'dns', 'http');
exec('2>&1 ' . CONF['ht']['sudo_path'] . ' ' . CONF['ht']['certbot_path'] . ' certonly' . (($_SESSION['type'] === 'approved') ? '' : ' --test-cert') . ' --key-type rsa --rsa-key-size 3072 --webroot --webroot-path /srv/niver/acme --domain ' . $_POST['domain'], $output, $returnCode);
if ($returnCode !== 0)
output(500, 'Certbot failed to get a Let\'s Encrypt certificate.', $output);
$nginxConf = 'server {
listen [' . CONF['ht']['ipv6_listen_address'] . ']:' . CONF['ht']['https_port'] . ' ssl http2;
listen ' . CONF['ht']['ipv4_listen_address'] . ':' . CONF['ht']['https_port'] . ' ssl http2;
server_name ' . $_POST['domain'] . ';
root ' . CONF['ht']['ht_path'] . '/' . $_SESSION['id'] . '/' . $_POST['dir'] . ';
ssl_certificate /etc/letsencrypt/live/' . $_POST['domain'] . '/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/' . $_POST['domain'] . '/privkey.pem;
include inc/ht-tls.conf;
}
';
if (file_put_contents(CONF['ht']['nginx_config_path'] . '/' . $_POST['domain'] . '.conf', $nginxConf) === false)
output(500, 'Failed to write Nginx configuration.');
// Reload Nginx
exec(CONF['ht']['sudo_path'] . ' ' . CONF['ht']['nginx_reload_cmd'], result_code: $code);
if ($code !== 0)
output(500, 'Failed to reload Nginx.');
output(200, 'Accès HTTP par domaine ajouté sur ce dossier !');

View file

@ -0,0 +1,47 @@
<?php
if (dirsStatuses('onion', 'http')[$_POST['dir']] !== false)
output(403, 'Wrong value for <code>dir</code>.');
rateLimit();
// Add Tor config
$torConf = 'HiddenServiceDir ' . CONF['ht']['tor_keys_path'] . '/' . $_SESSION['id'] . '/' . $_POST['dir'] . '/
HiddenServicePort 80 [::1]:' . CONF['ht']['internal_onion_http_port'] . '
';
if (file_put_contents(CONF['ht']['tor_config_path'] . '/' . $_SESSION['id'] . '/' . $_POST['dir'], $torConf) === false)
output(500, 'Failed to write new Tor configuration.');
// Reload Tor
exec(CONF['ht']['sudo_path'] . ' ' . CONF['ht']['tor_reload_cmd'], $output, $code);
if ($code !== 0)
output(500, 'Failed to reload Tor.');
// Get the address generated by Tor
exec(CONF['ht']['sudo_path'] . ' -u ' . CONF['ht']['tor_user'] . ' ' . CONF['ht']['cat_path'] . ' ' . CONF['ht']['tor_keys_path'] . '/' . $_SESSION['id'] . '/' . $_POST['dir'] . '/hostname', $output);
$onion = $output[0];
if (preg_match('/^[0-9a-z]{56}\.onion$/D', $onion) !== 1)
output(500, 'No onion address found.');
// Store it in the database
addSite($_SESSION['id'], $_POST['dir'], $onion, 'onion', 'http');
// Add Nginx config
$nginxConf = 'server {
listen [::1]:' . CONF['ht']['internal_onion_http_port'] . ';
server_name ' . $onion . ';
root ' . CONF['ht']['ht_path'] . '/' . $_SESSION['id'] . '/' . $_POST['dir'] . ';
include inc/ht-onion.conf;
}
';
if (file_put_contents(CONF['ht']['nginx_config_path'] . '/' . $onion . '.conf', $nginxConf) === false)
output(500, 'Failed to write Nginx configuration.');
// Reload Nginx
exec(CONF['ht']['sudo_path'] . ' ' . CONF['ht']['nginx_reload_cmd'], result_code: $code);
if ($code !== 0)
output(500, 'Failed to reload Nginx.');
// Tell the user their site address
output(200, 'L\'adresse de votre service Onion HTTP est : <a href="http://' . $onion . '/"><code>http://' . $onion . '/</code></a>');

View file

@ -0,0 +1,8 @@
<?php
if (dirsStatuses('dns', 'http')[$_POST['dir']] !== true)
output(403, 'Wrong value for <code>dir</code>.');
htDeleteSite($_POST['dir'], domainType: 'dns', protocol: 'http');
output(200, 'Accès retiré.');

View file

@ -0,0 +1,8 @@
<?php
if (dirsStatuses('onion', 'http')[$_POST['dir']] !== true)
output(403, 'Wrong value for <code>dir</code>.');
htDeleteSite($_POST['dir'], domainType: 'onion', protocol: 'http');
output(200, 'Accès retiré.');

23
pg-act/ns/caa.php Normal file
View file

@ -0,0 +1,23 @@
<?php
$values = nsParseCommonRequirements();
if (!($_POST['flag'] >= 0 AND $_POST['flag'] <= 255))
output(403, 'Wrong value for <code>flag</code>.');
if (!(preg_match('/^[a-z]{1,127}$/D', $_POST['tag'])))
output(403, 'Wrong value for <code>tag</code>.');
if (!(preg_match('/^[a-z0-9.-]{1,255}$/D', $_POST['value'])))
output(403, 'Wrong value for <code>value</code>.');
knotcZoneExec($_POST['zone'], [
$values['domain'],
$values['ttl'],
'CAA',
$_POST['flag'],
$_POST['tag'],
$_POST['value']
]);
output(200, 'Enregistrement ajouté/retiré.');

14
pg-act/ns/cname.php Normal file
View file

@ -0,0 +1,14 @@
<?php
$values = nsParseCommonRequirements();
$_POST['cname'] = formatAbsoluteDomain($_POST['cname']);
knotcZoneExec($_POST['zone'], [
$values['domain'],
$values['ttl'],
'CNAME',
$_POST['cname']
]);
output(200, 'Enregistrement ajouté/retiré.');

14
pg-act/ns/dname.php Normal file
View file

@ -0,0 +1,14 @@
<?php
$values = nsParseCommonRequirements();
$_POST['dname'] = formatAbsoluteDomain($_POST['dname']);
knotcZoneExec($_POST['zone'], [
$values['domain'],
$values['ttl'],
'DNAME',
$_POST['dname']
]);
output(200, 'Enregistrement ajouté/retiré.');

View file

@ -1,7 +1,8 @@
<?php
if (processForm() AND isset($_POST['zone-content'])) { // Update zone
nsCheckZonePossession($_POST['zone']);
nsCheckZonePossession($_POST['zone']);
if (isset($_POST['zone-content'])) { // Update zone
// Get current SOA record
$current_zone_content = file_get_contents(CONF['ns']['knot_zones_path'] . '/' . $_POST['zone'] . 'zone');
@ -58,85 +59,22 @@ if (processForm() AND isset($_POST['zone-content'])) { // Update zone
output(500, 'Failed to thaw zone file.', $output);
usleep(1000000);
output(200, 'La zone a été mise à jour.');
}
?>
// Display zone
<form method="post">
<label for="zone">Zone à modifier</label>
<br>
<select required="" name="zone" id="zone">
<option value="" disabled="" selected="">-</option>
<?php
if (isset($_SESSION['id']))
foreach (nsListUserZones($_SESSION['id']) as $zone)
echo ' <option value="' . $zone . '">' . $zone . '</option>' . LF;
?>
</select>
<br>
<input type="submit" value="Afficher">
</form>
$zone_content = file_get_contents(CONF['ns']['knot_zones_path'] . '/' . $_POST['zone'] . 'zone');
if ($zone_content === false)
output(500, 'Unable to read zone file.');
<?php
if (processForm()) { // Display zone
nsCheckZonePossession($_POST['zone']);
$zone_content = file_get_contents(CONF['ns']['knot_zones_path'] . '/' . $_POST['zone'] . 'zone');
if ($zone_content === false)
output(500, 'Unable to read zone file.');
$displayed_zone_content = '';
foreach(explode(LF, $zone_content) as $zone_line) {
if (empty($zone_line) OR str_starts_with($zone_line, ';'))
$data['zone_content'] = '';
foreach(explode(LF, $zone_content) 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)
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)
continue;
$displayed_zone_content .= $zone_line . LF;
}
$data['zone_content'] .= $zone_line . LF;
}
$displayed_zone_content .= LF;
?>
<form method="post">
<input type="hidden" name="zone" value="<?= $_POST['zone'] ?>">
<label for="zone-content">Nouveau contenu de la zone <code><strong><?= $_POST['zone'] ?></strong></code></label>
<textarea id="zone-content" name="zone-content" wrap="off" rows="<?= substr_count($displayed_zone_content, LF) + 1 ?>"><?= htmlspecialchars($displayed_zone_content) ?></textarea>
<br>
<input type="submit" value="Remplacer">
</form>
<?php
}
global $final_message;
displayFinalMessage();
?>
<h2>Valeurs par défaut</h2>
<p>Si le TTL est omis, il sera définit à <code><time datetime="PT<?= DEFAULT_TTL ?>S"><?= DEFAULT_TTL ?></time></code> secondes.</p>
<p>La précision de la classe (<code>IN</code>) est facultative.</p>
<h2>Valeurs autorisées</h2>
<p>La zone n'est pas autorisée à dépasser <?= ZONE_MAX_CHARACTERS ?> caractères.</p>
<p>Les TTLs ne sont autorisés qu'entre <code><time datetime="PT<?= MIN_TTL ?>S"><?= MIN_TTL ?></time></code> et <code><time datetime="PT<?= MAX_TTL ?>S"><?= MAX_TTL ?></time></code> secondes.</p>
<p>Les seuls types dont l'édition est autorisée sont :</p>
<ul>
<?php
foreach (ALLOWED_TYPES as $allowed_type)
echo ' <li><code>' . $allowed_type . '</code></li>';
?>
</ul>
$data['zone_content'] .= LF;

14
pg-act/ns/ip.php Normal file
View file

@ -0,0 +1,14 @@
<?php
$values = nsParseCommonRequirements();
$record = checkIpFormat($_POST['ip']);
knotcZoneExec($_POST['zone'], [
$values['domain'],
$values['ttl'],
$record,
$_POST['ip']
]);
output(200, 'Enregistrement ajouté/retiré.');

70
pg-act/ns/loc.php Normal file
View file

@ -0,0 +1,70 @@
<?php
$values = nsParseCommonRequirements();
if (empty($_POST['lat-min']))
$_POST['lat-min'] = 0;
if (empty($_POST['lat-sec']))
$_POST['lat-sec'] = 0;
if (empty($_POST['lon-min']))
$_POST['lon-min'] = 0;
if (empty($_POST['lon-sec']))
$_POST['lon-sec'] = 0;
if (empty($_POST['size']))
$_POST['size'] = 1;
if (empty($_POST['hp']))
$_POST['hp'] = 10000;
if (empty($_POST['vp']))
$_POST['vp'] = 10;
if (!($_POST['lat-deg'] >= 0 AND $_POST['lat-deg'] <= 90))
output(403, 'Wrong value for <code>lat-deg</code>.');
if (!($_POST['lat-min'] >= 0 AND $_POST['lat-min'] <= 59))
output(403, 'Wrong value for <code>lat-min</code>.');
if (!($_POST['lat-sec'] >= 0 AND $_POST['lat-sec'] <= 59.999))
output(403, 'Wrong value for <code>lat-sec</code>.');
if ($_POST['lat-dir'] !== 'N' AND $_POST['lat-dir'] !== 'S')
output(403, 'Wrong value for <code>lat-dir</code>.');
if (!($_POST['lon-deg'] >= 0 AND $_POST['lon-deg'] <= 180))
output(403, 'Wrong value for <code>lon-deg</code>.');
if (!($_POST['lon-min'] >= 0 AND $_POST['lon-min'] <= 59))
output(403, 'Wrong value for <code>lon-min</code>.');
if (!($_POST['lon-sec'] >= 0 AND $_POST['lon-sec'] <= 59.999))
output(403, 'Wrong value for <code>lon-sec</code>.');
if ($_POST['lon-dir'] !== 'E' AND $_POST['lon-dir'] !== 'W')
output(403, 'Wrong value for <code>lon-dir</code>.');
if (!($_POST['alt'] >= -100000 AND $_POST['alt'] <= 42849672.95))
output(403, 'Wrong value for <code>alt</code>.');
if (!($_POST['size'] >= 0 AND $_POST['size'] <= 90000000))
output(403, 'Wrong value for <code>size</code>.');
if (!($_POST['hp'] >= 0 AND $_POST['hp'] <= 90000000))
output(403, 'Wrong value for <code>hp</code>.');
if (!($_POST['vp'] >= 0 AND $_POST['vp'] <= 90000000))
output(403, 'Wrong value for <code>vp</code>.');
knotcZoneExec($_POST['zone'], [
$values['domain'],
$values['ttl'],
'LOC',
$_POST['lat-deg'],
$_POST['lat-min'],
$_POST['lat-sec'],
$_POST['lat-dir'],
$_POST['lon-deg'],
$_POST['lon-min'],
$_POST['lon-sec'],
$_POST['lon-dir'],
$_POST['alt'] . 'm',
$_POST['size'] . 'm',
$_POST['hp'] . 'm',
$_POST['vp'] . 'm',
]);
output(200, 'Enregistrement ajouté/retiré.');

18
pg-act/ns/mx.php Normal file
View file

@ -0,0 +1,18 @@
<?php
$values = nsParseCommonRequirements();
if (!($_POST['priority'] >= 0 AND $_POST['priority'] <= 255))
output(403, 'Wrong value for <code>priority</code>.');
$_POST['host'] = formatAbsoluteDomain($_POST['host']);
knotcZoneExec($_POST['zone'], [
$values['domain'],
$values['ttl'],
'MX',
$_POST['priority'],
$_POST['host']
]);
output(200, 'Enregistrement ajouté/retiré.');

14
pg-act/ns/ns.php Normal file
View file

@ -0,0 +1,14 @@
<?php
$values = nsParseCommonRequirements();
$_POST['ns'] = formatAbsoluteDomain($_POST['ns']);
knotcZoneExec($_POST['zone'], [
$values['domain'],
$values['ttl'],
'NS',
$_POST['ns']
]);
output(200, 'Enregistrement ajouté/retiré.');

25
pg-act/ns/print.php Normal file
View file

@ -0,0 +1,25 @@
<?php
nsCheckZonePossession($_POST['zone']);
$data['zone_name'] = $_POST['zone'];
$zone_content = file_get_contents(CONF['ns']['knot_zones_path'] . '/' . $data['zone_name'] . 'zone');
if ($zone_content === false)
output(500, 'Unable to read zone file.');
if ($_POST['print'] === 'raw')
$data['zone-raw'] = $zone_content;
elseif ($_POST['print'] === 'table')
$data['zone-table'] = parseZoneFile($zone_content, ALLOWED_TYPES);
elseif ($_POST['print'] === 'ds') {
$found = preg_match('/^' . preg_quote($data['zone_name'], '/') . '[\t ]+0[\t ]+CDS[\t ]+(?<tag>[0-9]{1,5})[\t ]+(?<algo>[0-9]{1,2})[\t ]+(?<digest_type>[0-9])[\t ]+(?<digest>[0-9A-F]{64})$/Dm', $zone_content, $data['zone-ds']);
if ($found !== 1)
output(500, 'Unable to get public key record from zone file.');
}
else
output(403, 'Wrong <code>print</code> method.');

26
pg-act/ns/srv.php Normal file
View file

@ -0,0 +1,26 @@
<?php
$values = nsParseCommonRequirements();
if (!($_POST['priority'] >= 0 AND $_POST['priority'] <= 65535))
output(403, 'Wrong value for <code>priority</code>.');
if (!($_POST['weight'] >= 0 AND $_POST['weight'] <= 65535))
output(403, 'Wrong value for <code>weight</code>.');
if (!($_POST['port'] >= 0 AND $_POST['port'] <= 65535))
output(403, 'Wrong value for <code>port</code>.');
$_POST['target'] = formatAbsoluteDomain($_POST['target']);
knotcZoneExec($_POST['zone'], [
$values['domain'],
$values['ttl'],
'SRV',
$_POST['priority'],
$_POST['weight'],
$_POST['port'],
$_POST['target']
]);
output(200, 'Enregistrement ajouté/retiré.');

23
pg-act/ns/sshfp.php Normal file
View file

@ -0,0 +1,23 @@
<?php
$values = nsParseCommonRequirements();
if (!($_POST['algo'] === '1' OR $_POST['algo'] === '3' OR $_POST['algo'] === '4'))
output(403, 'Wrong value for <code>algo</code>.');
if (!($_POST['type'] === '2'))
output(403, 'Wrong value for <code>type</code>.');
if (!(preg_match('/^[a-z0-9]{64}$/D', $_POST['fp'])))
output(403, 'Wrong value for <code>fp</code>.');
knotcZoneExec($_POST['zone'], [
$values['domain'],
$values['ttl'],
'SSHFP',
$_POST['algo'],
$_POST['type'],
$_POST['fp']
]);
output(200, 'Enregistrement ajouté/retiré.');

27
pg-act/ns/tlsa.php Normal file
View file

@ -0,0 +1,27 @@
<?php
$values = nsParseCommonRequirements();
if (!($_POST['use'] >= 0 AND $_POST['use'] <= 3))
output(403, 'Wrong value for <code>use</code>.');
if (!($_POST['selector'] === '0' OR $_POST['selector'] === '1'))
output(403, 'Wrong value for <code>selector</code>.');
if (!($_POST['type'] >= 0 AND $_POST['type'] <= 2))
output(403, 'Wrong value for <code>type</code>.');
if (!(preg_match('/^[a-zA-Z0-9.-]{1,1024}$/D', $_POST['content'])))
output(403, 'Wrong value for <code>content</code>.');
knotcZoneExec($_POST['zone'], [
$values['domain'],
$values['ttl'],
'TLSA',
$_POST['use'],
$_POST['selector'],
$_POST['type'],
$_POST['content']
]);
output(200, 'Enregistrement ajouté/retiré.');

15
pg-act/ns/txt.php Normal file
View file

@ -0,0 +1,15 @@
<?php
$values = nsParseCommonRequirements();
if (!(preg_match('/^[a-zA-Z0-9 .@=:!%$+\/\()[\]_-]{5,8192}$/D', $_POST['txt'])))
output(403, 'Wrong value for <code>txt</code>.');
knotcZoneExec($_POST['zone'], [
$values['domain'],
$values['ttl'],
'TXT',
'"' . $_POST['txt'] . '"'
]);
output(200, 'Enregistrement ajouté/retiré.');

52
pg-act/ns/zone-add.php Normal file
View file

@ -0,0 +1,52 @@
<?php
$_POST['domain'] = formatAbsoluteDomain($_POST['domain']);
if (query('select', 'zones', ['zone' => $_POST['domain']], 'zone') !== [])
output(403, 'Cette zone existe déjà sur ce service.');
exec(CONF['dns']['kdig_path'] . ' ' . ltrim(strstr($_POST['domain'], '.'), '.') . ' NS +short', $parentAuthoritatives);
if ($parentAuthoritatives === [])
output(403, 'Serveurs de noms de la zone parente introuvables');
foreach ($parentAuthoritatives as $parentAuthoritative)
checkAbsoluteDomainFormat($parentAuthoritative);
exec(CONF['dns']['kdig_path'] . ' ' . $_POST['domain'] . ' NS @' . $parentAuthoritatives[0] . ' +noidn', $results);
if (preg_match('/^' . preg_quote($_POST['domain'], '/') . '[\t ]+[0-9]{1,8}[\t ]+IN[\t ]+NS[\t ]+(?<salt>[0-9a-f]{8})-(?<hash>[0-9a-f]{32})\._domain-verification\.' . preg_quote(SERVER_NAME, '/') . '\.$/Dm', implode(LF, $results), $matches) !== 1)
output(403, 'Enregistrement d\'authentification introuvable');
checkAuthToken($matches['salt'], $matches['hash']);
rateLimit();
insert('zones', [
'zone' => $_POST['domain'],
'username' => $_SESSION['id'],
]);
$knotZonePath = CONF['ns']['knot_zones_path'] . '/' . $_POST['domain'] . 'zone';
$knotZone = implode(' ', [
$_POST['domain'],
SOA_VALUES['ttl'],
'SOA',
CONF['ns']['servers'][0],
SOA_VALUES['email'],
1,
SOA_VALUES['refresh'],
SOA_VALUES['retry'],
SOA_VALUES['expire'],
SOA_VALUES['negative'],
]) . LF;
foreach (CONF['ns']['servers'] as $server)
$knotZone .= $_POST['domain'] . ' 86400 NS ' . $server . LF;
if (is_int(file_put_contents($knotZonePath, $knotZone)) !== true)
output(500, 'Failed to write new zone file.');
if (chmod($knotZonePath, 0660) !== true)
output(500, 'Failed to chmod new zone file.');
knotcConfExec([
"set 'zone[" . $_POST['domain'] . "]'",
"set 'zone[" . $_POST['domain'] . "].template' 'niver'",
]);
output(200, 'La zone a été créée.');

7
pg-act/ns/zone-del.php Normal file
View file

@ -0,0 +1,7 @@
<?php
nsCheckZonePossession($_POST['zone']);
nsDeleteZone($_POST['zone']);
output(200, 'La zone a été supprimée.');

30
pg-act/reg/ds.php Normal file
View file

@ -0,0 +1,30 @@
<?php
if (
($_POST['algo'] !== '8')
AND ($_POST['algo'] !== '13')
AND ($_POST['algo'] !== '14')
AND ($_POST['algo'] !== '15')
AND ($_POST['algo'] !== '16')
) output(403, 'Wrong value for <code>algo</code>.');
$_POST['keytag'] = intval($_POST['keytag']);
if ((!preg_match('/^[0-9]{1,6}$/D', $_POST['keytag'])) 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>.');
regCheckDomainPossession($_POST['zone']);
knotcZoneExec(CONF['reg']['registry'], [
$_POST['zone'],
CONF['reg']['ttl'],
'DS',
$_POST['keytag'],
$_POST['algo'],
$_POST['dt'],
$_POST['key']
]);
output(200, 'Enregistrement ajouté/retiré.');

16
pg-act/reg/glue.php Normal file
View file

@ -0,0 +1,16 @@
<?php
regCheckDomainPossession($_POST['suffix']);
$domain = formatAbsoluteDomain(formatEndWithDot($_POST['subdomain']) . $_POST['suffix']);
$record = checkIpFormat($_POST['ip']);
knotcZoneExec(CONF['reg']['registry'], [
$domain,
CONF['reg']['ttl'],
$record,
$_POST['ip']
]);
output(200, 'Glue ajouté/retiré.');

13
pg-act/reg/ns.php Normal file
View file

@ -0,0 +1,13 @@
<?php
regCheckDomainPossession($_POST['domain']);
$_POST['ns'] = formatAbsoluteDomain($_POST['ns']);
knotcZoneExec(CONF['reg']['registry'], [
$_POST['domain'],
CONF['reg']['ttl'],
'NS',
$_POST['ns']
]);
output(200, 'Enregistrement ajouté/retiré.');

11
pg-act/reg/print.php Normal file
View file

@ -0,0 +1,11 @@
<?php
regCheckDomainPossession($_POST['domain']);
$zone_content = file_get_contents(CONF['reg']['registry_file']);
if ($zone_content === false)
output(500, 'Unable to read registry file.');
$data['zone-content'] = parseZoneFile($zone_content, ['A', 'AAAA', 'NS', 'DS'], $_POST['domain']);
output(200);

22
pg-act/reg/register.php Normal file
View file

@ -0,0 +1,22 @@
<?php
if (preg_match('/' . SUBDOMAIN_REGEX . '/D', $_POST['subdomain']) !== 1)
output(403, 'Le nom de domaine doit être composé uniquement d\'entre 4 et 63 lettres minuscules ou chiffre (a-z et 0-9)');
$domain = formatAbsoluteDomain($_POST['subdomain'] . '.' . CONF['reg']['registry']);
if (query('select', 'registry', ['domain' => $domain], 'domain') !== [])
output(403, 'Ce domaine n\'est pas disponible à l\'enregistrement. Il est déjà enregistré.');
if (in_array($_POST['subdomain'], explode(LF, file_get_contents(CONF['common']['root_path'] . '/pg-act/reg/reserved.txt'))))
output(403, 'Ce domaine n\'est pas disponible à l\'enregistrement. Il est réservé.');
rateLimit();
insert('registry', [
'domain' => $domain,
'username' => $_SESSION['id'],
'last_renewal' => date('Y-m-d H:i:s'),
]);
output(200, 'Domaine ajouté au registre.');

26
pg-act/reg/transfer.php Normal file
View file

@ -0,0 +1,26 @@
<?php
if (preg_match('/' . SUBDOMAIN_REGEX . '/D', $_POST['domain']) !== 1)
output(403, 'Le nom de domaine semble incorrect');
$domain = $_POST['domain'] . '.' . CONF['reg']['registry'];
if (query('select', 'registry', ['username' => $_SESSION['id'], 'domain' => $domain], 'domain') !== [])
output(403, 'Le compte présent possède déjà ce domaine.');
exec(CONF['dns']['kdig_path'] . ' ' . $domain . ' NS @' . CONF['reg']['address'] . ' +noidn', $results);
if (preg_match('/^' . preg_quote($domain, '/') . '[\t ]+[0-9]{1,8}[\t ]+IN[\t ]+NS[\t ]+(?<salt>[0-9a-f]{8})-(?<hash>[0-9a-f]{32})\._transfer-verification\.' . preg_quote(SERVER_NAME, '/') . '\.$/Dm', implode(LF, $results), $matches) !== 1)
output(403, 'Enregistrement d\'authentification introuvable');
checkAuthToken($matches['salt'], $matches['hash']);
DB->prepare('UPDATE registry SET username = :username WHERE domain = :domain')
->execute([':username' => $_SESSION['id'], ':domain' => $domain]);
knotcZoneExec(CONF['reg']['registry'], [
$domain,
'NS',
$matches['salt'] . '-' . $matches['hash'] . '._transfer-verification.' . SERVER_NAME . '.'
], 'delete');
output(200, 'Le domaine a été transféré vers le compte présent, l\'enregistrement d\'authentification a été automatiquement retiré.');

View file

@ -0,0 +1,7 @@
<?php
regCheckDomainPossession($_POST['domain']);
regDeleteDomain($_POST['domain']);
output(200, 'Domaine effacé du registre.');

10
pg-view/auth/approval.php Normal file
View file

@ -0,0 +1,10 @@
<p>
Ce formulaire permet d'utiliser une clé d'approbation pour valider son compte. Une clé d'approbation est distribuée par l'administrataire sur demande.
</p>
<form method="post">
<label for="key">Clé d'approbation</label><br>
<input required="" id="key" size="33" name="key" type="text" placeholder="27b81fbd8277b11ed1cf03d476cec503">
<br>
<input type="submit" value="Utiliser">
</form>

13
pg-view/auth/login.php Normal file
View file

@ -0,0 +1,13 @@
<p>Pas de compte ? <a href="register">En créer un</a></p>
<form method="post">
<label for="username">Identifiant</label><br>
<input required="" minlength="1" maxlength="1024" pattern="<?= USERNAME_REGEX ?>" id="username" name="username" type="text" placeholder="<?= PLACEHOLDER_USERNAME ?>">
<br>
<label for="password">Clé de passe</label><br>
<input required="" autocomplete="current-password" minlength="8" maxlength="1024" pattern="<?= PASSWORD_REGEX ?>" id="password" name="password" type="password" placeholder="<?= PLACEHOLDER_PASSWORD ?>">
<br>
<input type="submit">
</form>

3
pg-view/auth/logout.php Normal file
View file

@ -0,0 +1,3 @@
<?php
logout();

View file

@ -1,18 +1,3 @@
<?php
if (processForm()) {
checkPasswordFormat($_POST['new-password']);
if (checkPassword($_SESSION['id'], $_POST['current-password']) !== true)
output(403, 'Changement impossible : clé de passe invalide.');
changePassword($_SESSION['id'], $_POST['new-password']);
output(200, 'Clé de passe changée.');
}
?>
<p>
Vous pouvez ici changer la clé de passe permettant d'accéder à votre compte Niver.
</p>

17
pg-view/auth/register.php Normal file
View file

@ -0,0 +1,17 @@
<p>Déjà un compte ? <a href="login">Se connecter</a></p>
<form method="post">
<label for="username">Identifiant</label>
<br>
<input id="username" minlength="1" maxlength="1024" pattern="<?= USERNAME_REGEX ?>" required="" name="username" type="text" placeholder="<?= PLACEHOLDER_USERNAME ?>"><br>
<details>
<summary><label for="password">Clé de passe</label></summary>
<p>Une clé de passe sécurisée est trop compliquée à deviner pour une attaque qui testerait automatiquement plein de clés de passe tout en connaissant d'autres informations et secrets sur vous.</p>
<p>Minimum 8 caractères si elle contient minuscule, majuscule et chiffre, ou minimum 10 caractères sinon.</p>
</details>
<input autocomplete="new-password" id="password" minlength="8" maxlength="1024" pattern="<?= PASSWORD_REGEX ?>" required="" name="password" type="password" placeholder="<?= PLACEHOLDER_PASSWORD ?>">
<br>
<input type="submit">
</form>

View file

@ -0,0 +1,17 @@
<p>
Cette action supprimera toutes les données appartenant à ce compte, y compris :
</p>
<ul>
<li>la possession et la réservation des domaines dans le registre</li>
<li>les enregistrements DNS des zones hébergées sur le serveur de noms</li>
<li>le contenu des sites</li>
<li>les paires de clés des services Onion</li>
</ul>
<form method="post">
<input type="checkbox" name="delete" id="delete" required="">
<label for="delete">Supprimer mon compte et toutes ses données</label>
<br>
<input type="submit">
</form>

10
pg-view/auth/username.php Normal file
View file

@ -0,0 +1,10 @@
<p>
Vous pouvez ici changer l'identifiant permettant d'accéder à votre compte Niver.
</p>
<form method="post">
<label for="new-username">Nouvel identifiant</label><br>
<input required="" autocomplete="new-username" minlength="1" maxlength="1024" pattern="<?= USERNAME_REGEX ?>" id="new-username" name="new-username" type="text" placeholder="<?= PLACEHOLDER_USERNAME ?>"><br>
<input type="submit">
</form>

View file

@ -0,0 +1,45 @@
<?php
$dirsStatuses = dirsStatuses('dns', 'http');
$proof = getAuthToken();
?>
<p>
Ajouter sur un dossier de site un accès <?= linkToDocs('http', 'HTTP') ?> par <?= linkToDocs('dns', 'DNS') ?> et <?= linkToDocs('tls', 'TLS') ?> <?= linkToDocs('ca', 'authentifié par <em>Let\'s Encrypt</em>') ?>.
</p>
<p>
La présence des enregistrements ci-après sera vérifiée lors du traitement de ce formulaire.
</p>
<dl>
<dt><code>AAAA</code></dt>
<dd>
<code><?= CONF['ht']['ipv6_address'] ?></code>
</dd>
<dt><code>A</code></dt>
<dd>
<code><?= CONF['ht']['ipv4_address'] ?></code>
</dd>
<dt><code>TXT</code></dt>
<dd>
<code><?= SERVER_NAME ?>_domain-verification=<?= $proof ?></code>
</dd>
</dl>
<form method="post">
<label for="domain">Domaine sur lequel répondre</label><br>
<input required="" placeholder="site.<?= PLACEHOLDER_DOMAIN ?>" id="domain" name="domain" type="text"><br>
<label for="dir">Dossier ciblé</label><br>
<select required="" name="dir" id="dir">
<option value="" disabled="" selected="">---</option>
<?php
foreach ($dirsStatuses as $dir => $alreadyEnabled)
echo ' <option' . ($alreadyEnabled ? ' disabled=""' : '') . ' value="' . $dir . '">' . $dir . '</option>' . LF;
?>
</select>
<br>
<input value="Valider" type="submit">
</form>

View file

@ -0,0 +1,22 @@
<?php
$dirsStatuses = dirsStatuses('onion', 'http');
?>
<p>
Ajouter un accès en .onion sur un dossier
</p>
<form method="post">
<label for="dir">Dossier ciblé</label><br>
<select required="" name="dir" id="dir">
<option value="" disabled="" selected="">---</option>
<?php
foreach ($dirsStatuses as $dir => $alreadyEnabled)
echo ' <option' . ($alreadyEnabled ? ' disabled=""' : '') . ' value="' . $dir . '">' . $dir . '</option>' . LF;
?>
</select>
<br>
<input value="Valider" type="submit">
</form>

View file

@ -1,15 +1,6 @@
<?php
if (processForm()) {
if (dirsStatuses('dns', 'http')[$_POST['dir']] !== true)
output(403, 'Wrong value for <code>dir</code>.');
htDeleteSite($_POST['dir'], domainType: 'dns', protocol: 'http');
output(200, 'Accès retiré.');
}
$dirsStatuses = dirsStatuses('onion', 'http');
$dirsStatuses = dirsStatuses('dns', 'http');
?>

View file

@ -1,14 +1,5 @@
<?php
if (processForm()) {
if (dirsStatuses('onion', 'http')[$_POST['dir']] !== true)
output(403, 'Wrong value for <code>dir</code>.');
htDeleteSite($_POST['dir'], domainType: 'onion', protocol: 'http');
output(200, 'Accès retiré.');
}
$dirsStatuses = dirsStatuses('onion', 'http');
?>

View file

@ -1,31 +1,3 @@
<?php
if (processForm()) {
$values = nsParseCommonRequirements();
if (!($_POST['flag'] >= 0 AND $_POST['flag'] <= 255))
output(403, 'Wrong value for <code>flag</code>.');
if (!(preg_match('/^[a-z]{1,127}$/D', $_POST['tag'])))
output(403, 'Wrong value for <code>tag</code>.');
if (!(preg_match('/^[a-z0-9.-]{1,255}$/D', $_POST['value'])))
output(403, 'Wrong value for <code>value</code>.');
knotcZoneExec($_POST['zone'], array(
$values['domain'],
$values['ttl'],
'CAA',
$_POST['flag'],
$_POST['tag'],
$_POST['value']
));
output(200, 'Enregistrement ajouté/retiré.');
}
?>
<p>
<?= linkToDocs('record-caa', 'Documentation du type d\'enregistrement CAA') ?>
</p>

View file

@ -1,22 +1,3 @@
<?php
if (processForm()) {
$values = nsParseCommonRequirements();
$_POST['cname'] = formatAbsoluteDomain($_POST['cname']);
knotcZoneExec($_POST['zone'], array(
$values['domain'],
$values['ttl'],
'CNAME',
$_POST['cname']
));
output(200, 'Enregistrement ajouté/retiré.');
}
?>
<p>
<?= linkToDocs('record-cname', 'Documentation du type d\'enregistrement CNAME') ?>
</p>

View file

@ -1,22 +1,3 @@
<?php
if (processForm()) {
$values = nsParseCommonRequirements();
$_POST['dname'] = formatAbsoluteDomain($_POST['dname']);
knotcZoneExec($_POST['zone'], array(
$values['domain'],
$values['ttl'],
'DNAME',
$_POST['dname']
));
output(200, 'Enregistrement ajouté/retiré.');
}
?>
<p>
<?= linkToDocs('record-dname', 'Documentation du type d\'enregistrement DNAME') ?>
</p>

58
pg-view/ns/edit.php Normal file
View file

@ -0,0 +1,58 @@
<form method="post">
<label for="zone">Zone à modifier</label>
<br>
<select required="" name="zone" id="zone">
<option value="" disabled="" selected="">-</option>
<?php
foreach (nsListUserZones() as $zone)
echo ' <option value="' . $zone . '">' . $zone . '</option>' . LF;
?>
</select>
<br>
<input type="submit" value="Afficher">
</form>
<?php
if (isset($data['zone_content'])) { // Display zone
?>
<form method="post">
<input type="hidden" name="zone" value="<?= $_POST['zone'] ?>">
<label for="zone-content">Nouveau contenu de la zone <code><strong><?= $_POST['zone'] ?></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>
<br>
<input type="submit" value="Remplacer">
</form>
<?php
}
displayFinalMessage($data);
?>
<h2>Valeurs par défaut</h2>
<p>Si le TTL est omis, il sera définit à <code><time datetime="PT<?= DEFAULT_TTL ?>S"><?= DEFAULT_TTL ?></time></code> secondes.</p>
<p>La précision de la classe (<code>IN</code>) est facultative.</p>
<h2>Valeurs autorisées</h2>
<p>La zone n'est pas autorisée à dépasser <?= ZONE_MAX_CHARACTERS ?> caractères.</p>
<p>Les TTLs ne sont autorisés qu'entre <code><time datetime="PT<?= MIN_TTL ?>S"><?= MIN_TTL ?></time></code> et <code><time datetime="PT<?= MAX_TTL ?>S"><?= MAX_TTL ?></time></code> secondes.</p>
<p>Les seuls types dont l'édition est autorisée sont :</p>
<ul>
<?php
foreach (ALLOWED_TYPES as $allowed_type)
echo ' <li><code>' . $allowed_type . '</code></li>';
?>
</ul>

View file

@ -1,22 +1,3 @@
<?php
if (processForm()) {
$values = nsParseCommonRequirements();
$record = checkIpFormat($_POST['ip']);
knotcZoneExec($_POST['zone'], array(
$values['domain'],
$values['ttl'],
$record,
$_POST['ip']
));
output(200, 'Enregistrement ajouté/retiré.');
}
?>
<p>
<?= linkToDocs('record-ip', 'Documentation des types d\'enregistrements A et AAAA') ?>
</p>

View file

@ -1,78 +1,3 @@
<?php
if (processForm()) {
$values = nsParseCommonRequirements();
if (empty($_POST['lat-min']))
$_POST['lat-min'] = 0;
if (empty($_POST['lat-sec']))
$_POST['lat-sec'] = 0;
if (empty($_POST['lon-min']))
$_POST['lon-min'] = 0;
if (empty($_POST['lon-sec']))
$_POST['lon-sec'] = 0;
if (empty($_POST['size']))
$_POST['size'] = 1;
if (empty($_POST['hp']))
$_POST['hp'] = 10000;
if (empty($_POST['vp']))
$_POST['vp'] = 10;
if (!($_POST['lat-deg'] >= 0 AND $_POST['lat-deg'] <= 90))
output(403, 'Wrong value for <code>lat-deg</code>.');
if (!($_POST['lat-min'] >= 0 AND $_POST['lat-min'] <= 59))
output(403, 'Wrong value for <code>lat-min</code>.');
if (!($_POST['lat-sec'] >= 0 AND $_POST['lat-sec'] <= 59.999))
output(403, 'Wrong value for <code>lat-sec</code>.');
if ($_POST['lat-dir'] !== 'N' AND $_POST['lat-dir'] !== 'S')
output(403, 'Wrong value for <code>lat-dir</code>.');
if (!($_POST['lon-deg'] >= 0 AND $_POST['lon-deg'] <= 180))
output(403, 'Wrong value for <code>lon-deg</code>.');
if (!($_POST['lon-min'] >= 0 AND $_POST['lon-min'] <= 59))
output(403, 'Wrong value for <code>lon-min</code>.');
if (!($_POST['lon-sec'] >= 0 AND $_POST['lon-sec'] <= 59.999))
output(403, 'Wrong value for <code>lon-sec</code>.');
if ($_POST['lon-dir'] !== 'E' AND $_POST['lon-dir'] !== 'W')
output(403, 'Wrong value for <code>lon-dir</code>.');
if (!($_POST['alt'] >= -100000 AND $_POST['alt'] <= 42849672.95))
output(403, 'Wrong value for <code>alt</code>.');
if (!($_POST['size'] >= 0 AND $_POST['size'] <= 90000000))
output(403, 'Wrong value for <code>size</code>.');
if (!($_POST['hp'] >= 0 AND $_POST['hp'] <= 90000000))
output(403, 'Wrong value for <code>hp</code>.');
if (!($_POST['vp'] >= 0 AND $_POST['vp'] <= 90000000))
output(403, 'Wrong value for <code>vp</code>.');
knotcZoneExec($_POST['zone'], array(
$values['domain'],
$values['ttl'],
'LOC',
$_POST['lat-deg'],
$_POST['lat-min'],
$_POST['lat-sec'],
$_POST['lat-dir'],
$_POST['lon-deg'],
$_POST['lon-min'],
$_POST['lon-sec'],
$_POST['lon-dir'],
$_POST['alt'] . 'm',
$_POST['size'] . 'm',
$_POST['hp'] . 'm',
$_POST['vp'] . 'm',
));
output(200, 'Enregistrement ajouté/retiré.');
}
?>
<p>
<?= linkToDocs('record-loc', 'Documentation du type d\'enregistrement LOC') ?>
</p>

View file

@ -1,26 +1,3 @@
<?php
if (processForm()) {
$values = nsParseCommonRequirements();
if (!($_POST['priority'] >= 0 AND $_POST['priority'] <= 255))
output(403, 'Wrong value for <code>priority</code>.');
$_POST['host'] = formatAbsoluteDomain($_POST['host']);
knotcZoneExec($_POST['zone'], array(
$values['domain'],
$values['ttl'],
'MX',
$_POST['priority'],
$_POST['host']
));
output(200, 'Enregistrement ajouté/retiré.');
}
?>
<p>
<?= linkToDocs('record-mx', 'Documentation du type d\'enregistrement MX') ?>
</p>

View file

@ -1,22 +1,3 @@
<?php
if (processForm()) {
$values = nsParseCommonRequirements();
$_POST['ns'] = formatAbsoluteDomain($_POST['ns']);
knotcZoneExec($_POST['zone'], array(
$values['domain'],
$values['ttl'],
'NS',
$_POST['ns']
));
output(200, 'Enregistrement ajouté/retiré.');
}
?>
<p>
<?= linkToDocs('record-ns', 'Documentation du type d\'enregistrement NS') ?>
</p>

77
pg-view/ns/print.php Normal file
View file

@ -0,0 +1,77 @@
<form method="post">
<input type="radio" name="print" id="table" value="table" checked="">
<label for="table">Tableau de mes enregistrements</label>
<br>
<input type="radio" name="print" id="ds" value="ds">
<label for="ds">Enregistrement DS</label>
<br>
<input type="radio" name="print" id="raw" value="raw">
<label for="raw">Fichier de zone brut</label>
<br>
<label for="zone">Zone</label>
<select required="" name="zone" id="zone">
<option value="" disabled="" selected="">-</option>
<?php
foreach (nsListUserZones() as $zone)
echo ' <option value="' . $zone . '">' . $zone . '</option>' . LF;
?>
</select>
<br>
<input value="Afficher" type="submit">
</form>
<?php
if (isset($data['zone-raw']))
echo '<pre>' . htmlspecialchars($data['zone-raw']) . '</pre>';
if (isset($data['zone-table'])) { ?>
<table>
<tr>
<th>Domaine</th>
<th>TTL</th>
<th>Type</th>
<th>Contenu</th>
</tr>
<?php
foreach ($data['zone-table'] as $zone_line) {
echo ' <tr>' . LF;
foreach ($zone_line as $element)
echo ' <td><code>' . htmlspecialchars($element) . '</code></td>' . LF;
echo ' </tr>' . LF;
}
}
?>
</table>
<?php
if (isset($data['zone-ds'])) { ?>
<dl>
<dt>Zone</dt>
<dd>
<code><?= $_POST['zone'] ?></code>
</dd>
<dt>Tag</dt>
<dd>
<code><?= $data['zone-ds']['tag'] ?></code>
</dd>
<dt>Algorithme</dt>
<dd>
<code><?= $data['zone-ds']['algo'] ?></code><?= ($data['zone-ds']['algo'] === '15') ? ' (Ed25519)' : '' ?>
</dd>
<dt>Type de condensat</dt>
<dd>
<code><?= $data['zone-ds']['digest_type'] ?></code><?= ($data['zone-ds']['digest_type'] === '2') ? ' (SHA-256)' : '' ?>
</dd>
<dt>Condensat</dt>
<dd>
<code><?= $data['zone-ds']['digest'] ?></code>
</dd>
</dl>
<?php
}

View file

@ -1,34 +1,3 @@
<?php
if (processForm()) {
$values = nsParseCommonRequirements();
if (!($_POST['priority'] >= 0 AND $_POST['priority'] <= 65535))
output(403, 'Wrong value for <code>priority</code>.');
if (!($_POST['weight'] >= 0 AND $_POST['weight'] <= 65535))
output(403, 'Wrong value for <code>weight</code>.');
if (!($_POST['port'] >= 0 AND $_POST['port'] <= 65535))
output(403, 'Wrong value for <code>port</code>.');
$_POST['target'] = formatAbsoluteDomain($_POST['target']);
knotcZoneExec($_POST['zone'], array(
$values['domain'],
$values['ttl'],
'SRV',
$_POST['priority'],
$_POST['weight'],
$_POST['port'],
$_POST['target']
));
output(200, 'Enregistrement ajouté/retiré.');
}
?>
<p>
<?= linkToDocs('record-srv', 'Documentation du type d\'enregistrement SRV') ?>
</p>

View file

@ -1,31 +1,3 @@
<?php
if (processForm()) {
$values = nsParseCommonRequirements();
if (!($_POST['algo'] === '1' OR $_POST['algo'] === '3' OR $_POST['algo'] === '4'))
output(403, 'Wrong value for <code>algo</code>.');
if (!($_POST['type'] === '2'))
output(403, 'Wrong value for <code>type</code>.');
if (!(preg_match('/^[a-z0-9]{64}$/D', $_POST['fp'])))
output(403, 'Wrong value for <code>fp</code>.');
knotcZoneExec($_POST['zone'], array(
$values['domain'],
$values['ttl'],
'SSHFP',
$_POST['algo'],
$_POST['type'],
$_POST['fp']
));
output(200, 'Enregistrement ajouté/retiré.');
}
?>
<p>
<?= linkToDocs('record-sshfp', 'Documentation du type d\'enregistrement SSHFP') ?>
</p>

View file

@ -1,35 +1,3 @@
<?php
if (processForm()) {
$values = nsParseCommonRequirements();
if (!($_POST['use'] >= 0 AND $_POST['use'] <= 3))
output(403, 'Wrong value for <code>use</code>.');
if (!($_POST['selector'] === '0' OR $_POST['selector'] === '1'))
output(403, 'Wrong value for <code>selector</code>.');
if (!($_POST['type'] >= 0 AND $_POST['type'] <= 2))
output(403, 'Wrong value for <code>type</code>.');
if (!(preg_match('/^[a-zA-Z0-9.-]{1,1024}$/D', $_POST['content'])))
output(403, 'Wrong value for <code>content</code>.');
knotcZoneExec($_POST['zone'], array(
$values['domain'],
$values['ttl'],
'TLSA',
$_POST['use'],
$_POST['selector'],
$_POST['type'],
$_POST['content']
));
output(200, 'Enregistrement ajouté/retiré.');
}
?>
<p>
<?= linkToDocs('record-tlsa', 'Documentation du type d\'enregistrement TLSA') ?>
</p>

View file

@ -1,23 +1,3 @@
<?php
if (processForm()) {
$values = nsParseCommonRequirements();
if (!(preg_match('/^[a-zA-Z0-9 .@=:!%$+\/\()[\]_-]{5,8192}$/D', $_POST['txt'])))
output(403, 'Wrong value for <code>txt</code>.');
knotcZoneExec($_POST['zone'], array(
$values['domain'],
$values['ttl'],
'TXT',
'"' . $_POST['txt'] . '"'
));
output(200, 'Enregistrement ajouté/retiré.');
}
?>
<p>
<?= linkToDocs('record-txt', 'Documentation du type d\'enregistrement TXT') ?>
</p>

19
pg-view/ns/zone-add.php Normal file
View file

@ -0,0 +1,19 @@
<p>
Pour prouver que vous possédez bien ce domaine, il doit posséder un <?= linkToDocs('ns-record', 'enregistrement NS') ?> égal à <code><?= $proof ?>._domain-verification.<?= SERVER_NAME ?>.</code> lors du traitement de ce formulaire.
</p>
<p>
La zone sera servie par ces serveurs de noms :
<ul>
<?php
foreach (CONF['ns']['servers'] as $server)
echo ' <li><code>' . $server . '</code></li>';
?>
</ul>
</p>
<form method="post">
<label for="domain">Domaine</label><br>
<input required="" placeholder="domain.<?= PLACEHOLDER_DOMAIN ?>." id="domain" name="domain" type="text"><br>
<input value="Ajouter" type="submit">
</form>

12
pg-view/ns/zone-del.php Normal file
View file

@ -0,0 +1,12 @@
<form method="post">
<label for="zone">Zone</label>
<select required="" name="zone" id="zone">
<option value="" disabled="" selected="">-</option>
<?php
foreach (nsListUserZones() as $zone)
echo ' <option value="' . $zone . '">' . $zone . '</option>' . LF;
?>
</select>
<br>
<input value="Supprimer toutes les données liées à cette zone" type="submit">
</form>

View file

@ -1,45 +1,3 @@
<?php
if (isset($_SESSION['id']))
$domains = regListUserDomains($_SESSION['id']);
else
$domains = [];
if (processForm()) {
if (
($_POST['algo'] !== '8')
AND ($_POST['algo'] !== '13')
AND ($_POST['algo'] !== '14')
AND ($_POST['algo'] !== '15')
AND ($_POST['algo'] !== '16')
) output(403, 'Wrong value for <code>algo</code>.');
$_POST['keytag'] = intval($_POST['keytag']);
if ((!preg_match('/^[0-9]{1,6}$/D', $_POST['keytag'])) 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>.');
regCheckDomainPossession($_POST['zone']);
$action = checkAction($_POST['action']);
knotcZoneExec(CONF['reg']['registry'], array(
$_POST['zone'],
CONF['reg']['ttl'],
'DS',
$_POST['keytag'],
$_POST['algo'],
$_POST['dt'],
$_POST['key']
));
output(200, 'Enregistrement ajouté/retiré.');
}
?>
<p>
Ici vous pouvez indiquer au registre l'enregistrement DS d'une zone afin de permettre de déléguer la confiance <?= linkToDocs('dnssec', 'DNSSEC') ?>.
</p>
@ -56,7 +14,7 @@ if (processForm()) {
<select required="" name="zone" id="zone">
<option value="" disabled="" selected="">---</option>
<?php
foreach ($domains as $domain)
foreach (regListUserDomains() as $domain)
echo ' <option value="' . $domain . '">' . $domain . '</option>' . LF;
?>
</select>

View file

@ -1,24 +1,3 @@
<?php
if (processForm()) {
regCheckDomainPossession($_POST['suffix']);
$domain = formatAbsoluteDomain(formatEndWithDot($_POST['subdomain']) . $_POST['suffix']);
$record = checkIpFormat($_POST['ip']);
knotcZoneExec(CONF['reg']['registry'], array(
$domain,
CONF['reg']['ttl'],
$record,
$_POST['ip']
));
output(200, 'Glue ajouté/retiré.');
}
?>
<p>
<?= linkToDocs('glue-record', 'Documentation sur le glue record'); ?>
</p>
@ -42,9 +21,8 @@ if (processForm()) {
<select required="" name="suffix" id="suffix">
<option value="" disabled="" selected="">---</option>
<?php
if (isset($_SESSION['id']))
foreach(regListUserDomains($_SESSION['id']) as $suffix)
echo ' <option value="' . $suffix . '">' . $suffix . '</option>' . LF;
foreach(regListUserDomains() as $suffix)
echo ' <option value="' . $suffix . '">' . $suffix . '</option>' . LF;
?>
</select>
</div>

View file

@ -1,21 +1,3 @@
<?php
if (processForm()) {
regCheckDomainPossession($_POST['domain']);
$_POST['ns'] = formatAbsoluteDomain($_POST['ns']);
knotcZoneExec(CONF['reg']['registry'], array(
$_POST['domain'],
CONF['reg']['ttl'],
'NS',
$_POST['ns']
));
output(200, 'Enregistrement ajouté/retiré.');
}
?>
<p>
<?= linkToDocs('record-ns', 'Documentation du type d\'enregistrement NS') ?>
</p>
@ -32,9 +14,8 @@ if (processForm()) {
<select required="" name="domain" id="domain">
<option value="" disabled="" selected="">---</option>
<?php
if (isset($_SESSION['id']))
foreach (regListUserDomains($_SESSION['id']) as $domain)
echo ' <option value="' . $domain . '">' . $domain . '</option>' . LF;
foreach (regListUserDomains() as $domain)
echo ' <option value="' . $domain . '">' . $domain . '</option>' . LF;
?>
</select>
<br>

34
pg-view/reg/print.php Normal file
View file

@ -0,0 +1,34 @@
<form method="post">
<label for="domain">Domaine</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>
<br>
<input value="Afficher" type="submit">
</form>
<table>
<tr>
<th>Domaine</th>
<th>TTL</th>
<th>Type</th>
<th>Contenu</th>
</tr>
<?php
if (isset($data['zone-content'])) {
foreach ($data['zone-content'] as $zone_line) {
echo ' <tr>' . LF;
foreach ($zone_line as $element)
echo ' <td><code>' . htmlspecialchars($element) . '</code></td>' . LF;
echo ' </tr>' . LF;
}
}
?>
</table>

11
pg-view/reg/register.php Normal file
View file

@ -0,0 +1,11 @@
<p>
Enregistrer un nouveau domaine sur son compte. Ce domaine doit être composé uniquement d'au moins 4 lettres latines non accentuées (a-z).
</p>
<form method="post">
<label for="subdomain">Sous-domaine</label>
<br>
<code><input id="subdomain" pattern="<?= SUBDOMAIN_REGEX ?>" required="" placeholder="niver" name="subdomain" type="text">.<?= CONF['reg']['registry'] ?></code>
<br>
<input value="Enregistrer" type="submit">
</form>

242
pg-view/reg/reserved.txt Normal file
View file

@ -0,0 +1,242 @@
# List of subdomains not available to register
#
# They may be forbidden because:
# - they may be privileged for impersonating Niver, spamming or fishing
# - they are reserved for a project asking for it and deserving such a well-known name
niver
# Registry-related
nic
domain
domains
reg
registry
# Special subdomains
autoconfig
autodiscover
# Special TLDs
example
invalid
test
local
localhost
onion
# Standard-related
ns0
ns1
ns2
ns3
ns4
ns5
ns6
ns7
ns8
ns9
dns
dns0
dns1
dns2
dns3
dns4
dns5
dns6
dns7
dns8
dns9
www
wwww
www0
www1
www2
www3
www4
www5
www6
www7
www8
www9
srv
srv0
srv1
srv2
srv3
srv4
srv5
srv6
srv7
srv8
srv9
ssh
sftp
http
https
ssl
tls
mtx
matrix
gmi
gemini
ftp
ftps
mx
imap
imaps
smtp
smtps
pop
xmpp
fedi
html
rss
ipv4
ipv6
# Prevent account fishing
account
accounts
register
profile
signup
login
auth
authenticate
connect
# Commercial
com
free
trial
ads
bank
banks
business
customer
customers
store
stores
shop
shops
job
jobs
marketing
sales
# Miscellaneous
org
net
com
gov
gouv
edu
api
cdn
support
admin
web
dev
host
portal
beta
alpha
demo
vpn
temp
root
data
stats
chat
about
remote
portal
boost
core
learn
community
meta
news
public
online
join
mobile
tech
space
zone
name
access
search
static
secure
security
bbs
help
info
code
doc
docs
server
servers
client
clients
mail
mails
email
emails
webmail
site
sites
website
websites
blog
blogs
gemlog
gemlogs
capsule
capsules
source
sources
update
updates
forum
forums
service
services
ressource
ressources
image
images
video
videos
radio
radios
music
map
maps
app
apps
dev
devs
developer
developers
social
cloud
clouds
network
networks
survey
surveys
build
builds
upload
uploads
download
downloads
content
contents
drive
drives
home
homes

11
pg-view/reg/transfer.php Normal file
View file

@ -0,0 +1,11 @@
<p>
Pour prouver que vous êtes autorisé à recevoir le domaine par san possessaire actuele, ledit domaine doit posséder un <?= linkToDocs('ns-record', 'enregistrement NS') ?> égal à <code><?= getAuthToken() ?>._transfer-verification.<?= SERVER_NAME ?>.</code> lors du traitement de ce formulaire. Cet enregistrement sera automatiquement retiré une fois validé.
</p>
<form method="post">
<label for="subdomain">Sous-domaine à recevoir</label>
<br>
<code><input required="" placeholder="subdomain" id="subdomain" name="subdomain" type="text">.<?= CONF['reg']['registry'] ?></code>
<br>
<input value="Recevoir ce domaine" type="submit">
</form>

View file

@ -1,15 +1,3 @@
<?php
if (processForm()) {
regCheckDomainPossession($_POST['domain']);
regDeleteDomain($_POST['domain']);
output(200, 'Domaine effacé du registre.');
}
?>
<p>
Ceci désenregistrera le domaine, et le rendra ainsi à nouveau disponible à l'enregistrement par n'importe qui.
</p>
@ -20,9 +8,8 @@ if (processForm()) {
<select required="" name="domain" id="domain">
<option value="" disabled="" selected="">---</option>
<?php
if (isset($_SESSION['id']))
foreach(regListUserDomains($_SESSION['id']) as $domain)
echo ' <option value="' . $domain . '">' . $domain . '</option>' . LF;
foreach(regListUserDomains() as $domain)
echo ' <option value="' . $domain . '">' . $domain . '</option>' . LF;
?>
</select>
<br>

View file

@ -1,4 +1,5 @@
<?php
define('TIME', hrtime(true));
define('CONF', parse_ini_file(__DIR__ . '/config.ini', true, INI_SCANNER_TYPED));
foreach (array_diff(scandir(CONF['common']['root_path'] . '/fn'), array('..', '.')) as $file)
@ -31,7 +32,8 @@ function getPageInformations($pages, $pageElements) {
if (!isset($pages['index']) OR $pageElements[0] === 'index')
return [
'titles_lineage' => [$pages[$pageElements[0]]['title'] ?? false],
'page_metadata' => $pages[$pageElements[0]] ?? NULL
'page_metadata' => $pages[$pageElements[0]] ?? NULL,
'terminal' => $pageElements[0] !== 'index'
];
$result = $pages['index']['title'];
if (!isset($pageElements[1]))
@ -45,6 +47,7 @@ function getPageInformations($pages, $pageElements) {
$pageInformations = getPageInformations(PAGES, PAGE_LINEAGE);
define('TITLES_LINEAGE', array_reverse($pageInformations['titles_lineage']));
define('PAGE_METADATA', $pageInformations['page_metadata']);
define('PAGE_TERMINAL', $pageInformations['terminal']);
if (!TITLES_LINEAGE[array_key_last(TITLES_LINEAGE)]) {
http_response_code(404);
@ -52,11 +55,7 @@ if (!TITLES_LINEAGE[array_key_last(TITLES_LINEAGE)]) {
}
const SESSION_COOKIE_NAME = 'niver-session-key';
if (
isset($_COOKIE[SESSION_COOKIE_NAME]) // Resume session
OR
(isset($_POST['username']) AND in_array(PAGE_URL, ['auth/login', 'auth/register'])) // Start new session
) {
function startSession() {
session_start([
'name' => SESSION_COOKIE_NAME,
'sid_length' => 64,
@ -72,44 +71,8 @@ if (
'use_only_cookies' => true,
]);
}
?>
<!DOCTYPE html>
<html lang="fr"<?php if (!empty(SERVICE)) echo ' class="' . SERVICE . '"'; ?>>
<head>
<meta charset="utf-8">
<title><?php
foreach(array_reverse(TITLES_LINEAGE) as $id => $title)
echo strip_tags($title) . (array_key_last(TITLES_LINEAGE) === $id ? '' : ' < ');
?></title>
<?php
foreach (glob('css/*.css') as $cssPath)
echo ' <link type="text/css" rel="stylesheet" media="screen" href="' . CONF['common']['prefix'] . '/' . $cssPath . '">' . LF;
?>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<header>
<p>
<?php if (isset($_SESSION['id'])) { ?>
<?= ($_SESSION['type'] === 'approved') ? '<span title="Compte approuvé">👤 </span>' : '<span title="Compte de test">⏳ </span>' ?><strong><?= $_SESSION['display-username'] ?></strong> <a class="auth" href="<?= CONF['common']['prefix'] ?>/auth/logout">Se déconnecter</a>
<?php } else { ?>
<span aria-hidden="true">👻 </span><em>Anonyme</em> <a class="auth" href="<?= redirUrl('auth/login') ?>">Se connecter</a>
<?php } ?>
</p>
<nav>
<?php
foreach (TITLES_LINEAGE as $id => $title) {
$lastTitle = (TITLES_LINEAGE[array_key_last(TITLES_LINEAGE)] === $title);
echo '<ul><li>' . ($lastTitle ? '<h1>' : '') . '<a' . (($id === 0) ? ' class="niver"' : '') . ' href="' . CONF['common']['prefix'] . ($lastTitle ? '/' . PAGE_URL : '/' . implode('/', array_slice(PAGE_LINEAGE, 0, $id)) . (($lastTitle OR $id === 0) ? '' : '/')) . '">' . $title . '</a>' . ($lastTitle ? '</h1>' : '') . LF;
}
echo str_repeat('</li></ul>', count(TITLES_LINEAGE));
?>
</nav>
</header>
<main>
<?php
if (isset($_COOKIE[SESSION_COOKIE_NAME]))
startSession(); // Resume session
if (in_array(SERVICE, ['reg', 'ns', 'ht']) AND CONF[SERVICE]['enabled'] !== true)
output(403, 'Ce service est désactivé.');
@ -128,24 +91,26 @@ if (in_array($_SERVER['SERVER_NAME'], CONF['common']['public_domains'], true) !=
output(500, 'The current server name is not allowed in configuration.');
define('SERVER_NAME', $_SERVER['SERVER_NAME']);
function displayFinalMessage() {
global $final_message;
echo $final_message ?? '';
$final_message = NULL;
function displayFinalMessage($data) {
if (isset($data['final_message'])) {
echo $data['final_message'];
unset($data['final_message']);
}
}
function executePage() {
require 'pages/' . PAGE_ADDRESS . '.php';
if ($_POST !== []) {
if (PAGE_METADATA['require-login'] ?? true !== false) {
if (isset($_SESSION['id']) !== true)
output(403, 'Vous devez être connecté·e à un compte pour effectuer cette action.');
if (isset(query('select', 'users', ['id' => $_SESSION['id']], 'id')[0]) !== true)
output(403, 'Ce compte n\'existe plus. Déconnectez-vous pour terminer cette session fantôme.');
}
if (file_exists('pg-act/' . PAGE_ADDRESS . '.php'))
require 'pg-act/' . PAGE_ADDRESS . '.php';
}
displayFinalMessage();
?>
</main>
<footer>
<small><a rel="external" href="https://code.antopie.org/niver/niver" class="niver">Code source</a> sous <abbr title="Cooperative Nonviolent Public License No Attribution version 7 ou plus">CNPL-NAv7+</abbr>.</small>
</footer>
</body>
</html>
<?php
function displayPage($data) {
require 'view.php';
exit();
}
executePage();
displayPage($data ??= NULL);

51
view.php Normal file
View file

@ -0,0 +1,51 @@
<!DOCTYPE html>
<html lang="fr"<?php if (!empty(SERVICE)) echo ' class="' . SERVICE . '"'; ?>>
<head>
<meta charset="utf-8">
<title><?php
foreach(array_reverse(TITLES_LINEAGE) as $id => $title)
echo strip_tags($title) . (array_key_last(TITLES_LINEAGE) === $id ? '' : ' < ');
?></title>
<?php
foreach (glob('css/*.css') as $css_path)
echo ' <link type="text/css" rel="stylesheet" media="screen" href="' . CONF['common']['prefix'] . '/' . $css_path . '">' . LF;
?>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<header>
<p>
<?php if (isset($_SESSION['id'])) { ?>
<?= ($_SESSION['type'] === 'approved') ? '<span title="Compte approuvé">👤 </span>' : '<span title="Compte de test">⏳ </span>' ?><strong><?= $_SESSION['display-username'] ?></strong> <a class="auth" href="<?= CONF['common']['prefix'] ?>/auth/logout">Se déconnecter</a>
<?php } else { ?>
<span aria-hidden="true">👻 </span><em>Anonyme</em> <a class="auth" href="<?= redirUrl('auth/login') ?>">Se connecter</a>
<?php } ?>
</p>
<nav>
<?php
foreach (TITLES_LINEAGE as $id => $title) {
$lastTitle = (TITLES_LINEAGE[array_key_last(TITLES_LINEAGE)] === $title);
echo '<ul><li>' . ($lastTitle ? '<h1>' : '') . '<a' . (($id === 0) ? ' class="niver"' : '') . ' href="' . CONF['common']['prefix'] . ($lastTitle ? '/' . PAGE_URL : '/' . implode('/', array_slice(PAGE_LINEAGE, 0, $id)) . (($lastTitle OR $id === 0) ? '' : '/')) . '">' . $title . '</a>' . ($lastTitle ? '</h1>' : '') . LF;
}
echo str_repeat('</li></ul>', count(TITLES_LINEAGE));
?>
</nav>
</header>
<main>
<?php
require 'pg-view/' . PAGE_ADDRESS . '.php';
if ($_POST === [] AND PAGE_METADATA['require-login'] ?? true !== false AND !isset($_SESSION['id']) AND PAGE_TERMINAL)
echo '<p>Ce formulaire ne sera pas accepté car il faut <a class="auth" href="' . redirUrl('auth/login') . '">se connecter</a> avant.</p>';
displayFinalMessage($data);
?>
</main>
<footer>
<small><a rel="external" href="https://code.antopie.org/niver/niver" class="niver">Code source</a> sous <abbr title="Cooperative Nonviolent Public License No Attribution version 7 ou plus">CNPL-NAv7+</abbr>.</small>
</footer>
</body>
</html>