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"
-

Teleporter Export

+

Backup

-

Export your Pi-hole configuration (settings & lists) as a downloadable archive

- +

Backup your Pi-hole configuration (settings & lists) as a downloadable archive

+
@@ -1088,12 +1088,16 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "blocklists"
-

Teleporter Import

+

Restore

-
- +
+ +
+
+
+
+
+
+
+
+
+
+ +
+
+
-
+
+ +
+
-
+
+
+

Upload only Pi-hole backup files.