1675 lines
80 KiB
PHP
1675 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&sess=' . session_id() . '&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">© 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&sess=' . session_id() . '&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 . '&smode=' . ($hasRecord ? ('edit&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&smode=delete&di=' . $domainInfo->domain_id . '&ri=' . $recordInfo->record_id . '&confirm=' . session_id() . '&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&smode=add&di=' . $domainInfo->domain_id . '&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&smode=edit&di=' . $domainInfo->domain_id . '&ri=' . $record->record_id . '&ret=' . $returnUrl . '">Edit</a>';
|
||
|
echo ' <a href="?mode=records&smode=delete&di=' . $domainInfo->domain_id . '&ri=' . $record->record_id . '&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 . '&smode=' . ($domainIsset ? ('edit&di=' . $domain->domain_id) : 'add') . '&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&smode=delete&di=' . $domain->domain_id . '&confirm=' . session_id() . '&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 . '&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>№</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>№%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&di=' . $domain->domain_id . '&ret=' . $returnUrl . '">Records</a>';
|
||
|
if(empty($domain->domain_disabled) || $manageAll) {
|
||
|
echo ' <a href="' . $baseUrl . '&smode=edit&di=' . $domain->domain_id . '&ret=' . $returnUrl . '">Edit</a>';
|
||
|
echo ' <a href="' . $baseUrl . '&smode=delete&di=' . $domain->domain_id . '&ret=' . $returnUrl . '">Delete</a>';
|
||
|
}
|
||
|
echo '</td></tr>';
|
||
|
}
|
||
|
echo '</tbody></table>';
|
||
|
|
||
|
if($domainsAfter !== null && count($domains) >= 10)
|
||
|
echo '<a href="' . $baseUrl . '&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&smode=' . ($nsIsset ? ('edit&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&smode=delete&nsi=' . $ns->ns_id . '&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&smode=addresses&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&smode=addresses&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&smode=addresses&nsi=%d&amode=remove&addr=%s&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&smode=addresses&nsi=' . $ns->ns_id . '&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&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>№</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>№%d</b> %s</td><td>%s</td><td>%s</td><td>%s</td><td><a href="?mode=nameservers&smode=edit&nsi=%1$d">Edit</a> <a href="?mode=nameservers&smode=delete&nsi=%1$d">Delete</a> <a href="?mode=nameservers&smode=addresses&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>№</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&uid=%d&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&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 .= '&uid=' . $settingsUserId;
|
||
|
if($settingsManage)
|
||
|
$formTarget .= '&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&sess=' . session_id() . '&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;
|
||
|
}
|