Merge pull request #1276 from pi-hole/fix/teleporter

Improvements for Teleporter
This commit is contained in:
DL6ER
2020-05-12 21:47:44 +02:00
committed by GitHub
4 changed files with 337 additions and 165 deletions
+3 -127
View File
@@ -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 ];
}
?>
+173
View File
@@ -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."<br>";
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 ];
}
?>
+124 -26
View File
@@ -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)<br>\n";
$importedsomething = true;
}
if(isset($_POST["client"]) && $file->getFilename() === "client.json")
{
$num = archive_restore_table($file, "client", $flushtables);
echo "Processed client (".$num." entries)<br>\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)<br>\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)<br>\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)<br>\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)<br>\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/");
+37 -12
View File
@@ -1073,13 +1073,13 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "blocklists"
<div class="col-lg-6 col-md-12">
<div class="box box-warning">
<div class="box-header with-border">
<h3 class="box-title">Teleporter Export</h3>
<h3 class="box-title">Backup</h3>
</div>
<div class="box-body">
<div class="row">
<div class="col-lg-12">
<p>Export your Pi-hole configuration (settings &amp; lists) as a downloadable archive</p>
<button type="submit" class="btn btn-default">Export</button>
<p>Backup your Pi-hole configuration (settings &amp; lists) as a downloadable archive</p>
<button type="submit" class="btn btn-default">Backup</button>
</div>
</div>
</div>
@@ -1088,12 +1088,16 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "blocklists"
<div class="col-lg-6 col-md-12">
<div class="box box-warning">
<div class="box-header with-border">
<h3 class="box-title">Teleporter Import</h3>
<h3 class="box-title">Restore</h3>
</div>
<div class="box-body">
<div class="row">
<div class="col-lg-6 col-md-12">
<label>Import...</label>
<div class="col-lg-12">
<label>Restore...</label>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<div class="form-group">
<div class="checkbox">
<label><input type="checkbox" name="whitelist" value="true"
@@ -1118,27 +1122,48 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "blocklists"
<div class="checkbox">
<label><input type="checkbox" name="adlist" value="true"
checked>
Blocklists</label>
Adlists</label>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="form-group">
<div class="checkbox">
<label><input type="checkbox" name="client" value="true"
checked>
Clients</label>
</div>
<div class="checkbox">
<label><input type="checkbox" name="group" value="true"
checked>
Groups</label>
</div>
<div class="checkbox">
<label><input type="checkbox" name="auditlog" value="true"
checked>
checked>
Audit log</label>
</div>
<div class="checkbox">
<label><input type="checkbox" name="staticdhcpleases" value="true"
checked>
checked>
Static DHCP Leases</label>
</div>
</div>
<div class="checkbox">
<label><input type="checkbox" name="localdnsrecords" value="true"
checked>
Local DNS Records</label>
</div>
</div>
</div>
<div class="col-lg-6 col-md-12">
</div>
<div class="row">
<div class="col-lg-12">
<div class="form-group">
<label for="zip_file">File input</label>
<input type="file" name="zip_file" id="zip_file">
<p class="help-block">Upload only Pi-hole backup files.</p>
<button type="submit" class="btn btn-default" name="action"
value="in">Import
value="in">Restore
</button>
<div class="checkbox">
<label><input type="checkbox" name="flushtables" value="true"