This repository has been archived on 2024-10-05. You can view files and clone it, but cannot push or open issues or pull requests.
railgun-dns-old/index.php
2024-10-05 16:31:35 +02:00

1674 lines
80 KiB
PHP

<?php
define('RGDNS_PREFIX', 'rgdns_');
define('RGDNS_TITLE', 'Railgun DNS');
define('RGDNS_RECORD_TYPES', [
'A', 'AAAA', 'CNAME', 'MX', 'TXT',
]);
define('RGDNSP_MANAGE_RECORDS', 0x0001);
define('RGDNSP_MANAGE_DOMAINS', 0x0002);
define('RGDNSP_EDIT_SETTINGS', 0x0004);
define('RGDNSP_ADD_DOMAINS', 0x0008 | RGDNSP_MANAGE_DOMAINS);
define('RGDNSP_ADD_RECORDS', 0x0010 | RGDNSP_MANAGE_RECORDS);
define('RGDNSP_MANAGE_USERS', 0x0100 | RGDNSP_EDIT_SETTINGS);
define('RGDNSP_MANAGE_ALL_DOMAINS', 0x0200 | RGDNSP_MANAGE_DOMAINS);
define('RGDNSP_MANAGE_ALL_RECORDS', 0x0400 | RGDNSP_MANAGE_RECORDS | RGDNSP_MANAGE_ALL_DOMAINS);
define('RGDNSP_MANAGE_NAMESERVERS', 0x0800);
define('RGDNSP_VIEW_LOGIN_ATTEMPTS', 0x1000);
define('RGDNSP_EDIT_PERMISSIONS', 0x2000 | RGDNSP_MANAGE_USERS);
define('RGDNSP_REMOVE_AUTHENTICATOR', 0x4000 | RGDNSP_EDIT_PERMISSIONS);
mb_internal_encoding('utf-8');
date_default_timezone_set('utc');
ini_set('display_errors', 'on');
error_reporting(-1);
function rgDnsHead(string $title = '', array $headers = []): void {
$title = empty($title) ? RGDNS_TITLE : (htmlspecialchars($title) . ' :: ' . RGDNS_TITLE);
$html = '<!doctype html><html><head><meta charset="utf-8"/><title>'
. $title
. '</title><link href="style.css" type="text/css" rel="stylesheet"/>';
foreach($headers as $header) {
$header = (object)$header;
$html .= '<' . $header->tag;
if(isset($header->props))
$html .= rgDnsConstructProps($header->props);
$html .= empty($header->close) ? ' />' : '></' . $header->tag . '>';
}
$html .= '</head><body class="rgdns"><div class="rgdns-header"><h1 class="rgdns-logo">Railgun DNS</h1><div class="rgdns-navigation">';
if(rgDnsLoggedIn()) {
$html .= '<a href="?">Main Menu</a> | ';
$html .= 'Logged in as ' . $GLOBALS['userInfo']->user_name . ' [ ';
$html .= '<a href="?mode=logout&amp;sess=' . session_id() . '&amp;time=' . time() . '">Log out</a> ]';
} else $html .= '<a href="?">Log in</a>';
$html .= '</div></div><div class="rgdns-container">';
echo $html;
}
function rgDnsFoot(): void {
$html = '</div><div class="rgdns-footer">&copy; flashwave 2021</div></body></html>';
echo $html;
}
function rgDnsConstructProps(array $props): string {
$html = '';
foreach($props as $name => $value)
$html .= ' ' . $name . (empty($value) ? '' : '="' . htmlspecialchars($value) . '"');
return $html;
}
function rgDnsForm(string $action, array $fields, array $buttons = [], string $method = 'post', string $encType = 'application/x-www-form-urlencoded'): void {
$html = '<form method="' . $method . '" action="' . $action . '" enctype="' . $encType . '" class="rgdns-form">';
foreach($fields as $name => $info)
if(empty($info['hidden']))
$html .= rgDnsFormField($name, $info);
$html .= '<div class="rgdns-form-buttons">';
if(empty($buttons))
$buttons = ['Submit'];
foreach($buttons as $button) {
if(is_string($button))
$button = ['text' => $button];
$html .= '<button class="rgdns-button" type="' . ($button['type'] ?? 'submit') . '"' . (empty($button['disable']) ? '' : ' disabled') . '>' . $button['text'] . '</button>';
}
echo $html . '</div></form>';
}
function rgDnsFormField(string $name, array $fi): string {
$fi = (object)$fi; // objects are less annoying to address
switch($fi->type = ($fi->type ?? 'text')) {
case 'hidden':
return '<input type="' . $fi->type . '" name="' . $name . '" value="' . htmlspecialchars($fi->value ?? '') . '" />';
case 'checkbox':
return '<label class="rgdns-form-field rgdns-form-field-checkbox"><div class="rgdns-form-field-value">'
. '<input class="rgdns-checkbox" type="' . $fi->type . '" name="' . $name . '"' . (empty($fi->value) ? '' : ' checked') . '' . (empty($fi->disable) ? '' : ' disabled') . (empty($fi->props) ? '' : rgDnsConstructProps($fi->props)) . ' />'
. '</div><div class="rgdns-form-field-title">' . ($fi->title ?? 'Please set a title.') . '</div></label>';
case 'select':
$html = '<label class="rgdns-form-field"><div class="rgdns-form-field-title">' . ($fi->title ?? 'Please set a title.') . ':</div><div class="rgdns-form-field-value"><select class="rgdns-input" name="' . $name . '"' . (empty($fi->disable) ? '' : ' disabled'). (empty($fi->props) ? '' : rgDnsConstructProps($fi->props)) . '>';
if(!empty($fi->options))
foreach($fi->options as $value => $name) {
if(empty($fi->keyIsValue))
$value = $name;
$html .= '<option';
if($value !== $name)
$html .= ' value="' . $value . '"';
if($fi->value === $value)
$html .= ' selected';
$html .= '>' . $name . '</option>';
}
return $html. '</select></div></label>';
default:
return '<label class="rgdns-form-field"><div class="rgdns-form-field-title">' . ($fi->title ?? 'Please set a title.') . ':</div><div class="rgdns-form-field-value">'
. '<input class="rgdns-input' . (empty($fi->monospace) ? '' : ' monospace') . '" type="' . $fi->type . '" name="' . $name . '" value="' . htmlspecialchars($fi->value ?? '') . '"' . (empty($fi->disable) ? '' : ' disabled') . (empty($fi->props) ? '' : rgDnsConstructProps($fi->props)) . ' />'
. '</div></label>';
break;
}
}
function rgDnsHttpInfo(string $title, array $lines = [], array $headers = []): void {
rgDnsHead($title, $headers);
echo '<div class="rgdns-info"><h2>' . $title . '</h2>';
foreach($lines as $line)
echo '<p>' . $line . '</p>';
echo '</div>';
rgDnsFoot();
}
function rgDnsHttpError(int $code): void {
http_response_code($code);
rgDnsHttpInfo('Error #' . $code, [
'<a href="' . htmlspecialchars($_SERVER['HTTP_REFERER'] ?? '?') . '">Click here to return to where you came from.</a>',
]);
exit;
}
function rgDnsGet(string $name): mixed {
return $_SESSION[RGDNS_PREFIX . $name] ?? null;
}
function rgDnsSet(string $name, $value): mixed {
return $_SESSION[RGDNS_PREFIX . $name] = $value;
}
function rgDnsIsset(string $name): bool {
return isset($_SESSION[RGDNS_PREFIX . $name]);
}
function rgDnsLoggedIn(): bool {
return rgDnsIsset('uid');
}
function rgDnsConfig(string $name, $value = null) {
if($value === null)
return $GLOBALS[RGDNS_PREFIX . 'config'][$name] ?? null;
$GLOBALS[RGDNS_PREFIX . 'config'][$name] = $value;
}
function rgDnsBase32Decode(string $str, string $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'): string {
$out = '';
$length = strlen($str);
$char = $shift = 0;
for($i = 0; $i < $length; $i++) {
$char <<= 5;
$char += stripos($chars, $str[$i]);
$shift = ($shift + 5) % 8;
$out .= $shift < 5 ? chr(($char & (0xFF << $shift)) >> $shift) : '';
}
return $out;
}
function rgDnsBase32Encode(string $data, string $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'): string {
$bin = '';
$encoded = '';
$length = strlen($data);
for($i = 0; $i < $length; $i++)
$bin .= sprintf('%08b', ord($data[$i]));
$bin = str_split($bin, 5);
$last = array_pop($bin);
$bin[] = str_pad($last, 5, '0', STR_PAD_RIGHT);
foreach($bin as $part)
$encoded .= $chars[bindec($part)];
return $encoded;
}
function rgDnsTOTPTime(?int $time = null, int $interval = 30): int {
$time ??= time();
return ($time * 1000) / ($interval * 1000);
}
function rgDnsTOTPGenerateSecret(): string {
return rgDnsBase32Encode(random_bytes(16));
}
function rgDnsTOTPGenerate(string $privateKey, ?int $time = null, int $digits = 6, int $interval = 30, string $algo = 'sha1'): string {
$hash = hash_hmac($algo, pack('J', rgDnsTOTPTime($time)), rgDnsBase32Decode($privateKey), true);
$offset = ord($hash[strlen($hash) - 1]) & 0x0F;
$bin = 0;
$bin |= (ord($hash[$offset]) & 0x7F) << 24;
$bin |= (ord($hash[$offset + 1]) & 0xFF) << 16;
$bin |= (ord($hash[$offset + 2]) & 0xFF) << 8;
$bin |= (ord($hash[$offset + 3]) & 0xFF);
$otp = $bin % pow(10, $digits);
return str_pad($otp, $digits, STR_PAD_LEFT);
}
function rgDnsTOTPValidTokens(string $privateKey): array {
return [
rgDnsTOTPGenerate($privateKey, time()),
rgDnsTOTPGenerate($privateKey, time() - 30),
rgDnsTOTPGenerate($privateKey, time() + 30)
];
}
function rgDnsCan(int $perm, ?stdClass $userInfo = null): bool {
$userInfo ??= $GLOBALS['userInfo'];
return $userInfo !== null && ($userInfo->user_perms & $perm) === $perm;
}
function rgDnsDb(): PDO {
$name = RGDNS_PREFIX . 'pdo';
if(empty($GLOBALS[$name]))
try {
$GLOBALS[$name] = new PDO(
rgDnsConfig('pdo_dsn'),
rgDnsConfig('pdo_user'),
rgDnsConfig('pdo_pass'), [
PDO::ATTR_CASE => PDO::CASE_NATURAL,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ,
PDO::ATTR_STRINGIFY_FETCHES => false,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET SESSION time_zone = \'+00:00\''
. ', sql_mode = \'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION\'',
]);
} catch(PDOException $ex) {
die($ex->getMessage());
}
return $GLOBALS[$name];
}
function rgDnsCanLogin(?string $ipAddress = null): bool {
$ipAddress ??= $_SERVER['REMOTE_ADDR'];
$prep = rgDnsDb()->prepare('SELECT COUNT(*) < 5 FROM `' . RGDNS_PREFIX . 'attempts` WHERE `attempt_ip` = INET6_ATON(:ip) AND `attempt_success` = 0');
$prep->bindValue('ip', $ipAddress);
return $prep->execute() && (bool)$prep->fetchColumn();
}
function rgDnsRecordLogIn(bool $success, ?string $ipAddress = null): void {
$ipAddress ??= $_SERVER['REMOTE_ADDR'];
$prep = rgDnsDb()->prepare('INSERT INTO `' . RGDNS_PREFIX . 'attempts` (`attempt_ip`, `attempt_success`) VALUES (INET6_ATON(:ip), :success)');
$prep->bindValue('ip', $ipAddress);
$prep->bindValue('success', $success ? 1 : 0);
$prep->execute();
}
function rgDnsCleanLogInAttempts(): void {
rgDnsDb()->exec('DELETE FROM `' . RGDNS_PREFIX . 'attempts` WHERE `attempt_created` < NOW() - INTERVAL 1 HOUR');
}
function rgDnsUniqueChars(string $input, bool $multibyte = true): int {
$chars = [];
$strlen = $multibyte ? 'mb_strlen' : 'strlen';
$substr = $multibyte ? 'mb_substr' : 'substr';
$length = $strlen($input);
for($i = 0; $i < $length; $i++) {
$current = $substr($input, $i, 1);
if(!in_array($current, $chars, true)) {
$chars[] = $current;
}
}
return count($chars);
}
function rgDnsCheckPasswordStrength(string $password): bool {
return rgDnsUniqueChars($password) >= 6;
}
function rgDnsFetchUser(string $field, $match): stdClass|bool {
$prep = rgDnsDb()->prepare('SELECT *, UNIX_TIMESTAMP(`user_created`) AS `user_created`, UNIX_TIMESTAMP(`user_disabled`) AS `user_disabled` FROM `' . RGDNS_PREFIX . 'users` WHERE `user_' . $field . '` = :match');
$prep->bindValue('match', $match);
$prep->execute();
return $prep->fetchObject();
}
function rgDnsFetchUsers(string $field, $after, int $limit = 10): array {
$prep = rgDnsDb()->prepare('SELECT *, UNIX_TIMESTAMP(`user_created`) AS `user_created`, UNIX_TIMESTAMP(`user_disabled`) AS `user_disabled` FROM `' . RGDNS_PREFIX . 'users` WHERE `user_' . $field . '` > :after LIMIT :limit');
$prep->bindValue('after', $after);
$prep->bindValue('limit', $limit);
$prep->execute();
$items = [];
while($item = $prep->fetchObject())
$items[] = $item;
return $items;
}
function rgDnsSetUserPerms(int $userId, int $perms): void {
$prep = rgDnsDb()->prepare('UPDATE `' . RGDNS_PREFIX . 'users` SET `user_perms` = :perms WHERE `user_id` = :user');
$prep->bindValue('user', $userId);
$prep->bindValue('perms', $perms);
$prep->execute();
}
function rgDnsSetUserTOTPSecret(int $userId, ?string $privateKey): void {
$prep = rgDnsDb()->prepare('UPDATE `' . RGDNS_PREFIX . 'users` SET `user_totp_private` = :priv WHERE `user_id` = :user');
$prep->bindValue('user', $userId);
$prep->bindValue('priv', $privateKey);
$prep->execute();
}
function rgDnsSetUserPassword(int $userId, string $password): void {
$prep = rgDnsDb()->prepare('UPDATE `' . RGDNS_PREFIX . 'users` SET `user_password` = :pwd WHERE `user_id` = :user');
$prep->bindValue('user', $userId);
$prep->bindValue('pwd', $password);
$prep->execute();
}
function rgDnsQueryLoginAttempts(int $start): array {
$prep = rgDnsDb()->prepare('SELECT *, INET6_NTOA(`attempt_ip`) AS `attempt_ip`, UNIX_TIMESTAMP(`attempt_created`) AS `attempt_created` FROM `' . RGDNS_PREFIX . 'attempts` WHERE `attempt_created` > FROM_UNIXTIME(:start) ORDER BY `attempt_created` ASC');
$prep->bindValue('start', $start);
$prep->execute();
$items = [];
while($item = $prep->fetchObject())
$items[] = $item;
return $items;
}
function rgDnsFetchNameserver(string $field, $match): stdClass|bool {
$prep = rgDnsDb()->prepare('SELECT *, UNIX_TIMESTAMP(`ns_created`) AS `user_created`, UNIX_TIMESTAMP(`ns_disabled`) AS `user_disabled`, u.`user_name` AS `ns_owner_name` FROM `' . RGDNS_PREFIX . 'nameservers` AS ns LEFT JOIN `' . RGDNS_PREFIX . 'users` AS u ON ns.`ns_owner` = u.`user_id` WHERE `ns_' . $field . '` = :match');
$prep->bindValue('match', $match);
$prep->execute();
return $prep->fetchObject();
}
function rgDnsFetchNameservers(bool $withDisabled = true): array {
$prep = rgDnsDb()->prepare('SELECT ns.*, UNIX_TIMESTAMP(ns.`ns_created`) AS `ns_created`, UNIX_TIMESTAMP(ns.`ns_disabled`) AS `ns_disabled`, u.`user_name` AS `ns_owner_name` FROM `' . RGDNS_PREFIX . 'nameservers` AS ns LEFT JOIN `' . RGDNS_PREFIX . 'users` AS u ON ns.`ns_owner` = u.`user_id` ' . ($withDisabled ? '' : ' WHERE `ns_disabled` IS NULL'));
$prep->execute();
$items = [];
while($item = $prep->fetchObject())
$items[] = $item;
return $items;
}
function rgDnsDeleteNameserver(int $nsId): void {
$prep = rgDnsDb()->prepare('DELETE FROM `' . RGDNS_PREFIX . 'nameservers` WHERE `ns_id` = :id');
$prep->bindValue('id', $nsId);
$prep->execute();
}
function rgDnsEditNameserver(int $nsId, string $name, bool $master, bool $disable): bool {
$prep = rgDnsDb()->prepare('UPDATE `' . RGDNS_PREFIX . 'nameservers` SET `ns_name` = :name, `ns_type` = :type, `ns_disabled` = IF(:disable, COALESCE(`ns_disabled`, NOW()), NULL) WHERE `ns_id` = :id');
$prep->bindValue('id', $nsId);
$prep->bindValue('name', $name);
$prep->bindValue('type', $master ? 'MASTER' : 'SLAVE');
$prep->bindValue('disable', $disable ? 1 : 0);
try {
$prep->execute();
} catch(PDOException $ex) {
if($ex->getCode() == '23000')
return false;
throw $ex;
}
return true;
}
function rgDnsAddNameserver(string $name, bool $master, bool $disable, int $owner): int {
$prep = rgDnsDb()->prepare('INSERT INTO `' . RGDNS_PREFIX . 'nameservers` (`ns_name`, `ns_owner`, `ns_type`, `ns_disabled`) VALUES (:name, :owner, :type, IF(:disable, NOW(), NULL))');
$prep->bindValue('name', $name);
$prep->bindValue('owner', $owner);
$prep->bindValue('type', $master ? 'MASTER' : 'SLAVE');
$prep->bindValue('disable', $disable ? 1 : 0);
try {
$prep->execute();
} catch(PDOException $ex) {
if($ex->getCode() == '23000')
return -1;
throw $ex;
}
return (int)rgDnsDb()->lastInsertId();
}
function rgDnsFetchNameserverAddresses(int $nsId): array {
$prep = rgDnsDb()->prepare('SELECT *, INET6_NTOA(`server_ip`) AS `server_ip` FROM `' . RGDNS_PREFIX . 'nameservers_addresses` WHERE `server_ns` = :ns');
$prep->bindValue('ns', $nsId);
$prep->execute();
$items = [];
while($item = $prep->fetchObject())
$items[] = $item;
return $items;
}
function rgDnsAddNameserverAddress(int $nsId, string $address): bool {
$prep = rgDnsDb()->prepare('INSERT INTO `' . RGDNS_PREFIX . 'nameservers_addresses` (`server_ns`, `server_ip`) VALUES (:ns, INET6_ATON(:addr))');
$prep->bindValue('ns', $nsId);
$prep->bindValue('addr', $address);
try {
$prep->execute();
} catch(PDOException $ex) {
if($ex->getCode() == '23000')
return false;
throw $ex;
}
return true;
}
function rgDnsRemoveNameserverAddress(int $nsId, string $address): void {
$prep = rgDnsDb()->prepare('DELETE FROM `' . RGDNS_PREFIX . 'nameservers_addresses` WHERE `server_ns` = :ns AND `server_ip` = INET6_ATON(:addr)');
$prep->bindValue('ns', $nsId);
$prep->bindValue('addr', $address);
$prep->execute();
}
function rgDnsFetchDomain(string $field, $match): stdClass|false {
$prep = rgDnsDb()->prepare('SELECT d.*, UNIX_TIMESTAMP(d.`domain_created`) AS `domain_created`, UNIX_TIMESTAMP(d.`domain_disabled`) AS `domain_disabled`, u.`user_name` AS `domain_owner_name` FROM `' . RGDNS_PREFIX . 'domains` AS d LEFT JOIN `' . RGDNS_PREFIX . 'users` AS u ON d.`domain_owner` = u.`user_id` WHERE d.`domain_' . $field . '` = :match');
$prep->bindValue('match', $match);
$prep->execute();
return $prep->fetchObject();
}
function rgDnsFetchDomains(?int $owner = null, ?int $after = null, int $limit = 10): array {
$hasOwner = $owner !== null;
$hasAfter = $after !== null;
$query = 'SELECT d.*, UNIX_TIMESTAMP(d.`domain_created`) AS `domain_created`, UNIX_TIMESTAMP(d.`domain_disabled`) AS `domain_disabled`, u.`user_name` AS `domain_owner_name` FROM `' . RGDNS_PREFIX . 'domains` AS d LEFT JOIN `' . RGDNS_PREFIX . 'users` AS u ON d.`domain_owner` = u.`user_id`';
if($hasOwner || $hasAfter) {
$query .= ' WHERE';
if($hasOwner) {
$query .= ' d.`domain_owner` = :owner';
if($hasAfter)
$query .= ' AND';
}
if($hasAfter)
$query .= ' d.`domain_id` > :after LIMIT :limit';
}
$prep = rgDnsDb()->prepare($query);
if($hasOwner)
$prep->bindValue('owner', $owner);
if($hasAfter) {
$prep->bindValue('after', $after);
$prep->bindValue('limit', $limit);
}
$prep->execute();
$items = [];
while($item = $prep->fetchObject())
$items[] = $item;
return $items;
}
function rgDnsDeleteDomain(int $domainId): void {
$prep = rgDnsDb()->prepare('DELETE FROM `' . RGDNS_PREFIX . 'domains` WHERE `domain_id` = :domain');
$prep->bindValue('domain', $domainId);
$prep->execute();
}
function rgDnsAddDomain(int $userId, string $domainName, int $domainTTL, ?int $domainRefresh, ?int $domainRetry, ?int $domainExpire, ?int $domainCacheTTL, ?bool $domainDisable): int {
$prep = rgDnsDb()->prepare('INSERT INTO `' . RGDNS_PREFIX . 'domains` (`domain_name`, `domain_owner`, `domain_refresh`, `domain_retry`, `domain_expire`, `domain_ttl`, `domain_record_ttl`, `domain_disabled`) VALUES (:name, :owner, COALESCE(:refresh, DEFAULT(`domain_refresh`)), COALESCE(:retry, DEFAULT(`domain_retry`)), COALESCE(:expire, DEFAULT(`domain_expire`)), COALESCE(:cttl, DEFAULT(`domain_ttl`)), :rttl, IF(:disable, NOW(), NULL))');
$prep->bindValue('name', $domainName);
$prep->bindValue('owner', $userId);
$prep->bindValue('refresh', $domainRefresh);
$prep->bindValue('retry', $domainRetry);
$prep->bindValue('expire', $domainExpire);
$prep->bindValue('cttl', $domainCacheTTL);
$prep->bindValue('rttl', $domainTTL);
$prep->bindValue('disable', $domainDisable ? 1 : 0);
try {
$prep->execute();
} catch(PDOException $ex) {
var_dump($ex->getMessage());
if($ex->getCode() == '23000')
return -1;
throw $ex;
}
return (int)rgDnsDb()->lastInsertId();
}
function rgDnsEditDomain(int $domainId, int $domainTTL, ?int $domainRefresh, ?int $domainRetry, ?int $domainExpire, ?int $domainCacheTTL, ?bool $domainDisable): bool {
$prep = rgDnsDb()->prepare('UPDATE `' . RGDNS_PREFIX . 'domains` SET `domain_record_ttl` = :rttl, `domain_refresh` = COALESCE(:refresh, `domain_refresh`), `domain_retry` = COALESCE(:retry, `domain_retry`), `domain_expire` = COALESCE(:expire, `domain_expire`), `domain_ttl` = COALESCE(:cttl, `domain_ttl`), `domain_disabled` = IF(:hasDisable, IF(:disable, COALESCE(`domain_disabled`, NOW()), NULL), `domain_disabled`) WHERE `domain_id` = :domain');
$prep->bindValue('domain', $domainId);
$prep->bindValue('refresh', $domainRefresh);
$prep->bindValue('retry', $domainRetry);
$prep->bindValue('expire', $domainExpire);
$prep->bindValue('cttl', $domainCacheTTL);
$prep->bindValue('rttl', $domainTTL);
$prep->bindValue('hasDisable', ($domainDisable === null) ? 0 : 1);
$prep->bindValue('disable', $domainDisable ? 1 : 0);
try {
$prep->execute();
} catch(PDOException $ex) {
if($ex->getCode() == '23000')
return false;
throw $ex;
}
return true;
}
function rgDnsFetchRecord(string $field, $match): stdClass|bool {
$prep = rgDnsDb()->prepare('SELECT *, UNIX_TIMESTAMP(`record_created`) AS `record_created`, UNIX_TIMESTAMP(`record_disabled`) AS `record_disabled` FROM `' . RGDNS_PREFIX . 'records` WHERE `record_' . $field . '` = :match');
$prep->bindValue('match', $match);
$prep->execute();
return $prep->fetchObject();
}
function rgDnsFetchRecords(int $domainId): array {
$prep = rgDnsDb()->prepare('SELECT *, UNIX_TIMESTAMP(`record_created`) AS `record_created`, UNIX_TIMESTAMP(`record_disabled`) AS `record_disabled` FROM `' . RGDNS_PREFIX . 'records` WHERE `record_domain` = :domain ORDER BY `record_name`, `record_type`, `record_priority`');
$prep->bindValue('domain', $domainId);
$prep->execute();
$items = [];
while($item = $prep->fetchObject())
$items[] = $item;
return $items;
}
function rgDnsDeleteRecord(int $recordId): void {
$prep = rgDnsDb()->prepare('DELETE FROM `' . RGDNS_PREFIX . 'records` WHERE `record_id` = :record');
$prep->bindValue('record', $recordId);
$prep->execute();
}
function rgDnsFormatRecordValue(string $type, string $value): string {
switch($type) {
case 'TXT':
if(strlen($value) > 100)
$value = substr($value, 0, 100) . '...';
$value = '"' . $value . '"';
}
return htmlspecialchars($value);
}
$GLOBALS[RGDNS_PREFIX . 'config'] = [];
require_once __DIR__ . '/config.php';
rgDnsCleanLogInAttempts();
session_name(RGDNS_PREFIX . 'session');
ini_set('session.gc_maxlifetime', 86400);
session_set_cookie_params(86400);
session_start();
if(!rgDnsLoggedIn()) {
$canLogin = rgDnsCanLogin();
if(!$canLogin) {
$error = 'You may no longer log in.';
} elseif(!empty($_POST)) {
$sessionId = filter_input(INPUT_POST, 'sessid');
if($sessionId !== session_id()) {
rgDnsRecordLogIn(false);
$error = 'Username, password or token was invalid.';
} else {
$userName = filter_input(INPUT_POST, 'username');
$password = filter_input(INPUT_POST, 'password');
if(empty($userName) || empty($password)) {
rgDnsRecordLogIn(false);
$error = 'Username, password or token was invalid.';
} else {
$userInfo = rgDnsFetchUser('name', $userName);
if(!$userInfo || !empty($userInfo->user_disabled) || !password_verify($password, $userInfo->user_password)) {
rgDnsRecordLogIn(false);
$error = 'Username, password or token was invalid.';
} else {
$mfaDisarmed = empty($userInfo->user_totp_private);
if(!$mfaDisarmed) {
$mfaToken = filter_input(INPUT_POST, 'mfa');
$mfaValid = rgDnsTOTPValidTokens($userInfo->user_totp_private);
$mfaDisarmed = in_array($mfaToken, $mfaValid, true);
}
if(!$mfaDisarmed) {
rgDnsRecordLogIn(false);
$error = 'Username, password or token was invalid.';
} else {
rgDnsRecordLogIn(true);
rgDnsSet('uid', $userInfo->user_id);
rgDnsHead('Logged in!', [
['tag' => 'meta', 'props' => ['http-equiv' => 'refresh', 'content' => '2; url=?']],
]);
echo 'Logged in successfully! You will now be redirected to the <a href="?">main page</a>.';
rgDnsFoot();
exit;
}
}
}
}
}
rgDnsHead('Login');
echo '<div class="rgdns-login"><h2>Log in</h2>';
if(isset($error))
echo '<div class="rgdns-login-error">' . $error . '</div>';
rgDnsForm('?', [
'sessid' => [
'type' => 'hidden',
'value' => $canLogin ? session_id() : '',
],
'username' => [
'title' => 'Username',
'disable' => !$canLogin,
],
'password' => [
'title' => 'Password',
'type' => 'password',
'disable' => !$canLogin,
],
'mfa' => [
'title' => 'Authenticator Token',
'type' => 'text',
'disable' => !$canLogin,
'monospace' => true,
'props' => [
'maxlength' => 6,
'placeholder' => '------',
'style' => 'text-align:center',
'autocomplete' => 'off',
],
],
], [[
'text' => 'Log in',
'disable' => !$canLogin,
]]);
echo '</div>';
rgDnsFoot();
exit;
}
$userInfo = rgDnsFetchUser('id', rgDnsGet('uid'));
if(empty($userInfo) || !empty($userInfo->user_disabled)) {
session_destroy();
rgDnsHttpInfo('Account Disabled', [
'Your account has been disabled. You will now be redirected to the <a href="?">login page</a>.',
], [
['tag' => 'meta', 'props' => ['http-equiv' => 'refresh', 'content' => '5; url=?']],
]);
exit;
}
switch(filter_input(INPUT_GET, 'mode')) {
case '':
rgDnsHead('Main Menu');
$showLine = false;
echo '<div class="rgdns-home"><h2>Main Menu</h2><ul class="rgdns-ul">';
if(rgDnsCan(RGDNSP_MANAGE_DOMAINS)) {
echo '<li class="rgdns-li"><a href="?mode=domains">Manage Domains</a></li>';
$showLine = true;
}
if(rgDnsCan(RGDNSP_MANAGE_NAMESERVERS)) {
echo '<li class="rgdns-li"><a href="?mode=nameservers">Manage Nameservers</a></li>';
$showLine = true;
}
if(rgDnsCan(RGDNSP_MANAGE_USERS)) {
echo '<li class="rgdns-li"><a href="?mode=users">Manage Users</a></li>';
$showLine = true;
}
if(rgDnsCan(RGDNSP_VIEW_LOGIN_ATTEMPTS)) {
echo '<li class="rgdns-li"><a href="?mode=attempts">Watch Log In Attempts</a></li>';
$showLine = true;
}
if($showLine)
echo '<li class="rgdns-li rgdns-li-hr"></li>';
if(rgDnsCan(RGDNSP_EDIT_SETTINGS))
echo '<li class="rgdns-li"><a href="?mode=settings">Edit Settings</a></li>';
echo '<li class="rgdns-li"><a href="?mode=logout&amp;sess=' . session_id() . '&amp;time=' . time() . '">Log out</a></li>';
echo '</ul></div>';
rgDnsFoot();
break;
case 'records':
if(!rgDnsCan(RGDNSP_MANAGE_RECORDS))
rgDnsHttpError(403);
$returnUrl = (string)filter_input(INPUT_GET, 'ret');
if(substr($returnUrl, 0, 6) !== '?mode=')
$returnUrl = '?mode=domains';
$returnUrl = htmlspecialchars($returnUrl);
$manageAllDomains = rgDnsCan(RGDNSP_MANAGE_ALL_DOMAINS); // read-only
$manageAllRecords = rgDnsCan(RGDNSP_MANAGE_ALL_RECORDS); // read-write
$manageAddRecords = rgDnsCan(RGDNSP_ADD_RECORDS); // can write at all
$domainId = (int)filter_input(INPUT_GET, 'di');
$domainInfo = rgDnsFetchDomain('id', $domainId);
if(empty($domainInfo)) {
rgDnsHttpError(404);
break;
}
if(!$manageAllDomains && $domainInfo->domain_owner !== $userInfo->user_id) {
rgDnsHttpError(403);
break;
}
if(isset($_GET['smode'])) {
$recordId = (int)filter_input(INPUT_GET, 'ri', FILTER_SANITIZE_NUMBER_INT);
$hasRecord = $recordId > 0;
if($hasRecord) {
$recordInfo = rgDnsFetchRecord('id', $recordId);
if($recordInfo->record_domain !== $domainInfo->domain_id) {
rgDnsHttpError(403);
break;
}
} else $recordInfo = null;
$returnNoAmp = '?mode=records&di=' . $domainInfo->domain_id . '&ret=' . rawurlencode($returnUrl);
$return = htmlspecialchars($returnNoAmp);
switch(filter_input(INPUT_GET, 'smode')) {
case 'add':
case 'edit':
if(!$hasRecord && !$manageAddRecords) {
rgDnsHttpError(403);
break;
}
if(!empty($_POST)) {
if(filter_input(INPUT_POST, 'sessid') !== session_id()) {
$error = 'Invalid request.';
} else {
$recordTTL = ($recordTTL = (int)(filter_input(INPUT_POST, 'rttl'))) < 1 ? null : $recordTTL;
$recordPrio = ($recordPrio = (int)(filter_input(INPUT_POST, 'rprio'))) < 1 ? 10 : $recordPrio;
$recordValue = (string)filter_input(INPUT_POST, 'rvalue');
$recordDisable = !empty($_POST['rdisable']);
if($domainIsset) {
if($recordInfo->record_type !== 'MX')
$recordPrio = null;
if(rgDnsEditDomain($recordInfo->record_id, $recordTTL, $recordPrio, $recordValue, $recordDisable)) {
$recordInfo->record_ttl = $recordTTL;
$recordInfo->record_priority = $recordPrio;
$recordInfo->record_value = $recordValue;
$recordInfo->record_disabled = $recordDisable ? ($recordInfo->record_disabled ?? time()) : null;
} else
$error = 'This domain has already been added to the system.';
} else {
$recordName = trim((string)filter_input(INPUT_POST, 'rname'), " \n\r\t\v\0.");
$recordType = (string)filter_input(INPUT_POST, 'rtype');
$recordId = rgDnsAddRecord($domainInfo->domain_id, $recordName, $recordType, $recordTTL, $recordPrio, $recordValue, $recordDisable);
if($recordId < 1)
$error = 'Something went wrong while adding that record.';
else {
$recordInfo = rgDnsFetchRecord('id', $recordId);
$hasRecord = true;
}
}
}
}
$title = $hasRecord ? ('Editing ' . $recordInfo->record_type . ' record ' . $recordInfo->record_name . ' for ' . $domainInfo->domain_name) : ('Adding a new DNS record for ' . $domainInfo->domain_name);
$formTarget = $return . '&amp;smode=' . ($hasRecord ? ('edit&amp;ri=' . $recordInfo->record_id) : 'add');
rgDnsHead($title);
echo '<div class="rgdns-records"><h2>' . $title . '</h2>';
if(isset($error))
echo '<p class="rgdns-error-paragraph">Error: ' . $error . '</p>';
echo '<a href="' . $return . '"><button class="rgdns-button">Discard and Return</button></a>';
echo '<div class="rgdns-records-form">';
$recordFields = [
'sessid' => ['type' => 'hidden', 'value' => session_id()],
'rname' => [
'type' => 'text',
'title' => 'Name',
'value' => ($recordInfo?->record_name ?? ''),
'disable' => $hasRecord,
],
'rtype' => [
'type' => 'select',
'title' => 'Type',
'value' => ($recordInfo?->record_type ?? ''),
'options' => RGDNS_RECORD_TYPES,
'disable' => $hasRecord,
],
'rttl' => [
'type' => 'number',
'title' => 'TTL (in Seconds)',
'value' => ($recordInfo?->record_ttl ?? ''),
'props' => ['placeholder' => $domainInfo->domain_record_ttl],
],
'rprio' => [
'type' => 'number',
'title' => 'Priority (for MX)',
'value' => ($recordInfo?->record_priority ?? ''),
'hidden' => $hasRecord && $recordInfo->record_type !== 'MX',
'props' => ['placeholder' => '10'],
],
'rvalue' => [
'type' => 'text',
'title' => 'Value',
'value' => ($recordInfo?->record_value ?? ''),
],
'rdisable' => [
'type' => 'checkbox',
'title' => 'Disabled',
'value' => !empty($recordInfo->record_disabled),
],
];
rgDnsForm($formTarget, $recordFields);
echo '</div>';
echo '</div>';
rgDnsFoot();
break;
case 'delete':
if(filter_input(INPUT_GET, 'confirm') === session_id()) {
rgDnsDeleteRecord($recordInfo->record_id);
rgDnsHttpInfo('Successfully deleted ' . $recordInfo->record_type . ' record ' . $recordInfo->record_name . ' from ' . $domainInfo->domain_name . '!', [
'The record has been deleted successfully. You will now be redirected to the <a href="' . $return . '">records page</a>.',
], [
['tag' => 'meta', 'props' => ['http-equiv' => 'refresh', 'content' => '2; url=' . $returnNoAmp]],
]);
break;
}
$title = 'Deleting ' . $recordInfo->record_type . ' record ' . $recordInfo->record_name . ' from ' . $domainInfo->domain_name;
rgDnsHead($title);
echo '<div class="rgdns-domains"><h2>' . $title . '</h2>'
. '<p><b>Are you sure you want to irreversably delete this record?</b></p>'
. '<a href="' . $return . '"><button class="rgdns-button">No, return me to the DNS records listing</button></a> <a href="?mode=records&amp;smode=delete&amp;di=' . $domainInfo->domain_id . '&ri=' . $recordInfo->record_id . '&amp;confirm=' . session_id() . '&amp;ret=' . rawurlencode($returnUrl) . '"><button class="rgdns-button">Yes, I\'m sure</button></a></div>';
rgDnsFoot();
break;
default:
rgDnsHttpError(404);
break;
}
} else {
$records = rgDnsFetchRecords($domainInfo->domain_id);
$title = 'DNS Records for ' . $domainInfo->domain_name;
rgDnsHead($title);
echo '<div class="rgdns-records"><h2>' . $title . '</h2>';
if(!empty($returnUrl))
echo '<a href="' . $returnUrl . '"><button class="rgdns-button">Return</button></a>';
if($manageAddRecords)
echo '<a href="?mode=records&amp;smode=add&amp;di=' . $domainInfo->domain_id . '&amp;ret=' . $returnUrl . '"><button class="rgdns-button">Add</button></a>';
echo '<table class="rgdns-table rgdns-records-table"><thead><tr>';
echo '<th>Type</th>';
echo '<th>Name</th>';
echo '<th>TTL (Seconds)</th>';
echo '<th>Priority</th>';
echo '<th>Value</th>';
echo '<th></th>';
echo '</tr></thead><tbody>';
foreach($records as $record) {
echo '<tr>';
printf(
'<td class="rgdns-records-type rgdns-records-type-%s">%s</td><td class="rgdns-records-name">%s</td><td class="rgdns-records-ttl %s">%d</td><td class="rgdns-records-priority">%s</td><td>%s</td>',
strtolower($record->record_type),
$record->record_type,
$record->record_name,
empty($record->record_ttl) ? 'rgdns-records-default' : '',
$record->record_ttl ?? $domainInfo->domain_record_ttl,
$record->record_priority ?? '',
rgDnsFormatRecordValue($record->record_type, $record->record_value),
);
echo '<td>';
echo '<a href="?mode=records&amp;smode=edit&amp;di=' . $domainInfo->domain_id . '&amp;ri=' . $record->record_id . '&amp;ret=' . $returnUrl . '">Edit</a>';
echo ' <a href="?mode=records&amp;smode=delete&amp;di=' . $domainInfo->domain_id . '&amp;ri=' . $record->record_id . '&amp;ret=' . $returnUrl . '">Delete</a>';
echo '</td></tr>';
}
echo '</tbody>';
echo '</table>';
echo '</div>';
rgDnsFoot();
}
break;
case 'domains':
if(!rgDnsCan(RGDNSP_MANAGE_DOMAINS))
rgDnsHttpError(403);
$manageAll = rgDnsCan(RGDNSP_MANAGE_ALL_DOMAINS);
$domainsUserId = $manageAll
? filter_input(INPUT_GET, 'uid', FILTER_SANITIZE_NUMBER_INT)
: $userInfo->user_id;
if(isset($_GET['smode'])) {
$domainsUserId ??= $userInfo->user_id;
$domainIsset = isset($_GET['di']);
$domain = $domainIsset
? rgDnsFetchDomain('id', (int)filter_input(INPUT_GET, 'di', FILTER_SANITIZE_NUMBER_INT))
: null;
$returnPrefix = '?mode=domains';
$return = (string)filter_input(INPUT_GET, 'ret');
if(substr($return, 0, strlen($returnPrefix)) !== $returnPrefix)
$return = $returnPrefix;
$returnNoAmp = rawurlencode($return);
$return = htmlspecialchars($return);
if($domainIsset) {
if(empty($domain)) {
rgDnsHttpError(404);
break;
}
if(!$manageAll && ($domain->domain_owner !== $userInfo->user_id || !empty($domain->domain_disabled))) {
rgDnsHttpError(403);
break;
}
}
$domainsUserInfo = $domainsUserId === $userInfo->user_id ? $userInfo : rgDnsFetchUser('id', $domainsUserId);
if(empty($domainsUserInfo)) {
rgDnsHttpError(400);
break;
}
switch(filter_input(INPUT_GET, 'smode')) {
case 'add':
case 'edit':
if(!rgDnsCan(RGDNSP_ADD_DOMAINS)) {
rgDnsHttpError(403);
break;
}
if(!empty($_POST)) {
if(filter_input(INPUT_POST, 'sessid') !== session_id()) {
$error = 'Invalid request.';
} else {
$domainTTL = ($domainTTL = (int)(filter_input(INPUT_POST, 'drttl'))) < 1 ? 3600 : $domainTTL;
$domainRefresh = $manageAll ? (int)(filter_input(INPUT_POST, 'drefresh') ?? 3600) : null;
$domainRetry = $manageAll ? (int)(filter_input(INPUT_POST, 'dretry') ?? 1800) : null;
$domainExpire = $manageAll ? (int)(filter_input(INPUT_POST, 'dexpire') ?? 1209600) : null;
$domainCacheTTL = $manageAll ? (int)(filter_input(INPUT_POST, 'dcttl')) : null;
$domainDisable = $manageAll ? !empty($_POST['ddisable']) : null;
if($domainIsset) {
if(rgDnsEditDomain($domain->domain_id, $domainTTL, $domainRefresh, $domainRetry, $domainExpire, $domainCacheTTL, $domainDisable)) {
$domain->domain_record_ttl = $domainTTL;
$domain->domain_refresh = $domainRefresh ?? $domain->domain_refresh;
$domain->domain_retry = $domainRetry ?? $domain->domain_retry;
$domain->domain_expire = $domainExpire ?? $domain->domain_expire;
$domain->domain_ttl = $domainCacheTTL ?? $domain->domain_ttl;
$domain->domain_disabled = $domainDisable ? ($domain->domain_disabled ?? time()) : null;
} else
$error = 'This domain has already been added to the system.';
} else {
$domainName = trim((string)filter_input(INPUT_POST, 'dname'), " \n\r\t\v\0.");
$domainId = rgDnsAddDomain($domainsUserInfo->user_id, $domainName, $domainTTL, $domainRefresh, $domainRetry, $domainExpire, $domainCacheTTL, $domainDisable);
if($domainId < 1)
$error = 'This domain has already been added to the system.';
else {
$domain = rgDnsFetchDomain('id', $domainId);
$domainIsset = true;
}
}
}
}
$title = $domainIsset ? ('Editing domain #' . $domain->domain_id . ' ' . $domain->domain_name) : 'Adding a new Domain';
$formTarget = $return . '&amp;smode=' . ($domainIsset ? ('edit&amp;di=' . $domain->domain_id) : 'add') . '&amp;ret=' . rawurlencode($return);
rgDnsHead($title);
echo '<div class="rgdns-domains"><h2>' . $title . '</h2>';
if(isset($error))
echo '<p class="rgdns-error-paragraph">Error: ' . $error . '</p>';
echo '<a href="' . $return . '"><button class="rgdns-button">Discard and Return</button></a>';
echo '<div class="rgdns-domains-form">';
$domainFields = [
'sessid' => ['type' => 'hidden', 'value' => session_id()],
'dname' => [
'type' => 'text',
'title' => 'Name',
'value' => ($domain?->domain_name ?? ''),
'disable' => $domainIsset,
],
'drttl' => [
'type' => 'number',
'title' => 'Default TTL (Seconds)',
'value' => ($domain?->domain_record_ttl ?? ''),
'props' => ['placeholder' => '3600'],
],
];
if($manageAll) {
$domainFields['drefresh'] = [
'type' => 'number',
'title' => 'Refresh (Seconds)',
'value' => ($domain?->domain_refresh ?? ''),
'props' => ['placeholder' => '3600'],
];
$domainFields['dretry'] = [
'type' => 'number',
'title' => 'Retry (Seconds)',
'value' => ($domain?->domain_retry ?? ''),
'props' => ['placeholder' => '1800'],
];
$domainFields['dexpire'] = [
'type' => 'number',
'title' => 'Expires (Seconds)',
'value' => ($domain?->domain_expire ?? ''),
'props' => ['placeholder' => '1209600'],
];
$domainFields['dcttl'] = [
'type' => 'number',
'title' => 'Cache TTL (Seconds)',
'value' => ($domain?->domain_ttl ?? ''),
'props' => ['placeholder' => '86400'],
];
$domainFields['ddisable'] = [
'type' => 'checkbox',
'title' => 'Disabled',
'value' => !empty($domain->domain_disabled),
];
}
rgDnsForm($formTarget, $domainFields);
echo '</div>';
echo '</div>';
rgDnsFoot();
break;
case 'delete':
if(empty($domain)) {
rgDnsHttpError(404);
break;
}
if(isset($_GET['confirm']) && $_GET['confirm'] === session_id()) {
rgDnsDeleteDomain($domain->domain_id);
rgDnsHttpInfo('Successfully deleted ' . $domain->domain_name . '!', [
'Domain ' . $domain->domain_name . ' with ID ' . $domain->domain_id . ' has been deleted successfully. You will now be redirected to the <a href="' . $return . '">domains page</a>.',
], [
['tag' => 'meta', 'props' => ['http-equiv' => 'refresh', 'content' => '2; url=' . $returnNoAmp]],
]);
break;
}
$title = 'Deleting domain #' . $domain->domain_id . ' ' . $domain->domain_name;
rgDnsHead($title);
echo '<div class="rgdns-domains"><h2>' . $title . '</h2>'
. '<p><b>Are you sure you want to irreversably delete ' . $domain->domain_name . '?</b></p>'
. '<a href="' . $return . '"><button class="rgdns-button">No, return me to the domains listing</button></a> <a href="?mode=domains&amp;smode=delete&amp;di=' . $domain->domain_id . '&amp;confirm=' . session_id() . '&amp;ret=' . rawurlencode($return) . '"><button class="rgdns-button">Yes, I\'m sure</button></a></div>';
rgDnsFoot();
break;
default:
rgDnsHttpError(404);
break;
}
} else {
$domainsShowAll = $domainsUserId === null;
$domainsAfter = $domainsShowAll ? (int)filter_input(INPUT_GET, 'after', FILTER_SANITIZE_NUMBER_INT) : null;
$domains = rgDnsFetchDomains($domainsUserId, $domainsAfter);
$lastId = 0;
$baseUrl = '?mode=domains';
if(!$domainsShowAll && $manageAll)
$baseUrl .= '&uid=' . $domainsUserId;
$returnUrl = rawurlencode($baseUrl . ($domainsAfter > 0 ? ('&after=' . $domainsAfter) : ''));
$baseUrl = htmlspecialchars($baseUrl);
rgDnsHead('Domains');
echo '<div class="rgdns-domains"><h2>Domains</h2>';
if(rgDnsCan(RGDNSP_ADD_DOMAINS))
echo '<a href="' . $baseUrl . '&amp;smode=add"><button class="rgdns-button">Add new domain</div></a>';
if(empty($domains)) {
if($domainsAfter > 0) {
echo '<p>You\'ve reached the end of the list!</p>'
. '<a href="?mode=domains"><button class="rgdns-button">Go back to the start</button></a>';
} else {
echo '<p>There are no domains.</p>';
}
} else {
echo '<table class="rgdns-table rgdns-domains-table"><thead><tr>';
if($domainsShowAll) echo '<th>&numero;</th>';
echo '<th>Name</th>';
echo '<th>Status</th>';
if($domainsShowAll) echo '<th>Owner</th>';
if($manageAll) {
echo '<th>Refresh</th>'
. '<th>Retry</th>'
. '<th>Expire</th>'
. '<th>Cache TTL</th>';
}
echo '<th>Default TTL</th>';
echo '<th>Created</th>';
if($manageAll) echo '<th>Disabled</th>';
echo '<th></th></tr></thead><tbody>';
foreach($domains as $domain) {
$lastId = $domain->domain_id;
echo '<tr>';
if($domainsShowAll) printf('<td class="rgdns-table-important rgdns-table-right">%d</td>', $domain->domain_id);
printf(
'<td class="rgdns-table-important rgdns-domains-table-name rgdns-table-centre">%s</td><td class="rgdns-domains-table-status rgdns-domains-table-%s">%s</td>',
$domain->domain_name,
empty($domain->domain_disabled) ? 'active' : 'inactive',
empty($domain->domain_disabled) ? 'ACTIVE' : 'INACTIVE',
);
if($domainsShowAll) printf('<td class="rgdns-table-centre rgdns-domains-table-owner"><b>&numero;%d</b> %s</td>', $domain->domain_owner, $domain->domain_owner_name);
if($manageAll)
printf(
'<td class="rgdns-table-right">%d</td><td class="rgdns-table-right">%d</td><td class="rgdns-table-right">%d</td><td class="rgdns-table-right">%d</td>',
$domain->domain_refresh, $domain->domain_retry,
$domain->domain_expire, $domain->domain_ttl,
);
printf(
'<td>%d Seconds</td><td class="rgdns-domains-table-created">%s</td>',
$domain->domain_record_ttl,
date('c', $domain->domain_created)
);
if($manageAll) {
echo '<td class="rgdns-domains-table-disabled">';
if(empty($domain->domain_disabled))
echo 'No';
else {
printf('Yes (on %s)', date('c', $domain->domain_disabled));
}
echo '</td>';
}
echo '<td>';
echo '<a href="?mode=records&amp;di=' . $domain->domain_id . '&amp;ret=' . $returnUrl . '">Records</a>';
if(empty($domain->domain_disabled) || $manageAll) {
echo ' <a href="' . $baseUrl . '&amp;smode=edit&amp;di=' . $domain->domain_id . '&amp;ret=' . $returnUrl . '">Edit</a>';
echo ' <a href="' . $baseUrl . '&amp;smode=delete&amp;di=' . $domain->domain_id . '&amp;ret=' . $returnUrl . '">Delete</a>';
}
echo '</td></tr>';
}
echo '</tbody></table>';
if($domainsAfter !== null && count($domains) >= 10)
echo '<a href="' . $baseUrl . '&amp;after=' . $lastId . '"><button class="rgdns-button">Next</div></a>';
}
echo '</div>';
rgDnsFoot();
}
break;
case 'nameservers':
if(!rgDnsCan(RGDNSP_MANAGE_NAMESERVERS))
rgDnsHttpError(403);
if(isset($_GET['smode'])) {
$nsIsset = isset($_GET['nsi']);
$ns = $nsIsset
? rgDnsFetchNameserver('id', (int)filter_input(INPUT_GET, 'nsi', FILTER_SANITIZE_NUMBER_INT))
: null;
switch(filter_input(INPUT_GET, 'smode')) {
case 'add':
case 'edit':
if($nsIsset && empty($ns))
rgDnsHttpError(404);
if(!empty($_POST)) {
if(filter_input(INPUT_POST, 'sessid') !== session_id()) {
$error = 'Invalid request.';
} else {
$nsName = trim((string)filter_input(INPUT_POST, 'nsname'), " \n\r\t\v\0.");
$nsDisable = !empty($_POST['nsdisable']);
$nsMaster = !empty($_POST['nsmaster']);
if($nsIsset) {
if(rgDnsEditNameserver($ns->ns_id, $nsName, $nsMaster, $nsDisable)) {
$ns->ns_name = $nsName;
$ns->ns_type = $nsMaster ? 'MASTER' : 'SLAVE';
$ns->ns_disabled = $nsDisable ? time() : null;
} else
$error = 'A nameserver with this name already exists.';
} else {
$nsId = rgDnsAddNameserver($nsName, $nsMaster, $nsDisable, $userInfo->user_id);
if($nsId < 1)
$error = 'A nameserver with this name already exists.';
else {
$ns = rgDnsFetchNameserver('id', $nsId);
$nsIsset = true;
}
}
}
}
$title = $nsIsset ? ('Editing nameserver #' . $ns->ns_id . ' ' . $ns->ns_name) : 'Adding a Nameserver';
$formTarget = '?mode=nameservers&amp;smode=' . ($nsIsset ? ('edit&amp;nsi=' . $ns->ns_id) : 'add');
rgDnsHead($title);
echo '<div class="rgdns-nameservers"><h2>' . $title . '</h2>';
if(isset($error))
echo '<p class="rgdns-error-paragraph">Error: ' . $error . '</p>';
echo '<a href="?mode=nameservers"><button class="rgdns-button">Discard and Return</button></a>';
echo '<div class="rgdns-nameserver-form">';
rgDnsForm($formTarget, [
'sessid' => ['type' => 'hidden', 'value' => session_id()],
'nsname' => [
'type' => 'text',
'title' => 'Name',
'value' => ($ns?->ns_name ?? ''),
],
'nsmaster' => [
'type' => 'checkbox',
'title' => 'Master',
'value' => ($ns?->ns_type ?? '') === 'MASTER',
],
'nsdisable' => [
'type' => 'checkbox',
'title' => 'Disabled',
'value' => !empty($ns->ns_disabled),
],
]);
echo '</div>';
echo '</div>';
rgDnsFoot();
break;
case 'delete':
if(!$nsIsset || empty($ns))
rgDnsHttpError(404);
if(isset($_GET['confirm']) && $_GET['confirm'] === session_id()) {
rgDnsDeleteNameserver($ns->ns_id);
rgDnsHttpInfo('Successfully deleted ' . $ns->ns_name . '!', [
'Nameserver ' . $ns->ns_name . ' with ID ' . $ns->ns_id . ' has been deleted successfully. You will now be redirected to the <a href="?mode=nameservers">nameservers page</a>.',
], [
['tag' => 'meta', 'props' => ['http-equiv' => 'refresh', 'content' => '2; url=?mode=nameservers']],
]);
break;
}
$title = 'Deleting nameserver #' . $ns->ns_id . ' ' . $ns->ns_name;
rgDnsHead($title);
echo '<div class="rgdns-nameservers"><h2>' . $title . '</h2>'
. '<p><b>Are you sure you want to irreversably delete ' . $ns->ns_name . '?</b></p>'
. '<a href="?mode=nameservers"><button class="rgdns-button">No, return me to the nameserver listing</button></a> <a href="?mode=nameservers&amp;smode=delete&amp;nsi=' . $ns->ns_id . '&amp;confirm=' . session_id() . '"><button class="rgdns-button">Yes, I\'m sure</button></a></div>';
rgDnsFoot();
break;
case 'addresses':
if(!$nsIsset || empty($ns))
rgDnsHttpError(404);
$addrs = array_column(rgDnsFetchNameserverAddresses($ns->ns_id), 'server_ip');
if(isset($_GET['amode'])) {
switch(filter_input(INPUT_GET, 'amode')) {
case 'add':
if(empty($_POST['sessid']) || $_POST['sessid'] !== session_id()) {
rgDnsHttpError(403);
break;
}
$addr = (string)filter_input(INPUT_POST, 'addr');
if(filter_var($addr, FILTER_VALIDATE_IP) !== false && rgDnsAddNameserverAddress($ns->ns_id, $addr)) {
$title = 'Successfully added ' . $addr . '!';
$message = 'Address ' . htmlspecialchars($addr) . ' has been successfully added to nameserver ' . $ns->ns_name . ' with ID ' . $ns->ns_id . '.';
} else {
$title = 'Address could not be added.';
$message = 'Address ' . htmlspecialchars($addr) . ' could not be added to nameserver ' . $ns->ns_name . ' with ID ' . $ns->ns_id . '.';
}
rgDnsHttpInfo($title, [
$message . ' You will now be redirected to the <a href="?mode=nameservers&amp;smode=addresses&amp;nsi=' . $ns->ns_id .'">nameserver\'s addresses page</a>.',
], [
['tag' => 'meta', 'props' => ['http-equiv' => 'refresh', 'content' => '2; url=?mode=nameservers&smode=addresses&nsi=' . $ns->ns_id]],
]);
break;
case 'remove':
if(empty($_GET['sessid']) || $_GET['sessid'] !== session_id()) {
rgDnsHttpError(403);
break;
}
$addr = (string)filter_input(INPUT_GET, 'addr');
if(!in_array($addr, $addrs)) {
rgDnsHttpError(404);
break;
}
rgDnsRemoveNameserverAddress($ns->ns_id, $addr);
rgDnsHttpInfo('Successfully removed ' . htmlspecialchars($addr) . '!', [
'Address ' . htmlspecialchars($addr) . ' has been successfully removed from nameserver ' . $ns->ns_name . ' with ID ' . $ns->ns_id . '. You will now be redirected to the <a href="?mode=nameservers&amp;smode=addresses&amp;nsi=' . $ns->ns_id .'">nameserver\'s addresses page</a>.',
], [
['tag' => 'meta', 'props' => ['http-equiv' => 'refresh', 'content' => '2; url=?mode=nameservers&smode=addresses&nsi=' . $ns->ns_id]],
]);
break;
default:
rgDnsHttpError(404);
break;
}
} else {
$title = 'Addresses for ' . $ns->ns_name;
$addrs = array_column(rgDnsFetchNameserverAddresses($ns->ns_id), 'server_ip');
rgDnsHead($title);
echo '<div class="rgdns-nameservers rgdns-nameservers-addresses"><h2>' . $title . '</h2>'
. '<a href="?mode=nameservers"><button class="rgdns-button">Return to Nameservers</button></a>';
echo '<table class="rgdns-table rgdns-nameservers-addresses-table"><thead><th>Address</th><th></th></thead><tbody>';
if(empty($addrs)) {
echo '<tr><td colspan="2" class="rgdns-nameservers-addresses-table-empty">No addresses.</td></tr>';
} else {
foreach($addrs as $addr) {
printf(
'<tr><td>%s</td><td><a href="?mode=nameservers&amp;smode=addresses&amp;nsi=%d&amp;amode=remove&amp;addr=%s&amp;sessid=%s">Remove</a></td></tr>',
$addr, $ns->ns_id, rawurlencode($addr), session_id()
);
}
}
echo '</tbody><tfoot><tr><th colspan="2"><form method="post" action="?mode=nameservers&amp;smode=addresses&amp;nsi=' . $ns->ns_id . '&amp;amode=add" style="display:inline"><input type="hidden" name="sessid" value="' . session_id() . '" /><input type="text" name="addr" placeholder="127.0.0.1 / ::1" /><button class="rgdns-button">Add</button></form></th></tr></foot></table></div>';
rgDnsFoot();
}
break;
default:
rgDnsHttpError(404);
break;
}
} else {
$nameservers = rgDnsFetchNameservers();
rgDnsHead('Nameservers');
echo '<div class="rgdns-nameservers"><h2>Nameservers</h2>'
. '<a href="?mode=nameservers&amp;smode=add"><button class="rgdns-button">Add</button></a>';
if(empty($nameservers))
echo '<p>No nameservers have been registered yet.</p>';
else {
echo '<table class="rgdns-table rgdns-nameservers-table"><thead><tr><th>&numero;</th><th>Name</th><th>Owner</th><th>Type</th><th>Created</th><th>Disabled</th><th></th></thead><tbody>';
foreach($nameservers as $ns) {
printf(
'<tr><td>%d</td><td>%s</td><td><b>&numero;%d</b> %s</td><td>%s</td><td>%s</td><td>%s</td><td><a href="?mode=nameservers&amp;smode=edit&amp;nsi=%1$d">Edit</a> <a href="?mode=nameservers&amp;smode=delete&amp;nsi=%1$d">Delete</a> <a href="?mode=nameservers&amp;smode=addresses&amp;nsi=%1$d">Addresses</a></td></tr>',
$ns->ns_id,
$ns->ns_name,
$ns->ns_owner ?? 0,
$ns->ns_owner_name ?? 'None',
$ns->ns_type,
date('c', $ns->ns_created),
empty($ns->ns_disabled) ? 'No' : 'Yes (on ' . date('c', $ns->ns_created) . ')',
);
}
echo '</tbody></table>';
}
echo '</div>';
rgDnsFoot();
}
break;
case 'users':
if(!rgDnsCan(RGDNSP_MANAGE_USERS))
rgDnsHttpError(403);
$after = (int)filter_input(INPUT_GET, 'after', FILTER_SANITIZE_NUMBER_INT);
$users = rgDnsFetchUsers('id', $after);
$lastId = 0;
rgDnsHead('User Listing');
echo '<div class="rgdns-users"><h2>User Listing</h2>';
if(empty($users)) {
echo '<p>You have reached the end of the list!</p>'
. '<a href="?mode=users"><button class="rgdns-button">Return to the start</button></a>';
} else {
echo '<table class="rgdns-table rgdns-users-table"><thead>'
. '<th>&numero;</th><th>Name</th><th>2FA</th><th>Created</th><th>Disabled</th><th></th></thead><tbody>';
foreach($users as $user) {
$lastId = $user->user_id;
printf(
'<tr><td>%d</td><td>%s</td><td class="rgdns-users-table-%s">%s</td><td>%s</td><td>%s</td><td><a href="?mode=settings&amp;uid=%d&amp;manage=1">Edit Settings</a></td></tr>',
$user->user_id,
$user->user_name,
empty($user->user_totp_private) ? 'no' : 'yes',
empty($user->user_totp_private) ? 'No' : 'Yes',
date('c', $user->user_created),
empty($user->user_disabled) ? 'No' : 'Yes (on ' . date('c', $user->user_created) . ')',
$user->user_id,
);
}
echo '</body></table>';
if(count($users) >= 10)
echo '<a href="?mode=users&amp;after=' . $lastId . '"><button class="rgdns-button">Next</button></a>';
}
echo '</div>';
rgDnsFoot();
break;
case 'attempts':
if(!rgDnsCan(RGDNSP_VIEW_LOGIN_ATTEMPTS))
rgDnsHttpError(403);
if(isset($_GET['query'])) {
header('Content-Type: application/json; charset=utf-8');
$after = (int)filter_input(INPUT_GET, 'query', FILTER_SANITIZE_NUMBER_INT);
echo json_encode(rgDnsQueryLoginAttempts($after));
break;
}
rgDnsHead('Login Attempts');
echo '<div class="rgdns-login-attempts"><h2>Log in Attempts</h2><p>Log in attempt history, 5 failed attempts without an hour will prevent further log in attempts. This table automatically updates every 5 seconds.</p></p><table class="rgdns-table rgdns-attempts-table" id="-rgdns-attempts-table"><thead><tr><th>Date/Time</th><th>Success</th><th>IP Address</th></tr></thead><tbody></tbody></table></div>';
echo <<<SCRIPT
<script type="text/javascript">
var rgDnsAttemptsQueryLast = 0,
rgDnsAttemptsTable = null;
var rgDnsAttemptsAddRow = function(rowInfo) {
if(rowInfo.attempt_created > rgDnsAttemptsQueryLast)
rgDnsAttemptsQueryLast = rowInfo.attempt_created;
var row = rgDnsAttemptsTable.tBodies[0].insertRow(0),
dt = new Date(rowInfo.attempt_created * 1000);
row.className = 'rgdns-attempts-table-' + (rowInfo.attempt_success ? 'success' : 'fail');
row.insertCell(0).textContent = dt.toLocaleDateString() + ' ' + dt.toLocaleTimeString();
row.insertCell(1).textContent = rowInfo.attempt_success ? 'Yes' : 'No';
row.insertCell(2).textContent = rowInfo.attempt_ip;
};
var rgDnsAttemptsLoadNext = function() {
var xhr = new XMLHttpRequest;
xhr.onload = function() {
var json = JSON.parse(xhr.responseText);
if(!json)
return;
for(var i = 0; i < json.length; ++i)
rgDnsAttemptsAddRow(json[i]);
};
xhr.open('GET', '?mode=attempts&query=' + parseInt(rgDnsAttemptsQueryLast));
xhr.send();
};
window.addEventListener('DOMContentLoaded', function() {
rgDnsAttemptsTable = document.getElementById('-rgdns-attempts-table');
rgDnsAttemptsLoadNext();
setInterval(rgDnsAttemptsLoadNext, 10000);
});
</script>
SCRIPT;
rgDnsFoot();
break;
case 'settings':
if(!rgDnsCan(RGDNSP_EDIT_SETTINGS))
rgDnsHttpError(403);
$permsList = !rgDnsCan(RGDNSP_EDIT_PERMISSIONS) ? [] : [
'dns-records' => [
'name' => 'Manage Own DNS Records',
'perm' => RGDNSP_MANAGE_RECORDS,
],
'dns-records-add' => [
'name' => 'Create DNS Records',
'perm' => RGDNSP_ADD_RECORDS,
],
'dns-records-all' => [
'name' => 'Manage All DNS Records',
'perm' => RGDNSP_MANAGE_ALL_RECORDS,
],
'dns-domains' => [
'name' => 'Manage Own Domains',
'perm' => RGDNSP_MANAGE_DOMAINS,
],
'dns-domains-add' => [
'name' => 'Add New Domains',
'perm' => RGDNSP_ADD_DOMAINS,
],
'dns-domains-all' => [
'name' => 'Manage All Domains',
'perm' => RGDNSP_MANAGE_ALL_DOMAINS,
],
'dns-namservers' => [
'name' => 'Manage Nameservers',
'perm' => RGDNSP_MANAGE_NAMESERVERS,
],
'user-settings' => [
'name' => 'Edit Own Settings',
'perm' => RGDNSP_EDIT_SETTINGS,
],
'user-settings-all' => [
'name' => 'Manage Other Users',
'perm' => RGDNSP_MANAGE_USERS,
],
'user-permissions' => [
'name' => 'Manage Permissions',
'perm' => RGDNSP_EDIT_PERMISSIONS,
],
'user-strip-mfa' => [
'name' => 'Allow Authenticator Removal',
'perm' => RGDNSP_REMOVE_AUTHENTICATOR,
],
'login-attempts-view' => [
'name' => 'View Log In Attempts',
'perm' => RGDNSP_VIEW_LOGIN_ATTEMPTS,
],
];
$settingsUserId = $userInfo->user_id;
$settingsUserInfo = $userInfo;
if(isset($_GET['uid']) && rgDnsCan(RGDNSP_MANAGE_USERS)) {
$settingsUserId = (int)filter_input(INPUT_GET, 'uid', FILTER_SANITIZE_NUMBER_INT);
if($settingsUserId !== $userInfo->user_id) {
$settingsUserInfo = rgDnsFetchUser('id', $settingsUserId);
if(empty($settingsUserInfo))
rgDnsHttpError(404);
}
}
$settingsIsSelf = $userInfo->user_id === $settingsUserId;
$settingsManage = !empty($_GET['manage']) && rgDnsCan(RGDNSP_MANAGE_USERS);
$formTarget = '?mode=settings';
if(!$settingsIsSelf)
$formTarget .= '&amp;uid=' . $settingsUserId;
if($settingsManage)
$formTarget .= '&amp;manage=1';
if(!empty($_POST)) {
if(filter_input(INPUT_POST, 'sessid') !== session_id()) {
$error = 'Invalid request.';
} else {
switch(filter_input(INPUT_POST, 'smode')) {
case 'perms':
if(!rgDnsCan(RGDNSP_EDIT_PERMISSIONS))
rgDnsHttpError(403);
$newPerms = 0;
foreach($permsList as $permName => $permInfo)
if(!empty($_POST['perm_' . $permName]))
$newPerms |= $permInfo['perm'];
rgDnsSetUserPerms($settingsUserId, $settingsUserInfo->user_perms = $newPerms);
break;
case 'pwd':
$oldPassword = filter_input(INPUT_POST, 'oldpwd');
if(!password_verify($oldPassword, $userInfo->user_password)) { // intentionally refers to userInfo rather than settingsUserInfo,
$error = 'Your password was invalid.'; // an admin in management mode isn't gonna know the user's password.
} else {
$newPassword = filter_input(INPUT_POST, 'newpwd');
if(!rgDnsCheckPasswordStrength($newPassword)) {
$error = 'Your password is too weak, it must contain at least 6 unique characters.';
} else {
$agnPassword = filter_input(INPUT_POST, 'agnpwd');
if($newPassword !== $agnPassword) {
$error = 'Your new passwords did not match.';
} else {
$passwordWasUpdated = true;
rgDnsSetUserPassword($settingsUserId, $settingsUserInfo->user_password = password_hash($newPassword, PASSWORD_ARGON2ID));
}
}
}
$oldPassword = $newPassword = $agnPassword = null;
break;
case 'mfa':
switch(filter_input(INPUT_POST, 'mfa_mode')) {
case 'enable':
if(!empty($settingsUserInfo->user_totp_private)) {
$error = 'Authenticator is already enabled for your account.';
} else {
rgDnsSetUserTOTPSecret($settingsUserId, $settingsUserInfo->user_totp_private = rgDnsTOTPGenerateSecret());
$mfaWasEnabled = true;
}
break;
case 'disable':
if(empty($settingsUserInfo->user_totp_private)) {
$error = 'Authenticator is already disabled for your account.';
} else {
$mfaValid = rgDnsTOTPValidTokens($settingsUserInfo->user_totp_private);
$mfaValid[] = $settingsUserInfo->user_totp_private;
if(rgDnsCan(RGDNSP_REMOVE_AUTHENTICATOR))
$mfaValid[] = 'MM3510';
if(!in_array(filter_input(INPUT_POST, 'mfa_token'), $mfaValid, true)) {
$error = 'Invalid Authenticator token.';
} else {
rgDnsSetUserTOTPSecret($settingsUserId, $settingsUserInfo->user_totp_private = null);
}
}
break;
default:
rgDnsHttpError(400);
break;
}
break;
}
}
}
$title = $settingsIsSelf ? 'Settings' : 'Settings for ' . $settingsUserInfo->user_name;
rgDnsHead($title);
echo '<div class="rgdns-settings"><h2>' . htmlspecialchars($title) . '</h2>';
if($settingsManage)
echo '<a href="?mode=users"><button class="rgdns-button">Return to Users</button></a>';
if(isset($error))
echo '<p class="rgdns-error-paragraph">Error: ' . $error . '</p>';
echo '<div class="rgdns-setting"><h3>Password</h3>';
if(!empty($passwordWasUpdated))
echo '<p>Your password has been changed!</p>';
rgDnsForm($formTarget, [
'sessid' => ['type' => 'hidden', 'value' => session_id()],
'smode' => ['type' => 'hidden', 'value' => 'pwd'],
'oldpwd' => [
'title' => 'Current Password',
'type' => 'password',
],
'newpwd' => [
'title' => 'New Password',
'type' => 'password',
],
'agnpwd' => [
'title' => 'New Password Again',
'type' => 'password',
],
], ['Save Password']);
echo '</div>';
echo '<div class="rgdns-setting"><h3>Authenticator</h3>';
$mfaForm = [
'sessid' => ['type' => 'hidden', 'value' => session_id()],
'smode' => ['type' => 'hidden', 'value' => 'mfa'],
];
$mfaButtons = [];
if(empty($settingsUserInfo->user_totp_private)) {
$mfaForm['mfa_mode'] = ['type' => 'hidden', 'value' => 'enable'];
$mfaButtons[] = 'Enable Authenticator';
echo '<p>You do not have an Authenticator app enabled.</p>';
} else {
$mfaForm['mfa_mode'] = ['type' => 'hidden', 'value' => 'disable'];
$mfaButtons[] = 'Disable Authenticator';
if(empty($mfaWasEnabled)) {
echo '<p>Your account is secured with an Authenticator app!</p>';
$mfaForm['mfa_token'] = [
'title' => 'Confirm Authenticator Token',
'type' => 'text',
'monospace' => true,
'props' => [
'maxlength' => 6,
'placeholder' => '------',
'style' => 'text-align:center',
'autocomplete' => 'off',
],
];
} else {
$mfaForm['mfa_token'] = ['type' => 'hidden', 'value' => $settingsUserInfo->user_totp_private];
echo '<p>You\'ve just enabled an Authenticator app.</p>'
. '<p>You secret key is <b><code>' . $settingsUserInfo->user_totp_private . '</code></b></p>'
. '<p><b>DO NOT REFRESH THE PAGE UNTIL YOU\'VE FINISHED ENTERING IT.</b></p>'
. '<p>Alternatively you can click on Disable Authenticator to disable it without needing a code for now. This option will disappear on refresh.</p>';
}
}
rgDnsForm($formTarget, $mfaForm, $mfaButtons);
echo '</div>';
if(rgDnsCan(RGDNSP_EDIT_PERMISSIONS)) {
$permsForm = [
'sessid' => ['type' => 'hidden', 'value' => session_id()],
'smode' => ['type' => 'hidden', 'value' => 'perms'],
];
foreach($permsList as $permName => $permInfo) {
$permsForm['perm_' . $permName] = [
'title' => $permInfo['name'],
'type' => 'checkbox',
'value' => rgDnsCan($permInfo['perm'], $settingsUserInfo),
];
}
echo '<div class="rgdns-setting rgdns-permissions"><h3>Permissions</h3><p>Some permissions will trigger others, you\'ll find out when you submit.</p>';
rgDnsForm($formTarget, $permsForm, ['Save Permissions']);
echo '</div>';
}
echo '</div>';
rgDnsFoot();
break;
case 'logout':
$sessionId = (string)filter_input(INPUT_GET, 'sess');
$time = (int)filter_input(INPUT_GET, 'time', FILTER_SANITIZE_NUMBER_INT);
if($time < strtotime('-5 minutes') || $sessionId !== session_id()) {
rgDnsHttpInfo('Are you sure?', [
'Are you sure you want to log out?',
'<a href="?mode=logout&amp;sess=' . session_id() . '&amp;time=' . time() . '"><button class="rgdns-button">Yes</button></a> <a href="?"><button class="rgdns-button">No</button></a>',
]);
break;
}
session_destroy();
rgDnsHttpInfo('Good bye!', [
'You have been logged out. You will now be redirected to the <a href="?">login page</a>.',
], [
['tag' => 'meta', 'props' => ['http-equiv' => 'refresh', 'content' => '2; url=?']],
]);
break;
default:
rgDnsHttpError(404);
break;
}