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) && 0 == strlen($two)) { // dhcp-host=mac,IP - no HOST array_push($dhcp_static_leases, array('hwaddr' => $mac, 'IP' => $one, 'host' => '')); } elseif (0 == strlen($two)) { // 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!
', 0); } $mac = strtoupper($mac); if (!validIP($ip) && strlen($ip) > 0) { throw new Exception('IP address ('.htmlspecialchars($ip).') is invalid!
', 1); } if (!validDomain($hostname) && strlen($hostname) > 0) { throw new Exception('Host name ('.htmlspecialchars($hostname).') is invalid!
', 2); } if (0 == strlen($hostname) && 0 == strlen($ip)) { throw new Exception('You can not omit both the IP address and the host name!
', 3); } if (0 == strlen($hostname)) { $hostname = 'nohost'; } if (0 == strlen($ip)) { $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!
', 4); } if ('noip' !== $ip && $lease['IP'] === $ip) { throw new Exception('Static lease for IP address ('.htmlspecialchars($ip).') already defined!
', 5); } if ($lease['host'] === $hostname) { throw new Exception('Static lease for hostname ('.htmlspecialchars($hostname).') already defined!
', 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!
'; } else { if (count($exploded) > 1) { $port = trim($exploded[1]); if (!is_numeric($port)) { $error .= 'Port ('.htmlspecialchars($port).') is invalid!
'; } 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.
'; } // 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!
'. 'This field requires CIDR notation for local subnets (e.g., 192.168.0.0/16).
'; } // Validate target IP $target = trim($_POST['rev_server_target']); if (!validIP($target)) { $error .= 'Conditional forwarding target IP ("'.htmlspecialchars($target).'") is invalid!
'; } // 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!
'; } if (!$error) { $extra .= ' rev-server '.$cidr.' '.$target.' '.$domain; } } // Check if DNSinterface is set if (isset($_POST['DNSinterface'])) { if ('single' === $_POST['DNSinterface']) { $DNSinterface = 'single'; } elseif ('bind' === $_POST['DNSinterface']) { $DNSinterface = 'bind'; } elseif ('all' === $_POST['DNSinterface']) { $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)).'
'; $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 ('Disable' === $_POST['action']) { pihole_execute('-l off'); $success .= 'Logging has been disabled and logs have been flushed'; } elseif ('Disable-noflush' === $_POST['action']) { pihole_execute('-l off noflush'); $success .= 'Logging has been disabled, your logs have not 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)!
'; } 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)!
'; } 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
'; } 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': $adminemail = trim($_POST['adminemail']); if (0 == strlen($adminemail) || !isset($adminemail)) { $adminemail = ''; } if (strlen($adminemail) > 0 && !validEmail($adminemail)) { $error .= 'Administrator email address ('.htmlspecialchars($adminemail).') is invalid!
'; } else { pihole_execute('-a -e \''.$adminemail.'\''); } 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!
'; } $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!
'; } // Validate to IP $to = $_POST['to']; if (!validIP($to)) { $error .= 'To IP ('.htmlspecialchars($to).') is invalid!
'; } // Validate router IP $router = $_POST['router']; if (!validIP($router)) { $error .= 'Router IP ('.htmlspecialchars($router).') is invalid!
'; } $domain = $_POST['domain']; // Validate Domain name if (!validDomain($domain)) { $error .= 'Domain name '.htmlspecialchars($domain).' is invalid!
'; } $leasetime = $_POST['leasetime']; // Validate Lease time length if (!is_numeric($leasetime) || intval($leasetime) < 0) { $error .= 'Lease time '.htmlspecialchars($leasetime).' is invalid!
'; } 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('
', $output); } if (0 == strlen($error)) { $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 (1 == $bytes) { $bytes = $bytes.' byte'; } else { $bytes = '0 bytes'; } return $bytes; }