diff --git a/scripts/pi-hole/php/customdns.php b/scripts/pi-hole/php/customdns.php
index 10748f2a..a27f1e2b 100644
--- a/scripts/pi-hole/php/customdns.php
+++ b/scripts/pi-hole/php/customdns.php
@@ -6,134 +6,10 @@
switch ($_REQUEST['action'])
{
- case 'get': echo json_encode(echoCustomDNSEntries()); break;
- case 'add': echo json_encode(addCustomDNSEntry()); break;
- case 'delete': echo json_encode(deleteCustomDNSEntry()); break;
+ case 'get': echo json_encode(echoCustomDNSEntries()); break;
+ case 'add': echo json_encode(addCustomDNSEntry()); break;
+ case 'delete': echo json_encode(deleteCustomDNSEntry()); break;
default:
die("Wrong action");
}
-
- function echoCustomDNSEntries()
- {
- $entries = getCustomDNSEntries();
-
- $data = [];
- foreach ($entries as $entry)
- $data[] = [ $entry->domain, $entry->ip ];
-
- return [ "data" => $data ];
- }
-
- function getCustomDNSEntries()
- {
- global $customDNSFile;
-
- $entries = [];
-
- $handle = fopen($customDNSFile, "r");
- if ($handle)
- {
- while (($line = fgets($handle)) !== false) {
- $line = str_replace("\r","", $line);
- $line = str_replace("\n","", $line);
- $explodedLine = explode (" ", $line);
-
- if (count($explodedLine) != 2)
- continue;
-
- $data = new \stdClass();
- $data->ip = $explodedLine[0];
- $data->domain = $explodedLine[1];
- $entries[] = $data;
- }
-
- fclose($handle);
- }
-
- return $entries;
- }
-
- function addCustomDNSEntry()
- {
- try
- {
- $ip = !empty($_REQUEST['ip']) ? $_REQUEST['ip']: "";
- $domain = !empty($_REQUEST['domain']) ? $_REQUEST['domain']: "";
-
- if (empty($ip))
- return errorJsonResponse("IP must be set");
-
- $ipType = get_ip_type($ip);
-
- if (!$ipType)
- return errorJsonResponse("IP must be valid");
-
- if (empty($domain))
- return errorJsonResponse("Domain must be set");
-
- if (!is_valid_domain_name($domain))
- return errorJsonResponse("Domain must be valid");
-
- $existingEntries = getCustomDNSEntries();
-
- foreach ($existingEntries as $entry)
- if ($entry->domain == $domain)
- if (get_ip_type($entry->ip) == $ipType)
- return errorJsonResponse("This domain already has a custom DNS entry for an IPv" . $ipType);
-
- pihole_execute("-a addcustomdns ".$ip." ".$domain);
-
- return successJsonResponse();
- }
- catch (\Exception $ex)
- {
- return errorJsonResponse($ex->getMessage());
- }
- }
-
- function deleteCustomDNSEntry()
- {
- try
- {
- $ip = !empty($_REQUEST['ip']) ? $_REQUEST['ip']: "";
- $domain = !empty($_REQUEST['domain']) ? $_REQUEST['domain']: "";
-
- if (empty($ip))
- return errorJsonResponse("IP must be set");
-
- if (empty($domain))
- return errorJsonResponse("Domain must be set");
-
- $existingEntries = getCustomDNSEntries();
-
- $found = false;
- foreach ($existingEntries as $entry)
- if ($entry->domain == $domain)
- if ($entry->ip == $ip) {
- $found = true;
- break;
- }
-
- if (!$found)
- return errorJsonResponse("This domain/ip association does not exist");
-
- pihole_execute("-a removecustomdns ".$ip." ".$domain);
-
- return successJsonResponse();
- }
- catch (\Exception $ex)
- {
- return errorJsonResponse($ex->getMessage());
- }
- }
-
- function successJsonResponse($message = "")
- {
- return [ "success" => true, "message" => $message ];
- }
-
- function errorJsonResponse($message = "")
- {
- return [ "success" => false, "message" => $message ];
- }
?>
diff --git a/scripts/pi-hole/php/func.php b/scripts/pi-hole/php/func.php
index 3d539a52..ebd100c0 100644
--- a/scripts/pi-hole/php/func.php
+++ b/scripts/pi-hole/php/func.php
@@ -81,4 +81,177 @@ function pihole_execute($argument_string, $error_on_failure = true) {
return $output;
}
+function echoCustomDNSEntries()
+{
+ $entries = getCustomDNSEntries();
+
+ $data = [];
+ foreach ($entries as $entry)
+ $data[] = [ $entry->domain, $entry->ip ];
+
+ return [ "data" => $data ];
+}
+
+function getCustomDNSEntries()
+{
+ global $customDNSFile;
+
+ $entries = [];
+
+ $handle = fopen($customDNSFile, "r");
+ if ($handle)
+ {
+ while (($line = fgets($handle)) !== false) {
+ $line = str_replace("\r","", $line);
+ $line = str_replace("\n","", $line);
+ $explodedLine = explode (" ", $line);
+
+ if (count($explodedLine) != 2)
+ continue;
+
+ $data = new \stdClass();
+ $data->ip = $explodedLine[0];
+ $data->domain = $explodedLine[1];
+ $entries[] = $data;
+ }
+
+ fclose($handle);
+ }
+
+ return $entries;
+}
+
+function addCustomDNSEntry($ip="", $domain="", $json_reply=true)
+{
+ function error($msg)
+ {
+ global $json_reply;
+ if($json_reply)
+ return errorJsonResponse($msg);
+ else {
+ echo $msg."
";
+ return false;
+ }
+ }
+
+ try
+ {
+ if(isset($_REQUEST['ip']))
+ $ip = $_REQUEST['ip'];
+
+ if(isset($_REQUEST['domain']))
+ $domain = $_REQUEST['domain'];
+
+ if (empty($ip))
+ return error("IP must be set");
+
+ $ipType = get_ip_type($ip);
+
+ if (!$ipType)
+ return error("IP must be valid");
+
+ if (empty($domain))
+ return error("Domain must be set");
+
+ if (!is_valid_domain_name($domain))
+ return error("Domain must be valid");
+
+ // Only check for duplicates if adding new records from the web UI (not through Teleporter)
+ if(isset($_REQUEST['ip']) || isset($_REQUEST['domain']))
+ {
+ $existingEntries = getCustomDNSEntries();
+ foreach ($existingEntries as $entry)
+ if ($entry->domain == $domain && get_ip_type($entry->ip) == $ipType)
+ return error("This domain already has a custom DNS entry for an IPv" . $ipType);
+ }
+
+ // Add record
+ pihole_execute("-a addcustomdns ".$ip." ".$domain);
+
+ return $json_reply ? successJsonResponse() : true;
+ }
+ catch (\Exception $ex)
+ {
+ return error($ex->getMessage());
+ }
+}
+
+function deleteCustomDNSEntry()
+{
+ try
+ {
+ $ip = !empty($_REQUEST['ip']) ? $_REQUEST['ip']: "";
+ $domain = !empty($_REQUEST['domain']) ? $_REQUEST['domain']: "";
+
+ if (empty($ip))
+ return errorJsonResponse("IP must be set");
+
+ if (empty($domain))
+ return errorJsonResponse("Domain must be set");
+
+ $existingEntries = getCustomDNSEntries();
+
+ $found = false;
+ foreach ($existingEntries as $entry)
+ if ($entry->domain == $domain)
+ if ($entry->ip == $ip) {
+ $found = true;
+ break;
+ }
+
+ if (!$found)
+ return errorJsonResponse("This domain/ip association does not exist");
+
+ pihole_execute("-a removecustomdns ".$ip." ".$domain);
+
+ return successJsonResponse();
+ }
+ catch (\Exception $ex)
+ {
+ return errorJsonResponse($ex->getMessage());
+ }
+}
+
+function deleteAllCustomDNSEntries()
+{
+ $handle = fopen($customDNSFile, "r");
+ if ($handle)
+ {
+ try
+ {
+ while (($line = fgets($handle)) !== false) {
+ $line = str_replace("\r","", $line);
+ $line = str_replace("\n","", $line);
+ $explodedLine = explode (" ", $line);
+
+ if (count($explodedLine) != 2)
+ continue;
+
+ $ip = $explodedLine[0];
+ $domain = $explodedLine[1];
+
+ pihole_execute("-a removecustomdns ".$ip." ".$domain);
+ }
+ }
+ catch (\Exception $ex)
+ {
+ return errorJsonResponse($ex->getMessage());
+ }
+
+ fclose($handle);
+ }
+
+ return successJsonResponse();
+}
+
+function successJsonResponse($message = "")
+{
+ return [ "success" => true, "message" => $message ];
+}
+
+function errorJsonResponse($message = "")
+{
+ return [ "success" => false, "message" => $message ];
+}
+
?>
diff --git a/scripts/pi-hole/php/teleporter.php b/scripts/pi-hole/php/teleporter.php
index e746e6c0..5c75580a 100644
--- a/scripts/pi-hole/php/teleporter.php
+++ b/scripts/pi-hole/php/teleporter.php
@@ -40,11 +40,11 @@ function archive_add_table($name, $table, $type=-1)
if($type > -1)
{
- $querystr = "SELECT * FROM $table WHERE type = $type;";
+ $querystr = "SELECT * FROM \"$table\" WHERE type = $type;";
}
else
{
- $querystr = "SELECT * FROM $table;";
+ $querystr = "SELECT * FROM \"$table\";";
}
$results = $db->query($querystr);
@@ -87,31 +87,58 @@ function archive_restore_table($file, $table, $flush=false)
// Flush table if requested, only flush each table once
if($flush && !in_array($table, $flushed_tables))
{
- $db->exec("DELETE FROM ".$table);
+ $db->exec("DELETE FROM \"".$table."\"");
array_push($flushed_tables, $table);
}
- // Prepare field name for domain/address depending on the table we restore to
+ // Prepare fields depending on the table we restore to
if($table === "adlist")
{
$sql = "INSERT OR IGNORE INTO adlist";
$sql .= " (id,address,enabled,date_added,comment)";
$sql .= " VALUES (:id,:address,:enabled,:date_added,:comment);";
- $field = "address";
}
elseif($table === "domain_audit")
{
$sql = "INSERT OR IGNORE INTO domain_audit";
$sql .= " (id,domain,date_added)";
$sql .= " VALUES (:id,:domain,:date_added);";
- $field = "domain";
}
elseif($table === "domainlist")
{
$sql = "INSERT OR IGNORE INTO domainlist";
$sql .= " (id,domain,enabled,date_added,comment,type)";
$sql .= " VALUES (:id,:domain,:enabled,:date_added,:comment,:type);";
- $field = "domain";
+ }
+ elseif($table === "group")
+ {
+ $sql = "INSERT OR IGNORE INTO \"group\"";
+ $sql .= " (id,name,date_added,description)";
+ $sql .= " VALUES (:id,:name,:date_added,:description);";
+ }
+ elseif($table === "client")
+ {
+ $sql = "INSERT OR IGNORE INTO client";
+ $sql .= " (id,ip,date_added,comment)";
+ $sql .= " VALUES (:id,:ip,:date_added,:comment);";
+ }
+ elseif($table === "domainlist_by_group")
+ {
+ $sql = "INSERT OR IGNORE INTO domainlist_by_group";
+ $sql .= " (domainlist_id,group_id)";
+ $sql .= " VALUES (:domainlist_id,:group_id);";
+ }
+ elseif($table === "client_by_group")
+ {
+ $sql = "INSERT OR IGNORE INTO client_by_group";
+ $sql .= " (client_id,group_id)";
+ $sql .= " VALUES (:client_id,:group_id);";
+ }
+ elseif($table === "adlist_by_group")
+ {
+ $sql = "INSERT OR IGNORE INTO adlist_by_group";
+ $sql .= " (adlist_id,group_id)";
+ $sql .= " VALUES (:adlist_id,:group_id);";
}
else
{
@@ -149,23 +176,27 @@ function archive_restore_table($file, $table, $flush=false)
if(strlen($row[$field]) > 253)
continue;
- $stmt->bindValue(":id", $row["id"], SQLITE3_INTEGER);
- $stmt->bindValue(":date_added", $row["date_added"], SQLITE3_INTEGER);
- $stmt->bindValue(":".$field, $row[$field], SQLITE3_TEXT);
-
- if($table !== "domain_audit")
- {
- $stmt->bindValue(":enabled", $row["enabled"], SQLITE3_INTEGER);
- if(is_null($row["comment"]))
- $type = SQLITE3_NULL;
- else
- $type = SQLITE3_TEXT;
- $stmt->bindValue(":comment", $row["comment"], $type);
- }
-
- if($table === "domainlist")
- {
- $stmt->bindValue(":type", $row["type"], SQLITE3_INTEGER);
+ // Bind properties from JSON data
+ // Note that only defined above are actually used
+ // so even maliciously modified Teleporter files
+ // cannot be dangerous in any way
+ foreach($row as $key => $value) {
+ $type = gettype($value);
+ $sqltype=NULL;
+ switch($type) {
+ case "integer":
+ $sqltype = SQLITE3_INTEGER;
+ break;
+ case "string":
+ $sqltype = SQLITE3_TEXT;
+ break;
+ case "NULL":
+ $sqltype = SQLITE3_NULL;
+ break;
+ default:
+ $sqltype = "UNK";
+ }
+ $stmt->bindValue(":".$key, $value, $sqltype);
}
if($stmt->execute() && $stmt->reset() && $stmt->clear())
@@ -248,10 +279,10 @@ function flush_table($table, $type=null)
if(!in_array($table, $flushed_tables))
{
if($type !== null) {
- $sql = "DELETE FROM ".$table." WHERE type = ".$type;
+ $sql = "DELETE FROM \"".$table."\" WHERE type = ".$type;
array_push($flushed_tables, $table.$type);
} else {
- $sql = "DELETE FROM ".$table;
+ $sql = "DELETE FROM \"".$table."\"";
array_push($flushed_tables, $table);
}
$db->exec($sql);
@@ -411,6 +442,43 @@ if(isset($_POST["action"]))
$importedsomething = true;
}
+ if(isset($_POST["group"]) && $file->getFilename() === "group.json")
+ {
+ $num = archive_restore_table($file, "group", $flushtables);
+ echo "Processed group (".$num." entries)
\n";
+ $importedsomething = true;
+ }
+
+ if(isset($_POST["client"]) && $file->getFilename() === "client.json")
+ {
+ $num = archive_restore_table($file, "client", $flushtables);
+ echo "Processed client (".$num." entries)
\n";
+ $importedsomething = true;
+ }
+
+ if(isset($_POST["client"]) && $file->getFilename() === "client_by_group.json")
+ {
+ $num = archive_restore_table($file, "client_by_group", $flushtables);
+ echo "Processed client group assignments (".$num." entries)
\n";
+ $importedsomething = true;
+ }
+
+ if((isset($_POST["whitelist"]) || isset($_POST["regex_whitelist"]) ||
+ isset($_POST["blacklist"]) || isset($_POST["regex_blacklist"])) &&
+ $file->getFilename() === "domainlist_by_group.json")
+ {
+ $num = archive_restore_table($file, "domainlist_by_group", $flushtables);
+ echo "Processed black-/whitelist group assignments (".$num." entries)
\n";
+ $importedsomething = true;
+ }
+
+ if(isset($_POST["adlist"]) && $file->getFilename() === "adlist_by_group.json")
+ {
+ $num = archive_restore_table($file, "adlist_by_group", $flushtables);
+ echo "Processed adlist group assignments (".$num." entries)
\n";
+ $importedsomething = true;
+ }
+
if(isset($_POST["staticdhcpleases"]) && $file->getFilename() === "04-pihole-static-dhcp.conf")
{
if($flushtables) {
@@ -435,6 +503,26 @@ if(isset($_POST["action"]))
$importedsomething = true;
}
}
+
+ if(isset($_POST["localdnsrecords"]) && $file->getFilename() === "custom.list")
+ {
+ if($flushtables) {
+ // Defined in func.php included via auth.php
+ deleteAllCustomDNSEntries();
+ }
+ $num = 0;
+ $localdnsrecords = process_file(file_get_contents($file));
+ foreach($localdnsrecords as $record) {
+ list($ip,$domain) = explode(" ",$record);
+ if(addCustomDNSEntry($ip, $domain, false))
+ $num++;
+ }
+
+ echo "Processed local DNS records (".$num." entries)
\n";
+ if($num > 0) {
+ $importedsomething = true;
+ }
+ }
}
if($importedsomething)
@@ -467,8 +555,18 @@ else
archive_add_table("blacklist.regex.json", "domainlist", ListType::regex_blacklist);
archive_add_table("adlist.json", "adlist");
archive_add_table("domain_audit.json", "domain_audit");
+ archive_add_table("group.json", "group");
+ archive_add_table("client.json", "client");
+
+ // Group linking tables
+ archive_add_table("domainlist_by_group.json", "domainlist_by_group");
+ archive_add_table("adlist_by_group.json", "adlist_by_group");
+ archive_add_table("client_by_group.json", "client_by_group");
+
archive_add_file("/etc/pihole/","setupVars.conf");
archive_add_file("/etc/pihole/","dhcp.leases");
+ archive_add_file("/etc/pihole/","custom.list");
+ archive_add_file("/etc/pihole/","pihole-FTL.conf");
archive_add_file("/etc/","hosts","etc/");
archive_add_directory("/etc/dnsmasq.d/","dnsmasq.d/");
diff --git a/settings.php b/settings.php
index a6e69056..bbf45513 100644
--- a/settings.php
+++ b/settings.php
@@ -1073,13 +1073,13 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "blocklists"
Export your Pi-hole configuration (settings & lists) as a downloadable archive
- +Backup your Pi-hole configuration (settings & lists) as a downloadable archive
+Upload only Pi-hole backup files.