mirror of
https://github.com/pi-hole/web.git
synced 2026-02-15 07:25:39 +00:00
Merge branch 'devel' into release/v4.3.2
This commit is contained in:
44
api_db.php
44
api_db.php
@@ -8,6 +8,7 @@
|
||||
|
||||
$api = true;
|
||||
header('Content-type: application/json');
|
||||
require("scripts/pi-hole/php/database.php");
|
||||
require("scripts/pi-hole/php/password.php");
|
||||
require("scripts/pi-hole/php/auth.php");
|
||||
check_cors();
|
||||
@@ -48,7 +49,7 @@ function resolveHostname($clientip, $printIP)
|
||||
return $clientname;
|
||||
}
|
||||
|
||||
// Get posible non-standard location of FTL's database
|
||||
// Get possible non-standard location of FTL's database
|
||||
$FTLsettings = parse_ini_file("/etc/pihole/pihole-FTL.conf");
|
||||
if(isset($FTLsettings["DBFILE"]))
|
||||
{
|
||||
@@ -62,37 +63,7 @@ else
|
||||
// Needs package php5-sqlite, e.g.
|
||||
// sudo apt-get install php5-sqlite
|
||||
|
||||
function SQLite3_connect($trytoreconnect)
|
||||
{
|
||||
global $DBFILE;
|
||||
try
|
||||
{
|
||||
// connect to database
|
||||
return new SQLite3($DBFILE, SQLITE3_OPEN_READONLY);
|
||||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
// sqlite3 throws an exception when it is unable to connect, try to reconnect after 3 seconds
|
||||
if($trytoreconnect)
|
||||
{
|
||||
sleep(3);
|
||||
$db = SQLite3_connect(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(strlen($DBFILE) > 0)
|
||||
{
|
||||
$db = SQLite3_connect(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
die("No database available");
|
||||
}
|
||||
if(!$db)
|
||||
{
|
||||
die("Error connecting to database");
|
||||
}
|
||||
$db = SQLite3_connect($DBFILE);
|
||||
|
||||
if(isset($_GET["network"]) && $auth)
|
||||
{
|
||||
@@ -100,7 +71,16 @@ if(isset($_GET["network"]) && $auth)
|
||||
$results = $db->query('SELECT * FROM network');
|
||||
|
||||
while($results !== false && $res = $results->fetchArray(SQLITE3_ASSOC))
|
||||
{
|
||||
$id = $res["id"];
|
||||
// Empty array for holding the IP addresses
|
||||
$res["ip"] = array();
|
||||
// Get IP addresses for this device
|
||||
$network_addresses = $db->query("SELECT ip FROM network_addresses WHERE network_id = $id ORDER BY lastSeen DESC");
|
||||
while($network_addresses !== false && $ip = $network_addresses->fetchArray(SQLITE3_ASSOC))
|
||||
array_push($res["ip"],$ip["ip"]);
|
||||
array_push($network, $res);
|
||||
}
|
||||
|
||||
$data = array_merge($data, array('network' => $network));
|
||||
}
|
||||
|
||||
14
list.php
14
list.php
@@ -35,13 +35,9 @@ function getFullName() {
|
||||
<div class="form-group input-group">
|
||||
<input id="domain" type="text" class="form-control" placeholder="Add a domain (example.com or sub.example.com)">
|
||||
<span class="input-group-btn">
|
||||
<?php if($list === "black") { ?>
|
||||
<button id="btnAdd" class="btn btn-default" type="button">Add (exact)</button>
|
||||
<button id="btnAddWildcard" class="btn btn-default" type="button">Add (wildcard)</button>
|
||||
<button id="btnAddRegex" class="btn btn-default" type="button">Add (regex)</button>
|
||||
<?php }else{ ?>
|
||||
<button id="btnAdd" class="btn btn-default" type="button">Add</button>
|
||||
<?php } ?>
|
||||
<button id="btnRefresh" class="btn btn-default" type="button"><i class="fa fa-sync"></i></button>
|
||||
</span>
|
||||
</div>
|
||||
@@ -53,7 +49,7 @@ function getFullName() {
|
||||
</div>
|
||||
<div id="alSuccess" class="alert alert-success alert-dismissible fade in" role="alert" hidden="true">
|
||||
<button type="button" class="close" data-hide="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
Success! The list will refresh.
|
||||
<span id="success-message"></span>
|
||||
</div>
|
||||
<div id="alFailure" class="alert alert-danger alert-dismissible fade in" role="alert" hidden="true">
|
||||
<button type="button" class="close" data-hide="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
@@ -66,14 +62,10 @@ function getFullName() {
|
||||
|
||||
|
||||
<!-- Domain List -->
|
||||
<?php if($list === "black") { ?>
|
||||
<h3>Exact blocking</h3>
|
||||
<?php } ?>
|
||||
<h3 id="h3-exact" hidden="true">Exact blocking</h3>
|
||||
<ul class="list-group" id="list"></ul>
|
||||
<?php if($list === "black") { ?>
|
||||
<h3><a href="https://docs.pi-hole.net/ftldns/regex/overview/" target="_blank" title="Click for Pi-hole Regex documentation">Regex</a> & Wildcard blocking</h3>
|
||||
<h3 id="h3-regex" hidden="true"><a href="https://docs.pi-hole.net/ftldns/regex/overview/" target="_blank" title="Click for Pi-hole Regex documentation">Regex</a> & Wildcard blocking</h3>
|
||||
<ul class="list-group" id="list-regex"></ul>
|
||||
<?php } ?>
|
||||
|
||||
<script src="scripts/pi-hole/js/list.js"></script>
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ function add(domain,list) {
|
||||
$.ajax({
|
||||
url: "scripts/pi-hole/php/add.php",
|
||||
method: "post",
|
||||
data: {"domain":domain, "list":list, "token":token, "auditlog":1}
|
||||
data: {"domain":domain, "list":list, "token":token}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -91,6 +91,7 @@ $(document).ready(function() {
|
||||
var url = ($(this).parents("tr"))[0].innerText.split(" ")[0];
|
||||
if($(this).context.innerText === " Blacklist")
|
||||
{
|
||||
add(url,"audit");
|
||||
add(url,"black");
|
||||
$("#gravityBtn").prop("disabled", false);
|
||||
}
|
||||
@@ -104,6 +105,7 @@ $(document).ready(function() {
|
||||
var url = ($(this).parents("tr"))[0].innerText.split(" ")[0].split(" ")[0];
|
||||
if($(this).context.innerText === " Whitelist")
|
||||
{
|
||||
add(url,"audit");
|
||||
add(url,"white");
|
||||
$("#gravityBtn").prop("disabled", false);
|
||||
}
|
||||
|
||||
@@ -12,45 +12,42 @@ var token = $("#token").html();
|
||||
var listType = $("#list-type").html();
|
||||
var fullName = listType === "white" ? "Whitelist" : "Blacklist";
|
||||
|
||||
function sub(index, entry, arg) {
|
||||
var domain = $("#list #"+index);
|
||||
var locallistType = listType;
|
||||
if(arg === "regex")
|
||||
{
|
||||
locallistType = "regex";
|
||||
domain = $("#list-regex #"+index);
|
||||
}
|
||||
domain.hide("highlight");
|
||||
$.ajax({
|
||||
url: "scripts/pi-hole/php/sub.php",
|
||||
method: "post",
|
||||
data: {"domain":entry, "list":locallistType, "token":token},
|
||||
success: function(response) {
|
||||
if(response.length !== 0){
|
||||
return;
|
||||
}
|
||||
domain.remove();
|
||||
},
|
||||
error: function(jqXHR, exception) {
|
||||
alert("Failed to remove the domain!");
|
||||
domain.show({queue:true});
|
||||
}
|
||||
function addListEntry(entry, index, list, button, type)
|
||||
{
|
||||
var disabled = [];
|
||||
if(entry.enabled === "0")
|
||||
disabled.push("individual");
|
||||
// For entry.group_enabled we either get "0" (= disabled by a group),
|
||||
// "1" (= enabled by a group), or "" (= not managed by a group)
|
||||
if(entry.group_enabled === "0")
|
||||
disabled.push("group");
|
||||
|
||||
var used = disabled.length === 0 ? "used" : "not-used";
|
||||
var comment = entry.comment.length > 0 ? " - " + entry.comment : "";
|
||||
var disabled_message = disabled.length > 0 ? " - disabled due to " + disabled.join(" + ") + " setting" : "";
|
||||
var date_added = new Date(parseInt(entry.date_added)*1000);
|
||||
var date_modified = new Date(parseInt(entry.date_modified)*1000);
|
||||
var tooltip = "Added: " + date_added.toLocaleString() +
|
||||
"\nModified: " + date_modified.toLocaleString();
|
||||
list.append(
|
||||
"<li id=\"" + index + "\" class=\"list-group-item " + used + " clearfix\">" +
|
||||
"<span title=\"" + tooltip + "\" data-toggle=\"tooltip\" data-placement=\"right\">" +
|
||||
entry.domain + comment + disabled_message + "</span>" +
|
||||
"<button class=\"btn btn-danger btn-xs pull-right\" type=\"button\">" +
|
||||
"<span class=\"glyphicon glyphicon-trash\"></span></button></li>"
|
||||
);
|
||||
// Handle button
|
||||
$(button+" #"+index).on("click", "button", function() {
|
||||
sub(index, entry.domain, type);
|
||||
});
|
||||
}
|
||||
|
||||
function refresh(fade) {
|
||||
var listw;
|
||||
var list = $("#list");
|
||||
if(listType === "black")
|
||||
{
|
||||
listw = $("#list-regex");
|
||||
}
|
||||
var listw = $("#list-regex");
|
||||
if(fade) {
|
||||
list.fadeOut(100);
|
||||
if(listw)
|
||||
{
|
||||
listw.fadeOut(100);
|
||||
}
|
||||
listw.fadeOut(100);
|
||||
}
|
||||
$.ajax({
|
||||
url: "scripts/pi-hole/php/get.php",
|
||||
@@ -58,63 +55,51 @@ function refresh(fade) {
|
||||
data: {"list":listType},
|
||||
success: function(response) {
|
||||
list.html("");
|
||||
if(listw)
|
||||
{
|
||||
listw.html("");
|
||||
}
|
||||
var data = JSON.parse(response);
|
||||
listw.html("");
|
||||
|
||||
if(data.length === 0) {
|
||||
if((listType === "black" &&
|
||||
response.blacklist.length === 0 &&
|
||||
response.regex_blacklist.length === 0) ||
|
||||
(listType === "white" &&
|
||||
response.whitelist.length === 0 &&
|
||||
response.regex_whitelist.length === 0))
|
||||
{
|
||||
$("h3").hide();
|
||||
if(listw)
|
||||
{
|
||||
listw.html("<div class=\"alert alert-info\" role=\"alert\">Your " + fullName + " is empty!</div>");
|
||||
}
|
||||
else
|
||||
{
|
||||
list.html("<div class=\"alert alert-info\" role=\"alert\">Your " + fullName + " is empty!</div>");
|
||||
}
|
||||
list.html("<div class=\"alert alert-info\" role=\"alert\">Your " + fullName + " is empty!</div>");
|
||||
}
|
||||
else
|
||||
{
|
||||
$("h3").show();
|
||||
data[0] = data[0].sort();
|
||||
data[0].forEach(function (entry, index) {
|
||||
// Whitelist entry or Blacklist (exact entry) are in the zero-th
|
||||
// array returned by get.php
|
||||
list.append(
|
||||
"<li id=\"" + index + "\" class=\"list-group-item clearfix\">" + entry +
|
||||
"<button class=\"btn btn-danger btn-xs pull-right\" type=\"button\">" +
|
||||
"<span class=\"glyphicon glyphicon-trash\"></span></button></li>");
|
||||
// Handle button
|
||||
$("#list #"+index+"").on("click", "button", function() {
|
||||
sub(index, entry, "exact");
|
||||
});
|
||||
});
|
||||
|
||||
// Add regex domains if present in returned list data
|
||||
if(data.length === 2)
|
||||
if(listType === "white")
|
||||
{
|
||||
data[1] = data[1].sort();
|
||||
data[1].forEach(function (entry, index) {
|
||||
// Whitelist entry or Blacklist (exact entry) are in the zero-th
|
||||
// array returned by get.php
|
||||
listw.append(
|
||||
"<li id=\"" + index + "\" class=\"list-group-item clearfix\">" + entry +
|
||||
"<button class=\"btn btn-danger btn-xs pull-right\" type=\"button\">" +
|
||||
"<span class=\"glyphicon glyphicon-trash\"></span></button></li>");
|
||||
// Handle button
|
||||
$("#list-regex #"+index+"").on("click", "button", function() {
|
||||
sub(index, entry, "regex");
|
||||
});
|
||||
});
|
||||
data = response.whitelist.sort();
|
||||
data2 = response.regex_whitelist.sort();
|
||||
}
|
||||
else if(listType === "black")
|
||||
{
|
||||
data = response.blacklist.sort();
|
||||
data2 = response.regex_blacklist.sort();
|
||||
}
|
||||
|
||||
if(data.length > 0)
|
||||
{
|
||||
$("#h3-exact").fadeIn(100);
|
||||
}
|
||||
if(data2.length > 0)
|
||||
{
|
||||
$("#h3-regex").fadeIn(100);
|
||||
}
|
||||
|
||||
data.forEach(function (entry, index)
|
||||
{
|
||||
addListEntry(entry, index, list, "#list", "exact");
|
||||
});
|
||||
data2.forEach(function (entry, index)
|
||||
{
|
||||
addListEntry(entry, index, listw, "#list-regex", listType+"_regex");
|
||||
});
|
||||
}
|
||||
list.fadeIn(100);
|
||||
if(listw)
|
||||
{
|
||||
listw.fadeIn(100);
|
||||
}
|
||||
listw.fadeIn(100);
|
||||
},
|
||||
error: function(jqXHR, exception) {
|
||||
$("#alFailure").show();
|
||||
@@ -124,18 +109,69 @@ function refresh(fade) {
|
||||
|
||||
window.onload = refresh(false);
|
||||
|
||||
function add(arg) {
|
||||
function sub(index, entry, arg) {
|
||||
var list = "#list";
|
||||
var heading = "#h3-exact";
|
||||
var locallistType = listType;
|
||||
if(arg === "black_regex" || arg === "white_regex")
|
||||
{
|
||||
list = "#list-regex";
|
||||
heading = "#h3-regex";
|
||||
locallistType = arg;
|
||||
}
|
||||
var alInfo = $("#alInfo");
|
||||
var alSuccess = $("#alSuccess");
|
||||
var alFailure = $("#alFailure");
|
||||
var alWarning = $("#alWarning");
|
||||
var err = $("#err");
|
||||
var warn = $("#warn");
|
||||
var msg = $("#success-message");
|
||||
|
||||
|
||||
var domain = $(list+" #"+index);
|
||||
domain.hide("highlight");
|
||||
$.ajax({
|
||||
url: "scripts/pi-hole/php/sub.php",
|
||||
method: "post",
|
||||
data: {"domain":entry, "list":locallistType, "token":token},
|
||||
success: function(response) {
|
||||
if (response.indexOf("Success") === -1) {
|
||||
alFailure.show();
|
||||
err.html(response);
|
||||
alFailure.delay(8000).fadeOut(2000, function() {
|
||||
alFailure.hide();
|
||||
});
|
||||
alInfo.delay(8000).fadeOut(2000, function() {
|
||||
alInfo.hide();
|
||||
});
|
||||
} else {
|
||||
alSuccess.show();
|
||||
msg.html(response);
|
||||
alSuccess.delay(1000).fadeOut(2000, function() {
|
||||
alSuccess.hide();
|
||||
});
|
||||
alInfo.delay(1000).fadeOut(2000, function() {
|
||||
alInfo.hide();
|
||||
});
|
||||
domain.remove();
|
||||
if($(list+" li").length < 1)
|
||||
{
|
||||
$(heading).fadeOut(100);
|
||||
}
|
||||
}
|
||||
},
|
||||
error: function(jqXHR, exception) {
|
||||
alert("Failed to remove the domain!");
|
||||
domain.show({queue:true});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function add(type) {
|
||||
var domain = $("#domain");
|
||||
var wild = false;
|
||||
if(domain.val().length === 0){
|
||||
return;
|
||||
}
|
||||
if(arg === "wild" || arg === "regex")
|
||||
{
|
||||
locallistType = arg;
|
||||
wild = true;
|
||||
}
|
||||
|
||||
var alInfo = $("#alInfo");
|
||||
var alSuccess = $("#alSuccess");
|
||||
@@ -143,6 +179,7 @@ function add(arg) {
|
||||
var alWarning = $("#alWarning");
|
||||
var err = $("#err");
|
||||
var warn = $("#warn");
|
||||
var msg = $("#success-message");
|
||||
alInfo.show();
|
||||
alSuccess.hide();
|
||||
alFailure.hide();
|
||||
@@ -150,38 +187,29 @@ function add(arg) {
|
||||
$.ajax({
|
||||
url: "scripts/pi-hole/php/add.php",
|
||||
method: "post",
|
||||
data: {"domain":domain.val().trim(), "list":locallistType, "token":token},
|
||||
data: {"domain":domain.val().trim(), "list":type, "token":token},
|
||||
success: function(response) {
|
||||
if (!wild && response.indexOf(" already exists in ") !== -1) {
|
||||
alWarning.show();
|
||||
warn.html(response);
|
||||
alWarning.delay(8000).fadeOut(2000, function() {
|
||||
alWarning.hide();
|
||||
});
|
||||
alInfo.delay(8000).fadeOut(2000, function() {
|
||||
alInfo.hide();
|
||||
});
|
||||
} else if (!wild && response.indexOf("] Pi-hole blocking is ") === -1 ||
|
||||
wild && response.length > 1) {
|
||||
alFailure.show();
|
||||
err.html(response);
|
||||
alFailure.delay(8000).fadeOut(2000, function() {
|
||||
alFailure.hide();
|
||||
});
|
||||
alInfo.delay(8000).fadeOut(2000, function() {
|
||||
alInfo.hide();
|
||||
});
|
||||
} else {
|
||||
alSuccess.show();
|
||||
alSuccess.delay(1000).fadeOut(2000, function() {
|
||||
alSuccess.hide();
|
||||
});
|
||||
alInfo.delay(1000).fadeOut(2000, function() {
|
||||
alInfo.hide();
|
||||
});
|
||||
domain.val("");
|
||||
refresh(true);
|
||||
}
|
||||
if (response.indexOf("Success") === -1) {
|
||||
alFailure.show();
|
||||
err.html(response);
|
||||
alFailure.delay(8000).fadeOut(2000, function() {
|
||||
alFailure.hide();
|
||||
});
|
||||
alInfo.delay(8000).fadeOut(2000, function() {
|
||||
alInfo.hide();
|
||||
});
|
||||
} else {
|
||||
alSuccess.show();
|
||||
msg.html(response);
|
||||
alSuccess.delay(1000).fadeOut(2000, function() {
|
||||
alSuccess.hide();
|
||||
});
|
||||
alInfo.delay(1000).fadeOut(2000, function() {
|
||||
alInfo.hide();
|
||||
});
|
||||
domain.val("");
|
||||
refresh(true);
|
||||
}
|
||||
},
|
||||
error: function(jqXHR, exception) {
|
||||
alFailure.show();
|
||||
@@ -202,21 +230,21 @@ function add(arg) {
|
||||
$(document).keypress(function(e) {
|
||||
if(e.which === 13 && $("#domain").is(":focus")) {
|
||||
// Enter was pressed, and the input has focus
|
||||
add("exact");
|
||||
add(listType);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle buttons
|
||||
$("#btnAdd").on("click", function() {
|
||||
add("exact");
|
||||
add(listType);
|
||||
});
|
||||
|
||||
$("#btnAddWildcard").on("click", function() {
|
||||
add("wild");
|
||||
add(listType+"_wild");
|
||||
});
|
||||
|
||||
$("#btnAddRegex").on("click", function() {
|
||||
add("regex");
|
||||
add(listType+"_regex");
|
||||
});
|
||||
|
||||
$("#btnRefresh").on("click", function() {
|
||||
|
||||
@@ -8,6 +8,9 @@ var tableApi;
|
||||
|
||||
var APIstring = "api_db.php?network";
|
||||
|
||||
// How many IPs do we show at most per device?
|
||||
var MAXIPDISPLAY = 3;
|
||||
|
||||
function refreshData() {
|
||||
tableApi.ajax.url(APIstring).load();
|
||||
}
|
||||
@@ -113,12 +116,15 @@ $(document).ready(function() {
|
||||
// Set number of queries to localized string (add thousand separators)
|
||||
$("td:eq(6)", row).html(data["numQueries"].toLocaleString());
|
||||
|
||||
// Client -> jump to Query Log on click
|
||||
$("td:eq(0)", row).click( function () { openInNewTab("/admin/queries.php?client="+this.innerHTML); } );
|
||||
$("td:eq(0)", row).css("cursor","pointer");
|
||||
$("td:eq(0)", row).hover(
|
||||
function () { this.title="Click to show recent queries made by "+this.innerHTML; this.style.color="#72afd2"; },
|
||||
function () { this.style.color=""; } );
|
||||
var ips = data["ip"];
|
||||
var shortips = ips;
|
||||
if(ips.length > MAXIPDISPLAY)
|
||||
{
|
||||
shortips = ips.slice(0,MAXIPDISPLAY-1);
|
||||
shortips.push("...");
|
||||
}
|
||||
$("td:eq(0)", row).html(shortips.join("<br>"));
|
||||
$("td:eq(0)", row).hover(function () { this.title=ips.join("\n");});
|
||||
|
||||
// MAC + Vendor field if available
|
||||
if(data["macVendor"] && data["macVendor"].length > 0)
|
||||
|
||||
@@ -82,19 +82,19 @@ $(".confirm-flushlogs").confirm({
|
||||
dialogClass: "modal-dialog modal-mg"
|
||||
});
|
||||
|
||||
$(".confirm-disablelogging").confirm({
|
||||
text: "Are you sure you want to disable logging and flush your Pi-hole logs?",
|
||||
$(".confirm-flusharp").confirm({
|
||||
text: "Are you sure you want to flush your network table?",
|
||||
title: "Confirmation required",
|
||||
confirm(button) {
|
||||
$("#disablelogsform").submit();
|
||||
$("#flusharpform").submit();
|
||||
},
|
||||
cancel(button) {
|
||||
// nothing to do
|
||||
},
|
||||
confirmButton: "Yes, disable logs and flush my logs",
|
||||
confirmButton: "Yes, flush my network table",
|
||||
cancelButton: "No, go back",
|
||||
post: true,
|
||||
confirmButtonClass: "btn-danger",
|
||||
confirmButtonClass: "btn-warning",
|
||||
cancelButtonClass: "btn-success",
|
||||
dialogClass: "modal-dialog modal-mg"
|
||||
});
|
||||
|
||||
@@ -8,51 +8,62 @@
|
||||
|
||||
require_once('auth.php');
|
||||
|
||||
$type = $_POST['list'];
|
||||
$list = $_POST['list'];
|
||||
|
||||
// Perform all of the authentication for list editing
|
||||
// when NOT invoked and authenticated from API
|
||||
if (empty($api)) {
|
||||
list_verify($type);
|
||||
list_verify($list);
|
||||
}
|
||||
|
||||
// Don't check if the added item is a valid domain for regex expressions. Regex
|
||||
// filters are validated by FTL on import and skipped if invalid
|
||||
if($type !== "regex") {
|
||||
// Only check domains we add to the exact lists.
|
||||
// Regex are validated by FTL during import
|
||||
$check_lists = ["white","black","audit"];
|
||||
if(in_array($list, $check_lists)) {
|
||||
check_domain();
|
||||
}
|
||||
|
||||
switch($type) {
|
||||
case "white":
|
||||
if(!isset($_POST["auditlog"]))
|
||||
echo shell_exec("sudo pihole -w ${_POST['domain']}");
|
||||
else
|
||||
{
|
||||
echo shell_exec("sudo pihole -w -n ${_POST['domain']}");
|
||||
echo shell_exec("sudo pihole -a audit ${_POST['domain']}");
|
||||
}
|
||||
break;
|
||||
case "black":
|
||||
if(!isset($_POST["auditlog"]))
|
||||
echo shell_exec("sudo pihole -b ${_POST['domain']}");
|
||||
else
|
||||
{
|
||||
echo shell_exec("sudo pihole -b -n ${_POST['domain']}");
|
||||
echo shell_exec("sudo pihole -a audit ${_POST['domain']}");
|
||||
}
|
||||
break;
|
||||
case "wild":
|
||||
// Escape "." so it won't be interpreted as the wildcard character
|
||||
$domain = str_replace(".","\.",$_POST['domain']);
|
||||
// Add regex filter for legacy wildcard behavior
|
||||
add_regex("(^|\.)".$domain."$");
|
||||
break;
|
||||
case "regex":
|
||||
add_regex($_POST['domain']);
|
||||
break;
|
||||
case "audit":
|
||||
echo exec("sudo pihole -a audit ${_POST['domain']}");
|
||||
break;
|
||||
// Split individual domains into array
|
||||
$domains = preg_split('/\s+/', trim($_POST['domain']));
|
||||
|
||||
require_once("func.php");
|
||||
require_once("database.php");
|
||||
$GRAVITYDB = getGravityDBFilename();
|
||||
$db = SQLite3_connect($GRAVITYDB, SQLITE3_OPEN_READWRITE);
|
||||
|
||||
switch($list) {
|
||||
case "white":
|
||||
echo add_to_table($db, "whitelist", $domains);
|
||||
break;
|
||||
|
||||
case "black":
|
||||
echo add_to_table($db, "blacklist", $domains);
|
||||
break;
|
||||
|
||||
case "black_regex":
|
||||
echo add_to_table($db, "regex_blacklist", $domains);
|
||||
break;
|
||||
|
||||
case "white_regex":
|
||||
echo add_to_table($db, "regex_whitelist", $domains);
|
||||
break;
|
||||
|
||||
case "black_wild":
|
||||
echo add_to_table($db, "regex_blacklist", $domains, true);
|
||||
break;
|
||||
|
||||
case "white_wild":
|
||||
echo add_to_table($db, "regex_whitelist", $domains, true);
|
||||
break;
|
||||
|
||||
case "audit":
|
||||
echo add_to_table($db, "domain_audit", $domains);
|
||||
break;
|
||||
|
||||
default:
|
||||
die("Invalid list!");
|
||||
}
|
||||
|
||||
// Reload lists in pihole-FTL after having added something
|
||||
echo shell_exec("sudo pihole restartdns reload");
|
||||
?>
|
||||
|
||||
@@ -11,7 +11,6 @@ $ERRORLOG = getenv('PHP_ERROR_LOG');
|
||||
if (empty($ERRORLOG)) {
|
||||
$ERRORLOG = '/var/log/lighttpd/error.log';
|
||||
}
|
||||
$regexfile = "/etc/pihole/regex.list";
|
||||
|
||||
function pi_log($message) {
|
||||
error_log(date('Y-m-d H:i:s') . ': ' . $message . "\n", 3, $GLOBALS['ERRORLOG']);
|
||||
|
||||
237
scripts/pi-hole/php/database.php
Normal file
237
scripts/pi-hole/php/database.php
Normal file
@@ -0,0 +1,237 @@
|
||||
<?php
|
||||
/* Pi-hole: A black hole for Internet advertisements
|
||||
* (c) 2017 Pi-hole, LLC (https://pi-hole.net)
|
||||
* Network-wide ad blocking via your own hardware.
|
||||
*
|
||||
* This file is copyright under the latest version of the EUPL.
|
||||
* Please see LICENSE file for your rights under this license */
|
||||
|
||||
function getGravityDBFilename()
|
||||
{
|
||||
// Get possible non-standard location of FTL's database
|
||||
$FTLsettings = parse_ini_file("/etc/pihole/pihole-FTL.conf");
|
||||
if(isset($FTLsettings["GRAVITYDB"]))
|
||||
{
|
||||
return $FTLsettings["GRAVITYDB"];
|
||||
}
|
||||
else
|
||||
{
|
||||
return "/etc/pihole/gravity.db";
|
||||
}
|
||||
}
|
||||
|
||||
function SQLite3_connect_try($filename, $mode, $trytoreconnect)
|
||||
{
|
||||
try
|
||||
{
|
||||
// connect to database
|
||||
return new SQLite3($filename, $mode);
|
||||
}
|
||||
catch (Exception $exception)
|
||||
{
|
||||
// sqlite3 throws an exception when it is unable to connect, try to reconnect after 3 seconds
|
||||
if($trytoreconnect)
|
||||
{
|
||||
sleep(3);
|
||||
return SQLite3_connect_try($filename, $mode, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we should not try again (or are already trying again!), we return the exception string
|
||||
// so the user gets it on the dashboard
|
||||
return $filename.": ".$exception->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function SQLite3_connect($filename, $mode=SQLITE3_OPEN_READONLY)
|
||||
{
|
||||
if(strlen($filename) > 0)
|
||||
{
|
||||
$db = SQLite3_connect_try($filename, $mode, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
die("No database available");
|
||||
}
|
||||
if(is_string($db))
|
||||
{
|
||||
die("Error connecting to database\n".$db);
|
||||
}
|
||||
|
||||
// Add busy timeout so methods don't fail immediately when, e.g., FTL is currently reading from the DB
|
||||
$db->busyTimeout(5000);
|
||||
|
||||
return $db;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add domains to a given table
|
||||
*
|
||||
* @param $db object The SQLite3 database connection object
|
||||
* @param $table string The target table
|
||||
* @param $domains array Array of domains (strings) to be added to the table
|
||||
* @param $wildcardstyle boolean Whether to format the input domains in legacy wildcard notation
|
||||
* @param $returnnum boolean Whether to return an integer or a string
|
||||
* @return string Success/error and number of processed domains
|
||||
*/
|
||||
function add_to_table($db, $table, $domains, $wildcardstyle=false, $returnnum=false)
|
||||
{
|
||||
// Begin transaction
|
||||
if(!$db->exec("BEGIN TRANSACTION;"))
|
||||
{
|
||||
if($returnnum)
|
||||
return 0;
|
||||
else
|
||||
return "Error: Unable to begin transaction for ".$table." table.";
|
||||
}
|
||||
$initialcount = intval($db->querySingle("SELECT COUNT(*) FROM ".$table.";"));
|
||||
|
||||
// Prepare SQLite statememt
|
||||
$stmt = $db->prepare("INSERT OR IGNORE INTO ".$table." (domain) VALUES (:domain);");
|
||||
|
||||
// Return early if we failed to prepare the SQLite statement
|
||||
if(!$stmt)
|
||||
{
|
||||
if($returnnum)
|
||||
return 0;
|
||||
else
|
||||
return "Error: Failed to prepare statement for ".$table." table.";
|
||||
}
|
||||
|
||||
// Loop over domains and inject the lines into the database
|
||||
$num = 0;
|
||||
foreach($domains as $domain)
|
||||
{
|
||||
// Limit max length for a domain entry to 253 chars
|
||||
if(strlen($domain) > 253)
|
||||
continue;
|
||||
|
||||
if($wildcardstyle)
|
||||
$domain = "(\\.|^)".str_replace(".","\\.",$domain)."$";
|
||||
|
||||
$stmt->bindValue(":domain", $domain, SQLITE3_TEXT);
|
||||
|
||||
if($stmt->execute() && $stmt->reset())
|
||||
$num++;
|
||||
else
|
||||
{
|
||||
$stmt->close();
|
||||
if($returnnum)
|
||||
return $num;
|
||||
else
|
||||
{
|
||||
if($num === 1)
|
||||
$plural = "";
|
||||
else
|
||||
$plural = "s";
|
||||
return "Error: ".$db->lastErrorMsg().", added ".$num." domain".$plural;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close prepared statement and return number of processed rows
|
||||
$stmt->close();
|
||||
$db->exec("COMMIT;");
|
||||
|
||||
if($returnnum)
|
||||
return $num;
|
||||
else
|
||||
{
|
||||
$finalcount = intval($db->querySingle("SELECT COUNT(*) FROM ".$table.";"));
|
||||
$modified = $finalcount - $initialcount;
|
||||
|
||||
// If we add less domains than the user specified, then they wanted to add duplicates
|
||||
if($modified !== $num)
|
||||
{
|
||||
$delta = $num - $modified;
|
||||
$extra = " (skipped ".$delta." duplicates)";
|
||||
}
|
||||
else
|
||||
{
|
||||
$extra = "";
|
||||
}
|
||||
|
||||
if($num === 1)
|
||||
$plural = "";
|
||||
else
|
||||
$plural = "s";
|
||||
return "Success, added ".$modified." of ".$num." domain".$plural.$extra;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove domains from a given table
|
||||
*
|
||||
* @param $db object The SQLite3 database connection object
|
||||
* @param $table string The target table
|
||||
* @param $domains array Array of domains (strings) to be removed from the table
|
||||
* @param $returnnum boolean Whether to return an integer or a string
|
||||
* @return string Success/error and number of processed domains
|
||||
*/
|
||||
function remove_from_table($db, $table, $domains, $returnnum=false)
|
||||
{
|
||||
// Begin transaction
|
||||
if(!$db->exec("BEGIN TRANSACTION;"))
|
||||
{
|
||||
if($returnnum)
|
||||
return 0;
|
||||
else
|
||||
return "Error: Unable to begin transaction for ".$table." table.";
|
||||
}
|
||||
$initialcount = intval($db->querySingle("SELECT COUNT(*) FROM ".$table.";"));
|
||||
|
||||
// Prepare SQLite statememt
|
||||
$stmt = $db->prepare("DELETE FROM ".$table." WHERE domain = :domain;");
|
||||
|
||||
// Return early if we failed to prepare the SQLite statement
|
||||
if(!$stmt)
|
||||
{
|
||||
if($returnnum)
|
||||
return 0;
|
||||
else
|
||||
return "Error: Failed to prepare statement for ".$table." table.";
|
||||
}
|
||||
|
||||
// Loop over domains and remove the lines from the database
|
||||
$num = 0;
|
||||
foreach($domains as $domain)
|
||||
{
|
||||
$stmt->bindValue(":domain", $domain, SQLITE3_TEXT);
|
||||
|
||||
if($stmt->execute() && $stmt->reset())
|
||||
$num++;
|
||||
else
|
||||
{
|
||||
$stmt->close();
|
||||
if($returnnum)
|
||||
return $num;
|
||||
else
|
||||
{
|
||||
if($num === 1)
|
||||
$plural = "";
|
||||
else
|
||||
$plural = "s";
|
||||
return "Error: ".$db->lastErrorMsg().", removed ".$num." domain".$plural;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close prepared statement and return number or processed rows
|
||||
$stmt->close();
|
||||
$db->exec("COMMIT;");
|
||||
|
||||
if($returnnum)
|
||||
return $num;
|
||||
else
|
||||
{
|
||||
if($num === 1)
|
||||
$plural = "";
|
||||
else
|
||||
$plural = "s";
|
||||
return "Success, removed ".$num." domain".$plural;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -45,21 +45,4 @@ if(!function_exists('hash_equals')) {
|
||||
}
|
||||
}
|
||||
|
||||
function add_regex($regex, $mode=FILE_APPEND, $append="\n")
|
||||
{
|
||||
global $regexfile;
|
||||
if(file_put_contents($regexfile, $regex.$append, $mode) === FALSE)
|
||||
{
|
||||
$err = error_get_last()["message"];
|
||||
echo "Unable to add regex \"".htmlspecialchars($regex)."\" to ${regexfile}<br>Error message: $err";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Send SIGHUP to pihole-FTL using a frontend command
|
||||
// to force reloading of the regex domains
|
||||
// This will also wipe the resolver's cache
|
||||
echo exec("sudo pihole restartdns reload");
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
@@ -11,57 +11,67 @@ if(!isset($_GET['list']))
|
||||
|
||||
$listtype = $_GET['list'];
|
||||
|
||||
$basedir = "/etc/pihole/";
|
||||
require_once("func.php");
|
||||
|
||||
require_once "func.php";
|
||||
require("database.php");
|
||||
$GRAVITYDB = getGravityDBFilename();
|
||||
$db = SQLite3_connect($GRAVITYDB);
|
||||
|
||||
switch ($listtype) {
|
||||
case "white":
|
||||
$list = array(getListContent("whitelist.txt"));
|
||||
break;
|
||||
function getTableContent($listname) {
|
||||
global $db;
|
||||
$entries = array();
|
||||
$querystr = implode(" ",array("SELECT ${listname}.*,\"group\".enabled as group_enabled",
|
||||
"FROM ${listname}",
|
||||
"LEFT JOIN ${listname}_by_group ON ${listname}_by_group.${listname}_id = ${listname}.id",
|
||||
"LEFT JOIN \"group\" ON \"group\".id = ${listname}_by_group.group_id",
|
||||
"GROUP BY domain;"));
|
||||
$results = $db->query($querystr);
|
||||
|
||||
case "black":
|
||||
$exact = getListContent("blacklist.txt");
|
||||
$regex = getListContent("regex.list");
|
||||
$list = array($exact, $regex);
|
||||
break;
|
||||
|
||||
default:
|
||||
die("Invalid list parameter");
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
function getListContent($listname) {
|
||||
global $basedir;
|
||||
$rawList = file_get_contents(checkfile($basedir.$listname));
|
||||
$list = explode("\n", $rawList);
|
||||
|
||||
// Get rid of empty lines and comments
|
||||
for($i = sizeof($list)-1; $i >= 0; $i--) {
|
||||
if(strlen($list[$i]) < 1 || $list[$i][0] === '#')
|
||||
unset($list[$i]);
|
||||
}
|
||||
|
||||
// Re-index list after possible unset() activity
|
||||
$newlist = array_values($list);
|
||||
|
||||
return $newlist;
|
||||
while($results !== false && $res = $results->fetchArray(SQLITE3_ASSOC))
|
||||
{
|
||||
array_push($entries, $res);
|
||||
}
|
||||
|
||||
return array($listname => $entries);
|
||||
}
|
||||
|
||||
function filterArray(&$inArray) {
|
||||
$outArray = array();
|
||||
foreach ($inArray as $key=>$value) {
|
||||
if (is_array($value)) {
|
||||
$outArray[htmlspecialchars($key)] = filterArray($value);
|
||||
} else {
|
||||
$outArray[htmlspecialchars($key)] = htmlspecialchars($value);
|
||||
}
|
||||
}
|
||||
return $outArray;
|
||||
$outArray = array();
|
||||
foreach ($inArray as $key => $value)
|
||||
{
|
||||
if (is_array($value))
|
||||
{
|
||||
$outArray[htmlspecialchars($key)] = filterArray($value);
|
||||
}
|
||||
else
|
||||
{
|
||||
$outArray[htmlspecialchars($key)] = htmlspecialchars($value);
|
||||
}
|
||||
}
|
||||
return $outArray;
|
||||
}
|
||||
|
||||
switch ($listtype)
|
||||
{
|
||||
case "white":
|
||||
$exact = getTableContent("whitelist");
|
||||
$regex = getTableContent("regex_whitelist");
|
||||
$list = array_merge($exact, $regex);
|
||||
break;
|
||||
|
||||
case "black":
|
||||
$exact = getTableContent("blacklist");
|
||||
$regex = getTableContent("regex_blacklist");
|
||||
$list = array_merge($exact, $regex);
|
||||
break;
|
||||
|
||||
default:
|
||||
die("Invalid list parameter");
|
||||
break;
|
||||
}
|
||||
// Protect against XSS attacks
|
||||
$list = filterArray($list);
|
||||
echo json_encode(array_values($list));
|
||||
$output = filterArray($list);
|
||||
|
||||
// Return results
|
||||
header('Content-type: application/json');
|
||||
echo json_encode($output);
|
||||
|
||||
@@ -165,37 +165,22 @@ function readDNSserversList()
|
||||
return $list;
|
||||
}
|
||||
|
||||
require_once("database.php");
|
||||
$adlist = [];
|
||||
function readAdlists()
|
||||
{
|
||||
// Reset list
|
||||
$list = [];
|
||||
$handle = @fopen("/etc/pihole/adlists.list", "r");
|
||||
if ($handle)
|
||||
$db = SQLite3_connect(getGravityDBFilename());
|
||||
if ($db)
|
||||
{
|
||||
while (($line = fgets($handle)) !== false)
|
||||
$results = $db->query("SELECT * FROM adlist");
|
||||
|
||||
while($results !== false && $res = $results->fetchArray(SQLITE3_ASSOC))
|
||||
{
|
||||
if(strlen($line) < 3)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
elseif($line[0] === "#")
|
||||
{
|
||||
// Comments start either with "##" or "# "
|
||||
if($line[1] !== "#" &&
|
||||
$line[1] !== " ")
|
||||
{
|
||||
// Commented list
|
||||
array_push($list, [false,rtrim(substr($line, 1))]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Active list
|
||||
array_push($list, [true,rtrim($line)]);
|
||||
}
|
||||
array_push($list, $res);
|
||||
}
|
||||
fclose($handle);
|
||||
$db->close();
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
@@ -704,18 +689,18 @@ function readAdlists()
|
||||
if(isset($_POST["adlist-del-".$key]))
|
||||
{
|
||||
// Delete list
|
||||
exec("sudo pihole -a adlist del ".escapeshellcmd($value[1]));
|
||||
exec("sudo pihole -a adlist del ".escapeshellcmd($value["address"]));
|
||||
}
|
||||
elseif(isset($_POST["adlist-enable-".$key]) && !$value[0])
|
||||
elseif(isset($_POST["adlist-enable-".$key]) && $value["enabled"] !== 1)
|
||||
{
|
||||
// Is not enabled, but should be
|
||||
exec("sudo pihole -a adlist enable ".escapeshellcmd($value[1]));
|
||||
exec("sudo pihole -a adlist enable ".escapeshellcmd($value["address"]));
|
||||
|
||||
}
|
||||
elseif(!isset($_POST["adlist-enable-".$key]) && $value[0])
|
||||
elseif(!isset($_POST["adlist-enable-".$key]) && $value["enabled"] === 1)
|
||||
{
|
||||
// Is enabled, but shouldn't be
|
||||
exec("sudo pihole -a adlist disable ".escapeshellcmd($value[1]));
|
||||
exec("sudo pihole -a adlist disable ".escapeshellcmd($value["address"]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -765,6 +750,15 @@ function readAdlists()
|
||||
$error .= "Invalid privacy level (".$level.")!";
|
||||
}
|
||||
break;
|
||||
// Flush network table
|
||||
case "flusharp":
|
||||
exec("sudo pihole arpflush quiet", $output);
|
||||
$error = implode("<br>", $output);
|
||||
if(strlen($error) == 0)
|
||||
{
|
||||
$success .= "The network table has been flushed";
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// Option not found
|
||||
|
||||
@@ -16,44 +16,36 @@ if (empty($api)) {
|
||||
list_verify($type);
|
||||
}
|
||||
|
||||
// Don't check if the added item is a valid domain for regex expressions. Regex
|
||||
// filters are validated by FTL on import and skipped if invalid
|
||||
if($type !== "regex") {
|
||||
check_domain();
|
||||
}
|
||||
// Split individual domains into array
|
||||
$domains = preg_split('/\s+/', trim($_POST['domain']));
|
||||
|
||||
require_once("func.php");
|
||||
|
||||
require("database.php");
|
||||
$GRAVITYDB = getGravityDBFilename();
|
||||
$db = SQLite3_connect($GRAVITYDB, SQLITE3_OPEN_READWRITE);
|
||||
|
||||
switch($type) {
|
||||
case "white":
|
||||
exec("sudo pihole -w -q -d ${_POST['domain']}");
|
||||
break;
|
||||
case "black":
|
||||
exec("sudo pihole -b -q -d ${_POST['domain']}");
|
||||
break;
|
||||
case "regex":
|
||||
if(($list = file_get_contents($regexfile)) === FALSE)
|
||||
{
|
||||
$err = error_get_last()["message"];
|
||||
echo "Unable to read ${regexfile}<br>Error message: $err";
|
||||
}
|
||||
case "white":
|
||||
echo remove_from_table($db, "whitelist", $domains);
|
||||
break;
|
||||
|
||||
// Remove the regex and any empty lines from the list
|
||||
$list = explode("\n", $list);
|
||||
$list = array_diff($list, array($_POST['domain'], ""));
|
||||
$list = implode("\n", $list);
|
||||
case "black":
|
||||
echo remove_from_table($db, "blacklist", $domains);
|
||||
break;
|
||||
|
||||
if(file_put_contents($regexfile, $list."\n") === FALSE)
|
||||
{
|
||||
$err = error_get_last()["message"];
|
||||
echo "Unable to remove regex \"".htmlspecialchars($_POST['domain'])."\" from ${regexfile}<br>Error message: $err";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Send SIGHUP to pihole-FTL using a frontend command
|
||||
// to force reloading of the regex domains
|
||||
// This will also wipe the resolver's cache
|
||||
echo exec("sudo pihole restartdns reload");
|
||||
}
|
||||
break;
|
||||
case "black_regex":
|
||||
echo remove_from_table($db, "regex_blacklist", $domains);
|
||||
break;
|
||||
|
||||
case "white_regex":
|
||||
echo remove_from_table($db, "regex_whitelist", $domains);
|
||||
break;
|
||||
|
||||
default:
|
||||
die("Invalid list!");
|
||||
}
|
||||
|
||||
// Reload lists in pihole-FTL after having removed something
|
||||
echo shell_exec("sudo pihole restartdns reload");
|
||||
?>
|
||||
|
||||
@@ -8,12 +8,17 @@
|
||||
|
||||
require "password.php";
|
||||
require "auth.php"; // Also imports func.php
|
||||
require "database.php";
|
||||
|
||||
if (php_sapi_name() !== "cli") {
|
||||
if(!$auth) die("Not authorized");
|
||||
check_csrf(isset($_POST["token"]) ? $_POST["token"] : "");
|
||||
}
|
||||
|
||||
$db = SQLite3_connect(getGravityDBFilename(), SQLITE3_OPEN_READWRITE);
|
||||
|
||||
$flushed_tables = array();
|
||||
|
||||
function archive_add_file($path,$name,$subdir="")
|
||||
{
|
||||
global $archive;
|
||||
@@ -21,6 +26,160 @@ function archive_add_file($path,$name,$subdir="")
|
||||
$archive[$subdir.$name] = file_get_contents($path.$name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the contents of a table to the archive
|
||||
*
|
||||
* @param $name string The name of the file in the archive to save the table to
|
||||
* @param $table string The table to export
|
||||
*/
|
||||
function archive_add_table($name, $table)
|
||||
{
|
||||
global $archive, $db;
|
||||
|
||||
$results = $db->query("SELECT * FROM $table");
|
||||
|
||||
// Return early without creating a file if the
|
||||
// requested table cannot be accessed
|
||||
if(is_null($results))
|
||||
return;
|
||||
|
||||
$content = array();
|
||||
while ($row = $results->fetchArray(SQLITE3_ASSOC))
|
||||
{
|
||||
array_push($content, $row);
|
||||
}
|
||||
|
||||
$archive[$name] = json_encode($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the contents of a table from an uploaded archive
|
||||
*
|
||||
* @param $file object The file in the archive to restore the table from
|
||||
* @param $table string The table to import
|
||||
* @param $flush boolean Whether to flush the table before importing the archived data
|
||||
* @return integer Number of restored rows
|
||||
*/
|
||||
function archive_restore_table($file, $table, $flush=false)
|
||||
{
|
||||
global $db, $flushed_tables;
|
||||
|
||||
$json_string = file_get_contents($file);
|
||||
// Return early if we cannot extract the JSON string
|
||||
if(is_null($json_string))
|
||||
return 0;
|
||||
|
||||
$contents = json_decode($json_string, true);
|
||||
// Return early if we cannot decode the JSON string
|
||||
if(is_null($contents))
|
||||
return 0;
|
||||
|
||||
// Flush table if requested, only flush each table once
|
||||
if($flush && !in_array($table, $flushed_tables))
|
||||
{
|
||||
$db->exec("DELETE FROM ".$table);
|
||||
array_push($flushed_tables, $table);
|
||||
}
|
||||
|
||||
// Prepare field name for domain/address 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";
|
||||
}
|
||||
else
|
||||
{
|
||||
$sql = "INSERT OR IGNORE INTO ".$table;
|
||||
$sql .= " (id,domain,enabled,date_added,comment)";
|
||||
$sql .= " VALUES (:id,:domain,:enabled,:date_added,:comment);";
|
||||
$field = "domain";
|
||||
}
|
||||
|
||||
// Prepare SQLite statememt
|
||||
$stmt = $db->prepare($sql);
|
||||
|
||||
// Return early if we fail to prepare the SQLite statement
|
||||
if(!$stmt)
|
||||
{
|
||||
echo "Failed to prepare statement for ".$table." table.";
|
||||
echo $sql;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Loop over rows and inject the entries into the database
|
||||
$num = 0;
|
||||
foreach($contents as $row)
|
||||
{
|
||||
// Limit max length for a domain entry to 253 chars
|
||||
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($stmt->execute() && $stmt->reset() && $stmt->clear())
|
||||
$num++;
|
||||
else
|
||||
{
|
||||
$stmt->close();
|
||||
return $num;
|
||||
}
|
||||
}
|
||||
|
||||
// Close database connection and return number or processed rows
|
||||
$stmt->close();
|
||||
return $num;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create table rows from an uploaded archive file
|
||||
*
|
||||
* @param $file object The file of the file in the archive to import
|
||||
* @param $table string The target table
|
||||
* @param $flush boolean Whether to flush the table before importing the archived data
|
||||
* @param $wildcardstyle boolean Whether to format the input domains in legacy wildcard notation
|
||||
* @return integer Number of processed rows from the imported file
|
||||
*/
|
||||
function archive_insert_into_table($file, $table, $flush=false, $wildcardstyle=false)
|
||||
{
|
||||
global $db, $flushed_tables;
|
||||
|
||||
$domains = array_filter(explode("\n",file_get_contents($file)));
|
||||
// Return early if we cannot extract the lines in the file
|
||||
if(is_null($domains))
|
||||
return 0;
|
||||
|
||||
// Flush table if requested, only flush each table once
|
||||
if($flush && !in_array($table, $flushed_tables))
|
||||
{
|
||||
$db->exec("DELETE FROM ".$table);
|
||||
array_push($flushed_tables, $table);
|
||||
}
|
||||
|
||||
// Add domains to requested table
|
||||
return add_to_table($db, $table, $domains, $wildcardstyle, true);
|
||||
}
|
||||
|
||||
function archive_add_directory($path,$subdir="")
|
||||
{
|
||||
if($dir = opendir($path))
|
||||
@@ -36,40 +195,6 @@ function archive_add_directory($path,$subdir="")
|
||||
}
|
||||
}
|
||||
|
||||
function limit_length(&$item, $key)
|
||||
{
|
||||
// limit max length for a domain entry to 253 chars
|
||||
// return only a part of the string if it is longer
|
||||
$item = substr($item, 0, 253);
|
||||
}
|
||||
|
||||
function process_file($contents,$check=True)
|
||||
{
|
||||
$domains = array_filter(explode("\n",$contents));
|
||||
|
||||
// Walk array and apply a max string length
|
||||
// function to every member of the array of domains
|
||||
array_walk($domains, "limit_length");
|
||||
|
||||
// Check validity of domains (don't do it for regex filters)
|
||||
if($check)
|
||||
{
|
||||
check_domains($domains);
|
||||
}
|
||||
|
||||
return $domains;
|
||||
}
|
||||
|
||||
function check_domains($domains)
|
||||
{
|
||||
foreach($domains as $domain)
|
||||
{
|
||||
if(!is_valid_domain_name($domain)){
|
||||
die(htmlspecialchars($domain).' is not a valid domain');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($_POST["action"]))
|
||||
{
|
||||
if($_FILES["zip_file"]["name"] && $_POST["action"] == "in")
|
||||
@@ -103,58 +228,92 @@ if(isset($_POST["action"]))
|
||||
|
||||
$importedsomething = false;
|
||||
|
||||
$flushtables = isset($_POST["flushtables"]);
|
||||
|
||||
foreach($archive as $file)
|
||||
{
|
||||
if(isset($_POST["blacklist"]) && $file->getFilename() === "blacklist.txt")
|
||||
{
|
||||
$blacklist = process_file(file_get_contents($file));
|
||||
echo "Processing blacklist.txt (".count($blacklist)." entries)<br>\n";
|
||||
exec("sudo pihole -b -nr --nuke");
|
||||
exec("sudo pihole -b -q -nr ".implode(" ", $blacklist));
|
||||
$num = archive_insert_into_table($file, "blacklist", $flushtables);
|
||||
echo "Processed blacklist (exact) (".$num." entries)<br>\n";
|
||||
$importedsomething = true;
|
||||
}
|
||||
|
||||
if(isset($_POST["whitelist"]) && $file->getFilename() === "whitelist.txt")
|
||||
{
|
||||
$whitelist = process_file(file_get_contents($file));
|
||||
echo "Processing whitelist.txt (".count($whitelist)." entries)<br>\n";
|
||||
exec("sudo pihole -w -nr --nuke");
|
||||
exec("sudo pihole -w -q -nr ".implode(" ", $whitelist));
|
||||
$num = archive_insert_into_table($file, "whitelist", $flushtables);
|
||||
echo "Processed whitelist (exact) (".$num." entries)<br>\n";
|
||||
$importedsomething = true;
|
||||
}
|
||||
|
||||
if(isset($_POST["regexlist"]) && $file->getFilename() === "regex.list")
|
||||
{
|
||||
$regexraw = file_get_contents($file);
|
||||
$regexlist = process_file($regexraw,false);
|
||||
echo "Processing regex.list (".count($regexlist)." entries)<br>\n";
|
||||
// NULL = overwrite (or create) the regex filter file
|
||||
add_regex($regexraw, NULL,"");
|
||||
$num = archive_insert_into_table($file, "regex_blacklist", $flushtables);
|
||||
echo "Processed blacklist (regex) (".$num." entries)<br>\n";
|
||||
$importedsomething = true;
|
||||
}
|
||||
|
||||
// Also try to import legacy wildcard list if found
|
||||
if(isset($_POST["regexlist"]) && $file->getFilename() === "wildcardblocking.txt")
|
||||
{
|
||||
$wildlist = process_file(file_get_contents($file));
|
||||
echo "Processing wildcardblocking.txt (".count($wildlist)." entries)<br>\n";
|
||||
exec("sudo pihole --wild -nr --nuke");
|
||||
exec("sudo pihole --wild -q -nr ".implode(" ", $wildlist));
|
||||
$num = archive_insert_into_table($file, "regex_blacklist", $flushtables, true);
|
||||
echo "Processed blacklist (regex, wildcard style) (".$num." entries)<br>\n";
|
||||
$importedsomething = true;
|
||||
}
|
||||
|
||||
if(isset($_POST["auditlog"]) && $file->getFilename() === "auditlog.list")
|
||||
{
|
||||
$auditlog = process_file(file_get_contents($file));
|
||||
echo "Processing auditlog.list (".count($auditlog)." entries)<br>\n";
|
||||
exec("sudo pihole -a clearaudit");
|
||||
exec("sudo pihole -a audit ".implode(" ",$auditlog));
|
||||
$num = archive_insert_into_table($file, "domain_audit", $flushtables);
|
||||
echo "Processed blacklist (regex) (".$num." entries)<br>\n";
|
||||
$importedsomething = true;
|
||||
}
|
||||
|
||||
if($importedsomething)
|
||||
if(isset($_POST["blacklist"]) && $file->getFilename() === "blacklist.exact.json")
|
||||
{
|
||||
exec("sudo pihole restartdns");
|
||||
$num = archive_restore_table($file, "blacklist", $flushtables);
|
||||
echo "Processed blacklist (exact) (".$num." entries)<br>\n";
|
||||
$importedsomething = true;
|
||||
}
|
||||
|
||||
if(isset($_POST["regexlist"]) && $file->getFilename() === "blacklist.regex.json")
|
||||
{
|
||||
$num = archive_restore_table($file, "regex_blacklist", $flushtables);
|
||||
echo "Processed blacklist (regex) (".$num." entries)<br>\n";
|
||||
$importedsomething = true;
|
||||
}
|
||||
|
||||
if(isset($_POST["whitelist"]) && $file->getFilename() === "whitelist.exact.json")
|
||||
{
|
||||
$num = archive_restore_table($file, "whitelist", $flushtables);
|
||||
echo "Processed whitelist (exact) (".$num." entries)<br>\n";
|
||||
$importedsomething = true;
|
||||
}
|
||||
|
||||
if(isset($_POST["regex_whitelist"]) && $file->getFilename() === "whitelist.regex.json")
|
||||
{
|
||||
$num = archive_restore_table($file, "regex_whitelist", $flushtables);
|
||||
echo "Processed whitelist (regex) (".$num." entries)<br>\n";
|
||||
$importedsomething = true;
|
||||
}
|
||||
|
||||
if(isset($_POST["adlist"]) && $file->getFilename() === "adlist.json")
|
||||
{
|
||||
$num = archive_restore_table($file, "adlist", $flushtables);
|
||||
echo "Processed adlist (".$num." entries)<br>\n";
|
||||
$importedsomething = true;
|
||||
}
|
||||
|
||||
if(isset($_POST["auditlog"]) && $file->getFilename() === "domain_audit.json")
|
||||
{
|
||||
$num = archive_restore_table($file, "domain_audit", $flushtables);
|
||||
echo "Processed domain_audit (".$num." entries)<br>\n";
|
||||
$importedsomething = true;
|
||||
}
|
||||
}
|
||||
|
||||
if($importedsomething)
|
||||
{
|
||||
exec("sudo pihole restartdns reload");
|
||||
}
|
||||
|
||||
unlink($fullfilename);
|
||||
@@ -176,19 +335,20 @@ else
|
||||
exit("cannot open/create ".htmlentities($archive_file_name)."<br>\nPHP user: ".exec('whoami')."\n");
|
||||
}
|
||||
|
||||
archive_add_file("/etc/pihole/","whitelist.txt");
|
||||
archive_add_file("/etc/pihole/","blacklist.txt");
|
||||
archive_add_file("/etc/pihole/","adlists.list");
|
||||
archive_add_table("whitelist.exact.json", "whitelist");
|
||||
archive_add_table("whitelist.regex.json", "regex_whitelist");
|
||||
archive_add_table("blacklist.exact.json", "blacklist");
|
||||
archive_add_table("blacklist.regex.json", "regex_blacklist");
|
||||
archive_add_table("adlist.json", "adlist");
|
||||
archive_add_table("domain_audit.json", "domain_audit");
|
||||
archive_add_file("/etc/pihole/","setupVars.conf");
|
||||
archive_add_file("/etc/pihole/","auditlog.list");
|
||||
archive_add_file("/etc/pihole/","regex.list");
|
||||
archive_add_directory("/etc/dnsmasq.d/","dnsmasq.d/");
|
||||
|
||||
$archive->compress(Phar::GZ); // Creates a gziped copy
|
||||
unlink($archive_file_name); // Unlink original tar file as it is not needed anymore
|
||||
$archive_file_name .= ".gz"; // Append ".gz" extension to ".tar"
|
||||
|
||||
header("Content-type: application/zip");
|
||||
header("Content-type: application/gzip");
|
||||
header('Content-Transfer-Encoding: binary');
|
||||
header("Content-Disposition: attachment; filename=".$filename);
|
||||
header("Content-length: " . filesize($archive_file_name));
|
||||
|
||||
48
settings.php
48
settings.php
@@ -257,8 +257,11 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "blocklists"
|
||||
<table class="table table-striped table-bordered dt-responsive nowrap">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Enabled</th>
|
||||
<th style="width:1%">Enabled</th>
|
||||
<th>List</th>
|
||||
<th>Added</th>
|
||||
<th>Last modified</th>
|
||||
<th>Comment</th>
|
||||
<th style="width:1%">Delete</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -266,10 +269,19 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "blocklists"
|
||||
<?php foreach ($adlist as $key => $value) { ?>
|
||||
<tr>
|
||||
<td>
|
||||
<input type="checkbox" name="adlist-enable-<?php echo $key; ?>" <?php if ($value[0]){ ?>checked<?php } ?>>
|
||||
<input type="checkbox" name="adlist-enable-<?php echo $key; ?>" <?php if ($value["enabled"] === 1){ ?>checked<?php } ?>>
|
||||
</td>
|
||||
<td>
|
||||
<a href="<?php echo htmlentities($value[1]); ?>" target="_new" id="adlist-text-<?php echo $key; ?>"><?php echo htmlentities($value[1]); ?></a>
|
||||
<a href="<?php echo htmlentities($value["address"]); ?>" target="_new" id="adlist-text-<?php echo $key; ?>"><?php echo htmlentities($value["address"]); ?></a>
|
||||
</td>
|
||||
<td>
|
||||
<?php echo date(DateTime::RFC2822, intval($value["date_added"])); ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php echo date(DateTime::RFC2822, intval($value["date_modified"])); ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php echo htmlentities($value["comment"]); ?>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<button class="btn btn-danger btn-xs" id="adlist-btn-<?php echo $key; ?>">
|
||||
@@ -283,7 +295,7 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "blocklists"
|
||||
</table>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<textarea name="newuserlists" class="form-control" rows="1" placeholder="Enter one URL per line to add new blocklists"></textarea>
|
||||
<input name="newuserlists" class="form-control" placeholder="Enter a URL to add a new blocklist">
|
||||
</div>
|
||||
<input type="hidden" name="field" value="adlists">
|
||||
<input type="hidden" name="token" value="<?php echo $token ?>">
|
||||
@@ -1133,7 +1145,12 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "blocklists"
|
||||
<div class="checkbox">
|
||||
<label><input type="checkbox" name="whitelist" value="true"
|
||||
checked>
|
||||
Whitelist</label>
|
||||
Whitelist (exact)</label>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label><input type="checkbox" name="regex_whitelist" value="true"
|
||||
checked>
|
||||
Whitelist (regex/wildcard)</label>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label><input type="checkbox" name="blacklist" value="true"
|
||||
@@ -1143,7 +1160,12 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "blocklists"
|
||||
<div class="checkbox">
|
||||
<label><input type="checkbox" name="regexlist" value="true"
|
||||
checked>
|
||||
Regex filters</label>
|
||||
Blacklist (regex/wildcard)</label>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label><input type="checkbox" name="adlist" value="true"
|
||||
checked>
|
||||
Blocklists</label>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label><input type="checkbox" name="auditlog" value="true"
|
||||
@@ -1160,6 +1182,11 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "blocklists"
|
||||
<button type="submit" class="btn btn-default" name="action"
|
||||
value="in">Import
|
||||
</button>
|
||||
<div class="checkbox">
|
||||
<label><input type="checkbox" name="flushtables" value="true"
|
||||
checked>
|
||||
Clear existing data</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1319,9 +1346,7 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "blocklists"
|
||||
</div>
|
||||
<p class="hidden-md hidden-lg"></p>
|
||||
<div class="col-md-4">
|
||||
<?php if ($piHoleLogging) { ?>
|
||||
<button type="button" class="btn btn-danger confirm-disablelogging form-control">Disable query logging and flush logs</button>
|
||||
<?php } ?>
|
||||
<button type="button" class="btn btn-warning confirm-flusharp form-control">Flush network table</button>
|
||||
</div>
|
||||
<p class="hidden-md hidden-lg"></p>
|
||||
<div class="col-md-4">
|
||||
@@ -1347,9 +1372,8 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "blocklists"
|
||||
<input type="hidden" name="field" value="flushlogs">
|
||||
<input type="hidden" name="token" value="<?php echo $token ?>">
|
||||
</form>
|
||||
<form role="form" method="post" id="disablelogsform">
|
||||
<input type="hidden" name="field" value="Logging">
|
||||
<input type="hidden" name="action" value="Disable">
|
||||
<form role="form" method="post" id="flusharpform">
|
||||
<input type="hidden" name="field" value="flusharp">
|
||||
<input type="hidden" name="token" value="<?php echo $token ?>">
|
||||
</form>
|
||||
<form role="form" method="post" id="disablelogsform-noflush">
|
||||
|
||||
@@ -24,6 +24,23 @@
|
||||
.skin-blue .list-group-item:hover {
|
||||
background: #ddd;
|
||||
}
|
||||
|
||||
.skin-blue .not-used {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
.skin-blue .not-used:hover {
|
||||
background: #c5c5c95;
|
||||
}
|
||||
|
||||
.skin-blue .used {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.skin-blue .used:hover {
|
||||
background: #ddd;
|
||||
}
|
||||
|
||||
@-webkit-keyframes Pulse{
|
||||
from {color:#630030;-webkit-text-shadow:0 0 2px transparent;}
|
||||
50% {color:#e33100;-webkit-text-shadow:0 0 5px #e33100;}
|
||||
|
||||
Reference in New Issue
Block a user