mirror of
https://github.com/pi-hole/web.git
synced 2025-12-25 21:16:21 +00:00
606 lines
21 KiB
PHP
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;
|
|
}
|