0)
return $matches[0];
return null;
}
function validEmail($email)
{
return filter_var($email, FILTER_VALIDATE_EMAIL)
// Make sure that the email does not contain special characters which
// may be used to execute shell commands, even though they may be valid
// in an email address. If the escaped email does not equal the original
// email, it is not safe to store in setupVars.
&& escapeshellcmd($email) === $email;
}
$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,["hwaddr"=>$mac, "IP"=>$one, "host"=>""]);
elseif(strlen($two) == 0)
// dhcp-host=mac,hostname - no IP
array_push($dhcp_static_leases,["hwaddr"=>$mac, "IP"=>"", "host"=>$one]);
else
// dhcp-host=mac,IP,hostname
array_push($dhcp_static_leases,["hwaddr"=>$mac, "IP"=>$one, "host"=>$two]);
}
else if(validIP($one) && validDomain($mac))
{
// dhcp-host=hostname,IP - no MAC
array_push($dhcp_static_leases,["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 = [];
function readDNSserversList()
{
// Reset list
$list = [];
$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 = [];
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(strlen($hostname) == 0 && strlen($ip) == 0)
{
throw new Exception("You can not omit both the IP address and the host name!
", 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 release for MAC address (".htmlspecialchars($mac).") already defined!
", 4);
}
if($ip !== "noip" && $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 = [];
// Add selected predefined servers to list
foreach ($DNSserverslist as $key => $value)
{
foreach(["v4_1", "v4_2", "v6_1", "v6_2"] as $type)
{
if(@array_key_exists("DNSserver".str_replace(".","_",$value[$type]),$_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).
".
"Please use only subnets /8, /16, /24, and /32.
";
}
// 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($_POST["DNSinterface"] === "single")
{
$DNSinterface = "single";
}
elseif($_POST["DNSinterface"] === "all")
{
$DNSinterface = "all";
}
else
{
$DNSinterface = "local";
}
}
else
{
// Fallback
$DNSinterface = "local";
}
pihole_execute("-a -i ".$DNSinterface." -web");
// 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($_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 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"]) && isset($_POST["querylog-blocked"]))
{
pihole_execute("-a setquerylog all");
if(!isset($_POST["privacyMode"]))
{
$success .= "All entries will be shown in Query Log";
}
else
{
$success .= "Only blocked entries will be shown in Query Log";
}
}
elseif(isset($_POST["querylog-permitted"]))
{
pihole_execute("-a setquerylog permittedonly");
if(!isset($_POST["privacyMode"]))
{
$success .= "Only permitted will be shown in Query Log";
}
else
{
$success .= "No entries 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";
}
if(isset($_POST["privacyMode"]))
{
pihole_execute("-a privacymode true");
$success .= " (privacy mode enabled)";
}
else
{
pihole_execute("-a privacymode false");
}
break;
case "webUI":
$adminemail = trim($_POST["adminemail"]);
if(strlen($adminemail) == 0 || !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 been not been changed";
}
}
else
{
$error .= "Invalid privacy level (".$level.")!";
}
break;
// Flush network table
case "flusharp":
pihole_execute("arpflush quiet", $output);
$error = implode("
", $output);
if(strlen($error) == 0)
{
$success .= "The network table has been flushed";
}
break;
default:
// Option not found
$debug = true;
break;
}
}
// 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;
}
?>