Files
web/scripts/pi-hole/php/savesettings.php
Christian König e32bc2d07a Remove email functionality
Signed-off-by: Christian König <ckoenig@posteo.de>
2022-08-17 23:18:07 +02:00

606 lines
21 KiB
PHP

<?php
/* Pi-hole: A black hole for Internet advertisements
* (c) 2017 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license.
*/
require_once 'func.php';
if (!in_array(basename($_SERVER['SCRIPT_FILENAME']), array('settings.php', 'teleporter.php'), true)) {
exit('Direct access to this script is forbidden!');
}
// Check for existence of variable
// and test it only if it exists
function istrue(&$argument)
{
if (isset($argument)) {
if ($argument) {
return true;
}
}
return false;
}
function formatMAC($mac_addr)
{
preg_match('/([0-9a-fA-F]{2}[:]){5}([0-9a-fA-F]{2})/', $mac_addr, $matches);
if (count($matches) > 0) {
return $matches[0];
}
return null;
}
$dhcp_static_leases = array();
function readStaticLeasesFile($origin_file = '/etc/dnsmasq.d/04-pihole-static-dhcp.conf')
{
global $dhcp_static_leases;
$dhcp_static_leases = array();
if (!file_exists($origin_file) || !is_readable($origin_file)) {
return false;
}
$dhcpstatic = @fopen($origin_file, 'r');
if (!is_resource($dhcpstatic)) {
return false;
}
while (!feof($dhcpstatic)) {
// Remove any possibly existing variable with this name
$mac = '';
$one = '';
$two = '';
sscanf(trim(fgets($dhcpstatic)), 'dhcp-host=%[^,],%[^,],%[^,]', $mac, $one, $two);
if (strlen($mac) > 0 && validMAC($mac)) {
if (validIP($one) && strlen($two) == 0) {
// dhcp-host=mac,IP - no HOST
array_push($dhcp_static_leases, array('hwaddr' => $mac, 'IP' => $one, 'host' => ''));
} elseif (strlen($two) == 0) {
// dhcp-host=mac,hostname - no IP
array_push($dhcp_static_leases, array('hwaddr' => $mac, 'IP' => '', 'host' => $one));
} else {
// dhcp-host=mac,IP,hostname
array_push($dhcp_static_leases, array('hwaddr' => $mac, 'IP' => $one, 'host' => $two));
}
} elseif (validIP($one) && validDomain($mac)) {
// dhcp-host=hostname,IP - no MAC
array_push($dhcp_static_leases, array('hwaddr' => '', 'IP' => $one, 'host' => $mac));
}
}
return true;
}
function isequal(&$argument, &$compareto)
{
if (isset($argument)) {
if ($argument === $compareto) {
return true;
}
}
return false;
}
function isinserverlist($addr)
{
global $DNSserverslist;
foreach ($DNSserverslist as $key => $value) {
if (isequal($value['v4_1'], $addr) || isequal($value['v4_2'], $addr)) {
return true;
}
if (isequal($value['v6_1'], $addr) || isequal($value['v6_2'], $addr)) {
return true;
}
}
return false;
}
$DNSserverslist = array();
function readDNSserversList()
{
// Reset list
$list = array();
$handle = @fopen('/etc/pihole/dns-servers.conf', 'r');
if ($handle) {
while (($line = fgets($handle)) !== false) {
$line = rtrim($line);
$line = explode(';', $line);
$name = $line[0];
$values = array();
if (!empty($line[1]) && validIP($line[1])) {
$values['v4_1'] = $line[1];
}
if (!empty($line[2]) && validIP($line[2])) {
$values['v4_2'] = $line[2];
}
if (!empty($line[3]) && validIP($line[3])) {
$values['v6_1'] = $line[3];
}
if (!empty($line[4]) && validIP($line[4])) {
$values['v6_2'] = $line[4];
}
$list[$name] = $values;
}
fclose($handle);
}
return $list;
}
require_once 'database.php';
function addStaticDHCPLease($mac, $ip, $hostname)
{
global $error, $success, $dhcp_static_leases;
try {
if (!validMAC($mac)) {
throw new Exception('MAC address ('.htmlspecialchars($mac).') is invalid!<br>', 0);
}
$mac = strtoupper($mac);
if (!validIP($ip) && strlen($ip) > 0) {
throw new Exception('IP address ('.htmlspecialchars($ip).') is invalid!<br>', 1);
}
if (!validDomain($hostname) && strlen($hostname) > 0) {
throw new Exception('Host name ('.htmlspecialchars($hostname).') is invalid!<br>', 2);
}
if (strlen($hostname) == 0 && strlen($ip) == 0) {
throw new Exception('You can not omit both the IP address and the host name!<br>', 3);
}
if (strlen($hostname) == 0) {
$hostname = 'nohost';
}
if (strlen($ip) == 0) {
$ip = 'noip';
}
// Test if this lease is already included
readStaticLeasesFile();
foreach ($dhcp_static_leases as $lease) {
if ($lease['hwaddr'] === $mac) {
throw new Exception('Static lease for MAC address ('.htmlspecialchars($mac).') already defined!<br>', 4);
}
if ($ip !== 'noip' && $lease['IP'] === $ip) {
throw new Exception('Static lease for IP address ('.htmlspecialchars($ip).') already defined!<br>', 5);
}
if ($lease['host'] === $hostname) {
throw new Exception('Static lease for hostname ('.htmlspecialchars($hostname).') already defined!<br>', 6);
}
}
pihole_execute('-a addstaticdhcp '.$mac.' '.$ip.' '.$hostname);
$success .= 'A new static address has been added';
return true;
} catch (Exception $exception) {
$error .= $exception->getMessage();
return false;
}
}
// Read available DNS server list
$DNSserverslist = readDNSserversList();
$error = '';
$success = '';
if (isset($_POST['field'])) {
// Handle CSRF
check_csrf(isset($_POST['token']) ? $_POST['token'] : '');
// Process request
switch ($_POST['field']) {
// Set DNS server
case 'DNS':
$DNSservers = array();
// Add selected predefined servers to list
foreach ($DNSserverslist as $key => $value) {
foreach (array('v4_1', 'v4_2', 'v6_1', 'v6_2') as $type) {
// Skip if this IP type does not
// exist (e.g. IPv4-only or only
// one IPv6 address upstream
// server)
if (!array_key_exists($type, $value)) {
continue;
}
// If server exists and is set
// (POST), we add it to the
// array of DNS servers
$server = str_replace('.', '_', $value[$type]);
if (array_key_exists('DNSserver'.$server, $_POST)) {
array_push($DNSservers, $value[$type]);
}
}
}
// Test custom server fields
for ($i = 1; $i <= 4; ++$i) {
if (array_key_exists('custom'.$i, $_POST)) {
$exploded = explode('#', $_POST['custom'.$i.'val'], 2);
$IP = trim($exploded[0]);
if (!validIP($IP)) {
$error .= 'IP ('.htmlspecialchars($IP).') is invalid!<br>';
} else {
if (count($exploded) > 1) {
$port = trim($exploded[1]);
if (!is_numeric($port)) {
$error .= 'Port ('.htmlspecialchars($port).') is invalid!<br>';
} else {
$IP .= '#'.$port;
}
}
array_push($DNSservers, $IP);
}
}
}
$DNSservercount = count($DNSservers);
// Check if at least one DNS server has been added
if ($DNSservercount < 1) {
$error .= 'No DNS server has been selected.<br>';
}
// Check if domain-needed is requested
if (isset($_POST['DNSrequiresFQDN'])) {
$extra = 'domain-needed ';
} else {
$extra = 'domain-not-needed ';
}
// Check if domain-needed is requested
if (isset($_POST['DNSbogusPriv'])) {
$extra .= 'bogus-priv ';
} else {
$extra .= 'no-bogus-priv ';
}
// Check if DNSSEC is requested
if (isset($_POST['DNSSEC'])) {
$extra .= 'dnssec';
} else {
$extra .= 'no-dnssec';
}
// Check if rev-server is requested
if (isset($_POST['rev_server'])) {
// Validate CIDR IP
$cidr = trim($_POST['rev_server_cidr']);
if (!validCIDRIP($cidr)) {
$error .= 'Conditional forwarding subnet ("'.htmlspecialchars($cidr).'") is invalid!<br>'.
'This field requires CIDR notation for local subnets (e.g., 192.168.0.0/16).<br>';
}
// Validate target IP
$target = trim($_POST['rev_server_target']);
if (!validIP($target)) {
$error .= 'Conditional forwarding target IP ("'.htmlspecialchars($target).'") is invalid!<br>';
}
// Validate conditional forwarding domain name (empty is okay)
$domain = trim($_POST['rev_server_domain']);
if (strlen($domain) > 0 && !validDomain($domain)) {
$error .= 'Conditional forwarding domain name ("'.htmlspecialchars($domain).'") is invalid!<br>';
}
if (!$error) {
$extra .= ' rev-server '.$cidr.' '.$target.' '.$domain;
}
}
// Check if DNSinterface is set
if (isset($_POST['DNSinterface'])) {
if ($_POST['DNSinterface'] === 'single') {
$DNSinterface = 'single';
} elseif ($_POST['DNSinterface'] === 'bind') {
$DNSinterface = 'bind';
} elseif ($_POST['DNSinterface'] === 'all') {
$DNSinterface = 'all';
} else {
$DNSinterface = 'local';
}
} else {
// Fallback
$DNSinterface = 'local';
}
pihole_execute('-a -i '.$DNSinterface.' -web');
// Add rate-limiting settings
if (isset($_POST['rate_limit_count'], $_POST['rate_limit_interval'])) {
// Restart of FTL is delayed
pihole_execute('-a ratelimit '.intval($_POST['rate_limit_count']).' '.intval($_POST['rate_limit_interval']).' false');
}
// If there has been no error we can save the new DNS server IPs
if (!strlen($error)) {
$IPs = implode(',', $DNSservers);
$return = pihole_execute('-a setdns "'.$IPs.'" '.$extra);
$success .= htmlspecialchars(end($return)).'<br>';
$success .= 'The DNS settings have been updated (using '.$DNSservercount.' DNS servers)';
} else {
$error .= 'The settings have been reset to their previous values';
}
break;
// Set query logging
case 'Logging':
if ($_POST['action'] === 'Disable') {
pihole_execute('-l off');
$success .= 'Logging has been disabled and logs have been flushed';
} elseif ($_POST['action'] === 'Disable-noflush') {
pihole_execute('-l off noflush');
$success .= 'Logging has been disabled, your logs have <strong>not</strong> been flushed';
} else {
pihole_execute('-l on');
$success .= 'Logging has been enabled';
}
break;
// Set domains to be excluded from being shown in Top Domains (or Ads) and Top Clients
case 'API':
// Explode the contents of the textareas into PHP arrays
// \n (Unix) and \r\n (Win) will be considered as newline
// array_filter( ... ) will remove any empty lines
$domains = array_filter(preg_split('/\r\n|[\r\n]/', $_POST['domains']));
$clients = array_filter(preg_split('/\r\n|[\r\n]/', $_POST['clients']));
$domainlist = '';
$first = true;
foreach ($domains as $domain) {
if (!validDomainWildcard($domain) || validIP($domain)) {
$error .= 'Top Domains/Ads entry '.htmlspecialchars($domain).' is invalid (use only domains)!<br>';
}
if (!$first) {
$domainlist .= ',';
} else {
$first = false;
}
$domainlist .= $domain;
}
$clientlist = '';
$first = true;
foreach ($clients as $client) {
if (!validDomainWildcard($client) && !validIP($client)) {
$error .= 'Top Clients entry '.htmlspecialchars($client).' is invalid (use only host names and IP addresses)!<br>';
}
if (!$first) {
$clientlist .= ',';
} else {
$first = false;
}
$clientlist .= $client;
}
// Set Top Lists options
if (!strlen($error)) {
// All entries are okay
pihole_execute('-a setexcludedomains '.$domainlist);
pihole_execute('-a setexcludeclients '.$clientlist);
$success .= 'The API settings have been updated<br>';
} else {
$error .= 'The settings have been reset to their previous values';
}
// Set query log options
if (isset($_POST['querylog-permitted'], $_POST['querylog-blocked'])) {
pihole_execute('-a setquerylog all');
$success .= 'All entries will be shown in Query Log';
} elseif (isset($_POST['querylog-permitted'])) {
pihole_execute('-a setquerylog permittedonly');
$success .= 'Only permitted will be shown in Query Log';
} elseif (isset($_POST['querylog-blocked'])) {
pihole_execute('-a setquerylog blockedonly');
$success .= 'Only blocked entries will be shown in Query Log';
} else {
pihole_execute('-a setquerylog nothing');
$success .= 'No entries will be shown in Query Log';
}
break;
case 'webUI':
if (isset($_POST['boxedlayout'])) {
pihole_execute('-a layout boxed');
} else {
pihole_execute('-a layout traditional');
}
if (isset($_POST['webtheme'])) {
global $available_themes;
if (array_key_exists($_POST['webtheme'], $available_themes)) {
exec('sudo pihole -a theme '.$_POST['webtheme']);
}
}
$success .= 'The webUI settings have been updated';
break;
case 'poweroff':
pihole_execute('-a poweroff');
$success = 'The system will poweroff in 5 seconds...';
break;
case 'reboot':
pihole_execute('-a reboot');
$success = 'The system will reboot in 5 seconds...';
break;
case 'restartdns':
pihole_execute('-a restartdns');
$success = 'The DNS server has been restarted';
break;
case 'flushlogs':
pihole_execute('-f');
$success = 'The Pi-hole log file has been flushed';
break;
case 'DHCP':
if (isset($_POST['addstatic'])) {
$mac = trim($_POST['AddMAC']);
$ip = trim($_POST['AddIP']);
$hostname = trim($_POST['AddHostname']);
addStaticDHCPLease($mac, $ip, $hostname);
break;
}
if (isset($_POST['removestatic'])) {
$mac = $_POST['removestatic'];
if (!validMAC($mac)) {
$error .= 'MAC address ('.htmlspecialchars($mac).') is invalid!<br>';
}
$mac = strtoupper($mac);
if (!strlen($error)) {
pihole_execute('-a removestaticdhcp '.$mac);
$success .= 'The static address with MAC address '.htmlspecialchars($mac).' has been removed';
}
break;
}
if (isset($_POST['active'])) {
// Validate from IP
$from = $_POST['from'];
if (!validIP($from)) {
$error .= 'From IP ('.htmlspecialchars($from).') is invalid!<br>';
}
// Validate to IP
$to = $_POST['to'];
if (!validIP($to)) {
$error .= 'To IP ('.htmlspecialchars($to).') is invalid!<br>';
}
// Validate router IP
$router = $_POST['router'];
if (!validIP($router)) {
$error .= 'Router IP ('.htmlspecialchars($router).') is invalid!<br>';
}
$domain = $_POST['domain'];
// Validate Domain name
if (!validDomain($domain)) {
$error .= 'Domain name '.htmlspecialchars($domain).' is invalid!<br>';
}
$leasetime = $_POST['leasetime'];
// Validate Lease time length
if (!is_numeric($leasetime) || intval($leasetime) < 0) {
$error .= 'Lease time '.htmlspecialchars($leasetime).' is invalid!<br>';
}
if (isset($_POST['useIPv6'])) {
$ipv6 = 'true';
$type = '(IPv4 + IPv6)';
} else {
$ipv6 = 'false';
$type = '(IPv4)';
}
if (isset($_POST['DHCP_rapid_commit'])) {
$rapidcommit = 'true';
} else {
$rapidcommit = 'false';
}
if (!strlen($error)) {
pihole_execute('-a enabledhcp '.$from.' '.$to.' '.$router.' '.$leasetime.' '.$domain.' '.$ipv6.' '.$rapidcommit);
$success .= 'The DHCP server has been activated '.htmlspecialchars($type);
}
} else {
pihole_execute('-a disabledhcp');
$success = 'The DHCP server has been deactivated';
}
break;
case 'privacyLevel':
$level = intval($_POST['privacylevel']);
if ($level >= 0 && $level <= 4) {
// Check if privacylevel is already set
if (isset($piholeFTLConf['PRIVACYLEVEL'])) {
$privacylevel = intval($piholeFTLConf['PRIVACYLEVEL']);
} else {
$privacylevel = 0;
}
// Store privacy level
pihole_execute('-a privacylevel '.$level);
if ($privacylevel > $level) {
pihole_execute('-a restartdns');
$success .= 'The privacy level has been decreased and the DNS resolver has been restarted';
} elseif ($privacylevel < $level) {
$success .= 'The privacy level has been increased';
} else {
$success .= 'The privacy level has not been changed';
}
} else {
$error .= 'Invalid privacy level ('.$level.')!';
}
break;
// Flush network table
case 'flusharp':
$output = pihole_execute('arpflush quiet');
$error = '';
if (is_array($output)) {
$error = implode('<br>', $output);
}
if (strlen($error) == 0) {
$success .= 'The network table has been flushed';
}
break;
default:
// Option not found
$error = 'Invalid option';
}
}
// Credit: http://stackoverflow.com/a/5501447/2087442
function formatSizeUnits($bytes)
{
if ($bytes >= 1073741824) {
$bytes = number_format($bytes / 1073741824, 2).' GB';
} elseif ($bytes >= 1048576) {
$bytes = number_format($bytes / 1048576, 2).' MB';
} elseif ($bytes >= 1024) {
$bytes = number_format($bytes / 1024, 2).' kB';
} elseif ($bytes > 1) {
$bytes = $bytes.' bytes';
} elseif ($bytes == 1) {
$bytes = $bytes.' byte';
} else {
$bytes = '0 bytes';
}
return $bytes;
}