")
+ .append($('').text(i === 0 ? "Did you mean" : "or"))
+ .append($(" ").append(createButton(hostname)))
+ );
+ }
+
+ suggestDomainEl.slideUp("fast", function () {
+ suggestDomainEl.html(table);
+ suggestDomainEl.slideDown("fast");
+ });
+ } catch {
+ hideSuggestDomains();
+ }
+}
+
+function hideSuggestDomains() {
+ $("#suggest_domains").slideUp("fast");
+}
+
function initTable() {
table = $("#domainsTable").DataTable({
ajax: {
@@ -69,6 +122,7 @@ function initTable() {
order: [[0, "asc"]],
columns: [
{ data: "id", visible: false },
+ { data: null, visible: true, orderable: false, width: "15px" },
{ data: "domain" },
{ data: "type", searchable: false },
{ data: "enabled", searchable: false },
@@ -77,12 +131,23 @@ function initTable() {
{ data: null, width: "22px", orderable: false },
],
columnDefs: [
+ {
+ targets: 1,
+ className: "select-checkbox",
+ render: function () {
+ return "";
+ },
+ },
{
targets: "_all",
render: $.fn.dataTable.render.text(),
},
],
drawCallback: function () {
+ // Hide buttons if all domains were deleted
+ var hasRows = this.api().rows({ filter: "applied" }).data().length > 0;
+ $(".datatable-bt").css("visibility", hasRows ? "visible" : "hidden");
+
$('button[id^="deleteDomain_"]').on("click", deleteDomain);
// Remove visible dropdown to prevent orphaning
$("body > .bootstrap-select.dropdown").remove();
@@ -96,7 +161,7 @@ function initTable() {
utils.datetime(data.date_modified, false) +
"\nDatabase ID: " +
data.id;
- $("td:eq(0)", row).html(
+ $("td:eq(1)", row).html(
'Regex blacklist";
}
- $("td:eq(1)", row).html(
+ $("td:eq(2)", row).html(
'' +
@@ -140,7 +205,7 @@ function initTable() {
typeEl.on("change", editDomain);
var disabled = data.enabled === 0;
- $("td:eq(2)", row).html(
+ $("td:eq(3)", row).html(
' "
);
var statusEl = $("#status_" + data.id, row);
@@ -153,15 +218,15 @@ function initTable() {
});
statusEl.on("change", editDomain);
- $("td:eq(3)", row).html('');
+ $("td:eq(4)", row).html('');
var commentEl = $("#comment_" + data.id, row);
commentEl.val(utils.unescapeHtml(data.comment));
commentEl.on("change", editDomain);
// Show group assignment field only if in full domain management mode
- if (table.column(5).visible()) {
- $("td:eq(4)", row).empty();
- $("td:eq(4)", row).append(
+ if (table.column(6).visible()) {
+ $("td:eq(5)", row).empty();
+ $("td:eq(5)", row).append(
' '
);
var selectEl = $("#multiselect_" + data.id, row);
@@ -234,20 +299,67 @@ function initTable() {
var button =
'' +
' ' +
" ";
- if (table.column(5).visible()) {
- $("td:eq(5)", row).html(button);
+ if (table.column(6).visible()) {
+ $("td:eq(6)", row).html(button);
} else {
- $("td:eq(4)", row).html(button);
+ $("td:eq(5)", row).html(button);
}
},
+ select: {
+ style: "multi",
+ selector: "td:not(:last-child)",
+ info: false,
+ },
+ buttons: [
+ {
+ text: ' ',
+ titleAttr: "Select All",
+ className: "btn-sm datatable-bt selectAll",
+ action: function () {
+ table.rows({ page: "current" }).select();
+ },
+ },
+ {
+ text: ' ',
+ titleAttr: "Select All",
+ className: "btn-sm datatable-bt selectMore",
+ action: function () {
+ table.rows({ page: "current" }).select();
+ },
+ },
+ {
+ extend: "selectNone",
+ text: ' ',
+ titleAttr: "Deselect All",
+ className: "btn-sm datatable-bt removeAll",
+ },
+ {
+ text: ' ',
+ titleAttr: "Delete Selected",
+ className: "btn-sm datatable-bt deleteSelected",
+ action: function () {
+ // For each ".selected" row ...
+ var ids = [];
+ $("tr.selected").each(function () {
+ // ... add the row identified by "data-id".
+ ids.push(parseInt($(this).attr("data-id"), 10));
+ });
+ // Delete all selected rows at once
+ delItems(ids);
+ },
+ },
+ ],
dom:
- "<'row'<'col-sm-12'f>>" +
- "<'row'<'col-sm-4'l><'col-sm-8'p>>" +
+ "<'row'<'col-sm-6'l><'col-sm-6'f>>" +
+ "<'row'<'col-sm-3'B><'col-sm-9'p>>" +
"<'row'<'col-sm-12'<'table-responsive'tr>>>" +
- "<'row'<'col-sm-5'i><'col-sm-7'p>>",
+ "<'row'<'col-sm-3'B><'col-sm-9'p>>" +
+ "<'row'<'col-sm-12'i>>",
lengthMenu: [
[10, 25, 50, 100, -1],
[10, 25, 50, 100, "All"],
@@ -268,7 +380,7 @@ function initTable() {
// Reset visibility of ID column
data.columns[0].visible = false;
// Show group assignment column only on full page
- data.columns[5].visible = showtype === "all";
+ data.columns[6].visible = showtype === "all";
// Apply loaded state to table
return data;
},
@@ -294,6 +406,10 @@ function initTable() {
input.setAttribute("spellcheck", false);
}
+ table.on("init select deselect", function () {
+ utils.changeBulkDeleteStates(table);
+ });
+
table.on("order.dt", function () {
var order = table.order();
if (order[0][0] !== 0 || order[0][1] !== "asc") {
@@ -302,12 +418,85 @@ function initTable() {
$("#resetButton").addClass("hidden");
}
});
+
$("#resetButton").on("click", function () {
table.order([[0, "asc"]]).draw();
$("#resetButton").addClass("hidden");
});
}
+// Remove 'bnt-group' class from container, to avoid grouping
+$.fn.dataTable.Buttons.defaults.dom.container.className = "dt-buttons";
+
+function deleteDomain() {
+ // Passes the button data-del-id attribute as ID
+ var ids = [parseInt($(this).attr("data-del-id"), 10)];
+ delItems(ids);
+}
+
+function delItems(ids) {
+ // Check input validity
+ if (!Array.isArray(ids)) return;
+
+ var items = "";
+ var type = "";
+ var typeID = "";
+
+ for (var id of ids) {
+ // Exploit prevention: Return early for non-numeric IDs
+ if (typeof id !== "number") return;
+
+ // Retrieve domain type
+ typeID = $("#type_" + id).val();
+ if (typeID === "0" || typeID === "1") {
+ type = " (domain )";
+ } else if (typeID === "2" || typeID === "3") {
+ type = " (regex )";
+ }
+
+ // Add item
+ items += "" + utils.escapeHtml($("#domain_" + id).text()) + type + " ";
+ }
+
+ utils.disableAll();
+ var idstring = ids.join(", ");
+ utils.showAlert("info", "", "Deleting items: " + idstring, "...");
+
+ $.ajax({
+ url: "scripts/pi-hole/php/groups.php",
+ method: "post",
+ dataType: "json",
+ data: { action: "delete_domain", id: JSON.stringify(ids), token: token },
+ })
+ .done(function (response) {
+ utils.enableAll();
+ if (response.success) {
+ utils.showAlert(
+ "success",
+ "far fa-trash-alt",
+ "Successfully deleted items: " + idstring,
+ ""
+ );
+ for (var id in ids) {
+ if (Object.hasOwnProperty.call(ids, id)) {
+ table.row(id).remove().draw(false).ajax.reload(null, false);
+ }
+ }
+ } else {
+ utils.showAlert("error", "", "Error while deleting items: " + idstring, response.message);
+ }
+
+ // Clear selection after deletion
+ table.rows().deselect();
+ utils.changeBulkDeleteStates(table);
+ })
+ .fail(function (jqXHR, exception) {
+ utils.enableAll();
+ utils.showAlert("error", "", "Error while deleting items: " + idstring, jqXHR.responseText);
+ console.log(exception); // eslint-disable-line no-console
+ });
+}
+
function addDomain() {
var action = this.id;
var tabHref = $('a[data-toggle="tab"][aria-expanded="true"]').attr("href");
@@ -378,6 +567,7 @@ function addDomain() {
commentEl.val("");
wildcardEl.prop("checked", false);
table.ajax.reload(null, false);
+ table.rows().deselect();
} else {
utils.showAlert("error", "", "Error while adding new " + domainRegex, response.message);
}
@@ -490,55 +680,3 @@ function editDomain() {
},
});
}
-
-function deleteDomain() {
- var tr = $(this).closest("tr");
- var id = tr.attr("data-id");
- var domain = utils.escapeHtml(tr.find("#domain_" + id).text());
- var type = tr.find("#type_" + id).val();
-
- var domainRegex;
- if (type === "0" || type === "1") {
- domainRegex = "domain";
- } else if (type === "2" || type === "3") {
- domainRegex = "regex";
- }
-
- utils.disableAll();
- utils.showAlert("info", "", "Deleting " + domainRegex + "...", domain);
- $.ajax({
- url: "scripts/pi-hole/php/groups.php",
- method: "post",
- dataType: "json",
- data: { action: "delete_domain", id: id, token: token },
- success: function (response) {
- utils.enableAll();
- if (response.success) {
- utils.showAlert(
- "success",
- "far fa-trash-alt",
- "Successfully deleted " + domainRegex,
- domain
- );
- table.row(tr).remove().draw(false).ajax.reload(null, false);
- } else {
- utils.showAlert(
- "error",
- "",
- "Error while deleting " + domainRegex + " with ID " + id,
- response.message
- );
- }
- },
- error: function (jqXHR, exception) {
- utils.enableAll();
- utils.showAlert(
- "error",
- "",
- "Error while deleting " + domainRegex + " with ID " + id,
- jqXHR.responseText
- );
- console.log(exception); // eslint-disable-line no-console
- },
- });
-}
diff --git a/scripts/pi-hole/js/groups.js b/scripts/pi-hole/js/groups.js
index 8b7e3b63b..e37e3a9b4 100644
--- a/scripts/pi-hole/js/groups.js
+++ b/scripts/pi-hole/js/groups.js
@@ -22,18 +22,30 @@ $(function () {
order: [[0, "asc"]],
columns: [
{ data: "id", visible: false },
+ { data: null, visible: true, orderable: false, width: "15px" },
{ data: "name" },
{ data: "enabled", searchable: false },
{ data: "description" },
{ data: null, width: "22px", orderable: false },
],
columnDefs: [
+ {
+ targets: 1,
+ className: "select-checkbox",
+ render: function () {
+ return "";
+ },
+ },
{
targets: "_all",
render: $.fn.dataTable.render.text(),
},
],
drawCallback: function () {
+ // Hide buttons if all groups were deleted
+ var hasRows = this.api().rows({ filter: "applied" }).data().length > 0;
+ $(".datatable-bt").css("visibility", hasRows ? "visible" : "hidden");
+
$('button[id^="deleteGroup_"]').on("click", deleteGroup);
},
rowCallback: function (row, data) {
@@ -45,7 +57,7 @@ $(function () {
utils.datetime(data.date_modified, false) +
"\nDatabase ID: " +
data.id;
- $("td:eq(0)", row).html(
+ $("td:eq(1)", row).html(
' '
);
var nameEl = $("#name_" + data.id, row);
@@ -53,7 +65,7 @@ $(function () {
nameEl.on("change", editGroup);
var disabled = data.enabled === 0;
- $("td:eq(1)", row).html(
+ $("td:eq(2)", row).html(
' "
);
var statusEl = $("#status_" + data.id, row);
@@ -66,28 +78,75 @@ $(function () {
});
statusEl.on("change", editGroup);
- $("td:eq(2)", row).html(' ');
+ $("td:eq(3)", row).html(' ');
var desc = data.description !== null ? data.description : "";
var descEl = $("#desc_" + data.id, row);
descEl.val(utils.unescapeHtml(desc));
descEl.on("change", editGroup);
- $("td:eq(3)", row).empty();
+ $("td:eq(4)", row).empty();
if (data.id !== 0) {
var button =
'' +
' ' +
" ";
- $("td:eq(3)", row).html(button);
+ $("td:eq(4)", row).html(button);
}
},
+ select: {
+ style: "multi",
+ selector: "td:not(:last-child)",
+ info: false,
+ },
+ buttons: [
+ {
+ text: ' ',
+ titleAttr: "Select All",
+ className: "btn-sm datatable-bt selectAll",
+ action: function () {
+ table.rows({ page: "current" }).select();
+ },
+ },
+ {
+ text: ' ',
+ titleAttr: "Select All",
+ className: "btn-sm datatable-bt selectMore",
+ action: function () {
+ table.rows({ page: "current" }).select();
+ },
+ },
+ {
+ extend: "selectNone",
+ text: ' ',
+ titleAttr: "Deselect All",
+ className: "btn-sm datatable-bt removeAll",
+ },
+ {
+ text: ' ',
+ titleAttr: "Delete Selected",
+ className: "btn-sm datatable-bt deleteSelected",
+ action: function () {
+ // For each ".selected" row ...
+ var ids = [];
+ $("tr.selected").each(function () {
+ // ... add the row identified by "data-id".
+ ids.push(parseInt($(this).attr("data-id"), 10));
+ });
+ // Delete all selected rows at once
+ delItems(ids);
+ },
+ },
+ ],
dom:
- "<'row'<'col-sm-12'f>>" +
- "<'row'<'col-sm-4'l><'col-sm-8'p>>" +
+ "<'row'<'col-sm-6'l><'col-sm-6'f>>" +
+ "<'row'<'col-sm-3'B><'col-sm-9'p>>" +
"<'row'<'col-sm-12'<'table-responsive'tr>>>" +
- "<'row'<'col-sm-5'i><'col-sm-7'p>>",
+ "<'row'<'col-sm-3'B><'col-sm-9'p>>" +
+ "<'row'<'col-sm-12'i>>",
lengthMenu: [
[10, 25, 50, 100, -1],
[10, 25, 50, 100, "All"],
@@ -121,6 +180,10 @@ $(function () {
input.setAttribute("spellcheck", false);
}
+ table.on("init select deselect", function () {
+ utils.changeBulkDeleteStates(table);
+ });
+
table.on("order.dt", function () {
var order = table.order();
if (order[0][0] !== 0 || order[0][1] !== "asc") {
@@ -129,12 +192,75 @@ $(function () {
$("#resetButton").addClass("hidden");
}
});
+
$("#resetButton").on("click", function () {
table.order([[0, "asc"]]).draw();
$("#resetButton").addClass("hidden");
});
});
+// Remove 'bnt-group' class from container, to avoid grouping
+$.fn.dataTable.Buttons.defaults.dom.container.className = "dt-buttons";
+
+function deleteGroup() {
+ // Passes the button data-del-id attribute as ID
+ var ids = [parseInt($(this).attr("data-del-id"), 10)];
+ delItems(ids);
+}
+
+function delItems(ids) {
+ // Check input validity
+ if (!Array.isArray(ids)) return;
+
+ var items = "";
+
+ for (var id of ids) {
+ // Exploit prevention: Return early for non-numeric IDs
+ if (typeof id !== "number") return;
+
+ // List deleted items
+ items += "" + utils.escapeHtml($("#name_" + id).val()) + " ";
+ }
+
+ utils.disableAll();
+ var idstring = ids.join(", ");
+ utils.showAlert("info", "", "Deleting groups: " + idstring, "...");
+
+ $.ajax({
+ url: "scripts/pi-hole/php/groups.php",
+ method: "post",
+ dataType: "json",
+ data: { action: "delete_group", id: JSON.stringify(ids), token: token },
+ })
+ .done(function (response) {
+ utils.enableAll();
+ if (response.success) {
+ utils.showAlert(
+ "success",
+ "far fa-trash-alt",
+ "Successfully deleted groups: " + idstring,
+ ""
+ );
+ for (var id in ids) {
+ if (Object.hasOwnProperty.call(ids, id)) {
+ table.row(id).remove().draw(false).ajax.reload(null, false);
+ }
+ }
+ } else {
+ utils.showAlert("error", "", "Error while deleting groups: " + idstring, response.message);
+ }
+
+ // Clear selection after deletion
+ table.rows().deselect();
+ utils.changeBulkDeleteStates(table);
+ })
+ .fail(function (jqXHR, exception) {
+ utils.enableAll();
+ utils.showAlert("error", "", "Error while deleting groups: " + idstring, jqXHR.responseText);
+ console.log(exception); // eslint-disable-line no-console
+ });
+}
+
function addGroup() {
var name = utils.escapeHtml($("#new_name").val());
var desc = utils.escapeHtml($("#new_desc").val());
@@ -161,6 +287,7 @@ function addGroup() {
$("#new_name").val("");
$("#new_desc").val("");
table.ajax.reload();
+ table.rows().deselect();
} else {
utils.showAlert("error", "", "Error while adding new group", response.message);
}
@@ -246,32 +373,3 @@ function editGroup() {
},
});
}
-
-function deleteGroup() {
- var tr = $(this).closest("tr");
- var id = tr.attr("data-id");
- var name = utils.escapeHtml(tr.find("#name_" + id).val());
-
- utils.disableAll();
- utils.showAlert("info", "", "Deleting group...", name);
- $.ajax({
- url: "scripts/pi-hole/php/groups.php",
- method: "post",
- dataType: "json",
- data: { action: "delete_group", id: id, token: token },
- success: function (response) {
- utils.enableAll();
- if (response.success) {
- utils.showAlert("success", "far fa-trash-alt", "Successfully deleted group ", name);
- table.row(tr).remove().draw(false);
- } else {
- utils.showAlert("error", "", "Error while deleting group with ID " + id, response.message);
- }
- },
- error: function (jqXHR, exception) {
- utils.enableAll();
- utils.showAlert("error", "", "Error while deleting group with ID " + id, jqXHR.responseText);
- console.log(exception); // eslint-disable-line no-console
- },
- });
-}
diff --git a/scripts/pi-hole/js/index.js b/scripts/pi-hole/js/index.js
index 9849d7aa1..b3d7d18a9 100644
--- a/scripts/pi-hole/js/index.js
+++ b/scripts/pi-hole/js/index.js
@@ -223,8 +223,8 @@ function updateQueriesOverTime() {
// convert received objects to arrays
data.domains_over_time = utils.objectToArray(data.domains_over_time);
data.ads_over_time = utils.objectToArray(data.ads_over_time);
- // remove last data point since it not representative
- data.ads_over_time[0].splice(-1, 1);
+ // remove last data point for line charts as it is not representative there
+ if (utils.getGraphType() === "line") data.ads_over_time[0].splice(-1, 1);
// Remove possibly already existing data
timeLineChart.data.labels = [];
timeLineChart.data.datasets = [];
@@ -441,8 +441,8 @@ function updateClientsOverTime() {
// convert received objects to arrays
data.over_time = utils.objectToArray(data.over_time);
- // remove last data point since it not representative
- data.over_time[0].splice(-1, 1);
+ // remove last data point for line charts as it is not representative there
+ if (utils.getGraphType() === "line") data.over_time[0].splice(-1, 1);
var timestamps = data.over_time[0];
var plotdata = data.over_time[1];
var labels = [];
@@ -538,8 +538,14 @@ function updateForwardDestinationsPie() {
values.push([key, value, THEME_COLORS[i++ % THEME_COLORS.length]]);
});
- // Move the "other" element to the end of the array
- values.push(values.splice(values.indexOf("other"), 1)[0]);
+ // Show "Other" destination as the last graphic item and only if it's different than zero
+ var other = values.splice(
+ values.findIndex(arr => arr.includes("other")),
+ 1
+ )[0];
+ if (other[1] !== 0) {
+ values.push(other);
+ }
// Split data into individual arrays for the graphs
values.forEach(function (value) {
@@ -961,13 +967,21 @@ $(function () {
ticks: {
beginAtZero: true,
fontColor: ticksColor,
+ precision: 0,
},
gridLines: {
color: gridColor,
+ zeroLineColor: gridColor,
},
},
],
},
+ elements: {
+ line: {
+ borderWidth: 0,
+ spanGaps: false,
+ },
+ },
maintainAspectRatio: false,
},
});
@@ -1039,14 +1053,22 @@ $(function () {
ticks: {
beginAtZero: true,
fontColor: ticksColor,
+ precision: 0,
},
stacked: true,
gridLines: {
color: gridColor,
+ zeroLineColor: gridColor,
},
},
],
},
+ elements: {
+ line: {
+ borderWidth: 0,
+ spanGaps: false,
+ },
+ },
maintainAspectRatio: false,
hover: {
animationDuration: 0,
diff --git a/scripts/pi-hole/js/queries.js b/scripts/pi-hole/js/queries.js
index 851efd945..18f09d729 100644
--- a/scripts/pi-hole/js/queries.js
+++ b/scripts/pi-hole/js/queries.js
@@ -125,12 +125,10 @@ $(function () {
case "2":
fieldtext =
replyid === 0
- ? "OK , sent to "
- : "OK , answered by ";
+ ? "OK (sent to "
+ : "OK (answered by ";
fieldtext +=
- " " +
- (data.length > 10 && data[10] !== "N/A" ? data[10] : "") +
- dnssecStatus;
+ (data.length > 10 && data[10] !== "N/A" ? data[10] : "") + ")" + dnssecStatus;
buttontext =
' Blacklist ';
break;
@@ -174,7 +172,8 @@ $(function () {
buttontext = "";
break;
case "9":
- fieldtext = "Blocked (gravity, CNAME) ";
+ fieldtext =
+ "Blocked (gravity, CNAME) ";
blocked = true;
buttontext =
' Whitelist ';
@@ -218,6 +217,11 @@ $(function () {
"Blocked (database is busy) ";
blocked = true;
break;
+ case "16":
+ fieldtext =
+ "Blocked (special domain) ";
+ blocked = true;
+ break;
default:
fieldtext = "Unknown (" + parseInt(data[4], 10) + ")";
}
diff --git a/scripts/pi-hole/js/queryads.js b/scripts/pi-hole/js/queryads.js
index a97eadd92..cb8625aca 100644
--- a/scripts/pi-hole/js/queryads.js
+++ b/scripts/pi-hole/js/queryads.js
@@ -53,7 +53,6 @@ function eventsource() {
return;
}
- // eslint-disable-next-line compat/compat
var source = new EventSource(
"scripts/pi-hole/php/queryads.php?domain=" + domain.toLowerCase() + "&" + exact
);
diff --git a/scripts/pi-hole/js/utils.js b/scripts/pi-hole/js/utils.js
index f6d2434a9..16aa44739 100644
--- a/scripts/pi-hole/js/utils.js
+++ b/scripts/pi-hole/js/utils.js
@@ -381,6 +381,33 @@ function checkMessages() {
});
}
+// Show only the appropriate delete buttons in datatables
+function changeBulkDeleteStates(table) {
+ var allRows = table.rows({ filter: "applied" }).data().length;
+ var pageLength = table.page.len();
+ var selectedRows = table.rows(".selected").data().length;
+
+ if (selectedRows === 0) {
+ // Nothing selected
+ $(".selectAll").removeClass("hidden");
+ $(".selectMore").addClass("hidden");
+ $(".removeAll").addClass("hidden");
+ $(".deleteSelected").addClass("hidden");
+ } else if (selectedRows >= pageLength || selectedRows === allRows) {
+ // Whole page is selected (or all available messages were selected)
+ $(".selectAll").addClass("hidden");
+ $(".selectMore").addClass("hidden");
+ $(".removeAll").removeClass("hidden");
+ $(".deleteSelected").removeClass("hidden");
+ } else {
+ // Some rows are selected, but not all
+ $(".selectAll").addClass("hidden");
+ $(".selectMore").removeClass("hidden");
+ $(".removeAll").addClass("hidden");
+ $(".deleteSelected").removeClass("hidden");
+ }
+}
+
window.utils = (function () {
return {
escapeHtml: escapeHtml,
@@ -404,5 +431,6 @@ window.utils = (function () {
addTD: addTD,
colorBar: colorBar,
checkMessages: checkMessages,
+ changeBulkDeleteStates: changeBulkDeleteStates,
};
})();
diff --git a/scripts/pi-hole/php/FTL.php b/scripts/pi-hole/php/FTL.php
index 48f81c3f4..d4a249567 100644
--- a/scripts/pi-hole/php/FTL.php
+++ b/scripts/pi-hole/php/FTL.php
@@ -9,6 +9,7 @@
const DEFAULT_FTLCONFFILE = "/etc/pihole/pihole-FTL.conf";
const DEFAULT_FTL_IP = "127.0.0.1";
const DEFAULT_FTL_PORT = 4711;
+const DEFAULT_FTL_PORTFILE = "/run/pihole-FTL.port";
function piholeFTLConfig($piholeFTLConfFile = DEFAULT_FTLCONFFILE, $force = false) {
static $piholeFTLConfig;
@@ -30,7 +31,7 @@ function connectFTL($address, $port) {
if ($address == DEFAULT_FTL_IP) {
$config = piholeFTLConfig();
// Read port
- $portfileName = isset($config['PORTFILE']) ? $config['PORTFILE'] : '';
+ $portfileName = isset($config['PORTFILE']) ? $config['PORTFILE'] : DEFAULT_FTL_PORTFILE;
if ($portfileName != '') {
$portfileContents = file_get_contents($portfileName);
if (is_numeric($portfileContents)) {
@@ -79,9 +80,9 @@ function getResponseFTL($socket) {
}
function disconnectFTL($socket) {
- if (is_resource($socket)) {
- fclose($socket);
- }
+ if (is_resource($socket)) {
+ fclose($socket);
+ }
}
function callFTLAPI($request, $FTL_IP = DEFAULT_FTL_IP, $port = DEFAULT_FTL_PORT) {
diff --git a/scripts/pi-hole/php/auth.php b/scripts/pi-hole/php/auth.php
index fbf6b76ca..993b057b4 100644
--- a/scripts/pi-hole/php/auth.php
+++ b/scripts/pi-hole/php/auth.php
@@ -9,7 +9,7 @@
require_once('func.php');
$ERRORLOG = getenv('PHP_ERROR_LOG');
if (empty($ERRORLOG)) {
- $ERRORLOG = '/var/log/lighttpd/error.log';
+ $ERRORLOG = '/var/log/lighttpd/error-pihole.log';
if (!file_exists($ERRORLOG) || !is_writable($ERRORLOG)) {
$ERRORLOG = '/var/log/apache2/error.log';
@@ -30,14 +30,11 @@ function log_and_die($message) {
}
function check_cors() {
- $setupVars = parse_ini_file("/etc/pihole/setupVars.conf");
- $ipv4 = isset($setupVars["IPV4_ADDRESS"]) ? explode("/", $setupVars["IPV4_ADDRESS"])[0] : $_SERVER['SERVER_ADDR'];
- $ipv6 = isset($setupVars["IPV6_ADDRESS"]) ? explode("/", $setupVars["IPV6_ADDRESS"])[0] : $_SERVER['SERVER_ADDR'];
+ $ip = $_SERVER['SERVER_ADDR'];
// Check CORS
$AUTHORIZED_HOSTNAMES = array(
- $ipv4,
- $ipv6,
+ $ip,
str_replace(array("[","]"), array("",""), $_SERVER["SERVER_NAME"]),
"pi.hole",
"localhost"
diff --git a/scripts/pi-hole/php/func.php b/scripts/pi-hole/php/func.php
index 8724b06a5..bede54eb9 100644
--- a/scripts/pi-hole/php/func.php
+++ b/scripts/pi-hole/php/func.php
@@ -137,9 +137,8 @@ function hash_equals($known_string, $user_string) {
* and returns output of that command as a string.
*
* @param $argument_string String of arguments to run pihole with.
- * @param $error_on_failure If true, a warning is raised if command execution fails. Defaults to true.
*/
-function pihole_execute($argument_string, $error_on_failure = true) {
+function pihole_execute($argument_string) {
$escaped = escapeshellcmd($argument_string);
$output = null;
$return_status = -1;
@@ -286,7 +285,7 @@ function deleteAllCustomDNSEntries($reload="")
$reload = $_REQUEST['reload'];
$existingEntries = getCustomDNSEntries();
- // passing false to pihole_execute stops pihole from reloading after each enty has been deleted
+ // passing false to pihole_execute stops pihole from reloading after each entry has been deleted
foreach ($existingEntries as $entry) {
pihole_execute("-a removecustomdns ".$entry->ip." ".$entry->domain." ".$reload);
}
@@ -438,7 +437,7 @@ function deleteAllCustomCNAMEEntries($reload="")
$reload = $_REQUEST['reload'];
$existingEntries = getCustomCNAMEEntries();
- // passing false to pihole_execute stops pihole from reloading after each enty has been deleted
+ // passing false to pihole_execute stops pihole from reloading after each entry has been deleted
foreach ($existingEntries as $entry) {
pihole_execute("-a removecustomcname ".$entry->domain." ".$entry->target." ".$reload);
}
@@ -475,7 +474,7 @@ function returnError($message = "", $json = true)
function getQueryTypeStr($querytype)
{
- $qtypes = ["A (IPv4)", "AAAA (IPv6)", "ANY", "SRV", "SOA", "PTR", "TXT", "NAPTR", "MX", "DS", "RRSIG", "DNSKEY", "NS", "OTHER", "SVCB", "HTTPS"];
+ $qtypes = ["A", "AAAA", "ANY", "SRV", "SOA", "PTR", "TXT", "NAPTR", "MX", "DS", "RRSIG", "DNSKEY", "NS", "OTHER", "SVCB", "HTTPS"];
$qtype = intval($querytype);
if($qtype > 0 && $qtype <= count($qtypes))
return $qtypes[$qtype-1];
@@ -483,6 +482,37 @@ function getQueryTypeStr($querytype)
return "TYPE".($qtype - 100);
}
+// Functions to return Alert messages (success, error, warning) in JSON format.
+// Used in multiple pages.
+
+// Return Success message in JSON format
+function JSON_success($message = null) {
+ header('Content-type: application/json');
+ echo json_encode(array('success' => true, 'message' => $message));
+}
+
+// Return Error message in JSON format
+function JSON_error($message = null) {
+ header('Content-type: application/json');
+ $response = array('success' => false, 'message' => $message);
+ if (isset($_POST['action'])) {
+ array_push($response, array('action' => $_POST['action']));
+ }
+ echo json_encode($response);
+}
+
+// Return Warning message in JSON format.
+// - sends "success", because it wasn't a failure.
+// - sends "warning" to use the correct alert type.
+function JSON_warning($message = null) {
+ header('Content-type: application/json');
+ echo json_encode(array(
+ 'success' => true,
+ 'warning' => true,
+ 'message' => $message,
+ ));
+}
+
// Returns an integer representing pihole blocking status
function piholeStatus() {
// Retrieve DNS Port calling FTL API directly
@@ -513,4 +543,15 @@ function piholeStatus() {
return $ret;
}
+
+//Returns the default gateway address and interface
+function getGateway() {
+ $gateway= callFTLAPI("gateway");
+ if (array_key_exists("FTLnotrunning", $gateway)) {
+ $ret = array("ip" => -1);
+ } else {
+ $ret = array_combine(["ip", "iface"], explode(" ", $gateway[0]));
+ }
+ return $ret;
+}
?>
diff --git a/scripts/pi-hole/php/groups.php b/scripts/pi-hole/php/groups.php
index b57258f74..da95aabbf 100644
--- a/scripts/pi-hole/php/groups.php
+++ b/scripts/pi-hole/php/groups.php
@@ -7,6 +7,8 @@
* Please see LICENSE file for your rights under this license. */
require_once('auth.php');
+require_once('func.php');
+require_once('database.php');
// Authentication checks
if (!isset($api)) {
@@ -20,35 +22,21 @@
$reload = false;
-require_once('func.php');
-require_once('database.php');
$GRAVITYDB = getGravityDBFilename();
$db = SQLite3_connect($GRAVITYDB, SQLITE3_OPEN_READWRITE);
-function JSON_success($message = null)
-{
- header('Content-type: application/json');
- echo json_encode(array('success' => true, 'message' => $message));
-}
-
-function JSON_warning($message = null)
+function verify_ID_array($arr)
{
- header('Content-type: application/json');
- echo json_encode(array(
- 'success' => true,
- 'warning' => true,
- 'message' => $message,
- ));
-}
+ if (!is_array($arr)) {
+ throw new Exception('Invalid payload: id is not an array');
+ }
-function JSON_error($message = null)
-{
- header('Content-type: application/json');
- $response = array('success' => false, 'message' => $message);
- if (isset($_POST['action'])) {
- array_push($response, array('action' => $_POST['action']));
+ // Exploit prevention: Ensure all entries in the ID array are integers
+ foreach ($arr as $value) {
+ if (!is_numeric($value)) {
+ throw new Exception('Invalid payload: id contains non-numeric entries');
+ }
}
- echo json_encode($response);
}
if ($_POST['action'] == 'get_groups') {
@@ -153,21 +141,23 @@ function JSON_error($message = null)
} elseif ($_POST['action'] == 'delete_group') {
// Delete group identified by ID
try {
- $table_name = ['domainlist_by_group', 'client_by_group', 'adlist_by_group', 'group'];
+ $ids = json_decode($_POST['id']);
+
+ // Exploit prevention: Ensure all entries in the ID array are integers
+ verify_ID_array($ids);
+
+ $table_name = ['domainlist_by_group', 'client_by_group', 'adlist_by_group', '"group"']; //quote reserved word
$table_keys = ['group_id', 'group_id', 'group_id', 'id'];
+
for ($i = 0; $i < count($table_name); $i++) {
$table = $table_name[$i];
$key = $table_keys[$i];
- $stmt = $db->prepare("DELETE FROM \"$table\" WHERE $key = :id;");
+ $stmt = $db->prepare("DELETE FROM ".$table." WHERE ".$key." IN (".implode(",",$ids).")");
if (!$stmt) {
throw new Exception("While preparing DELETE FROM $table statement: " . $db->lastErrorMsg());
}
- if (!$stmt->bindValue(':id', intval($_POST['id']), SQLITE3_INTEGER)) {
- throw new Exception("While binding id to DELETE FROM $table statement: " . $db->lastErrorMsg());
- }
-
if (!$stmt->execute()) {
throw new Exception("While executing DELETE FROM $table statement: " . $db->lastErrorMsg());
}
@@ -463,30 +453,29 @@ function JSON_error($message = null)
} elseif ($_POST['action'] == 'delete_client') {
// Delete client identified by ID
try {
+ $ids = json_decode($_POST['id']);
+
+ // Exploit prevention: Ensure all entries in the ID array are integers
+ verify_ID_array($ids);
+
$db->query('BEGIN TRANSACTION;');
- $stmt = $db->prepare('DELETE FROM client_by_group WHERE client_id=:id');
+ // Delete from: client_by_group
+ $stmt = $db->prepare('DELETE FROM client_by_group WHERE client_id IN ('.implode(",",$ids).')');
if (!$stmt) {
throw new Exception('While preparing client_by_group statement: ' . $db->lastErrorMsg());
}
- if (!$stmt->bindValue(':id', intval($_POST['id']), SQLITE3_INTEGER)) {
- throw new Exception('While binding id to client_by_group statement: ' . $db->lastErrorMsg());
- }
-
if (!$stmt->execute()) {
throw new Exception('While executing client_by_group statement: ' . $db->lastErrorMsg());
}
- $stmt = $db->prepare('DELETE FROM client WHERE id=:id');
+ // Delete from: client
+ $stmt = $db->prepare('DELETE FROM client WHERE id IN ('.implode(",",$ids).')');
if (!$stmt) {
throw new Exception('While preparing client statement: ' . $db->lastErrorMsg());
}
- if (!$stmt->bindValue(':id', intval($_POST['id']), SQLITE3_INTEGER)) {
- throw new Exception('While binding id to client statement: ' . $db->lastErrorMsg());
- }
-
if (!$stmt->execute()) {
throw new Exception('While executing client statement: ' . $db->lastErrorMsg());
}
@@ -651,15 +640,9 @@ function JSON_error($message = null)
}
}
- if(isset($_POST['type']) && strlen($_POST['type']) === 2 && $_POST['type'][1] === 'W')
- {
- // Apply wildcard-style formatting
- $domain = "(\\.|^)".str_replace(".","\\.",$domain)."$";
- }
-
- if($type === ListType::whitelist || $type === ListType::blacklist)
+ if( $_POST['type'] != '2' && $_POST['type'] != '3')
{
- // If adding to the exact lists, we convert the domain lower case and check whether it is valid
+ // If not adding a RegEx, we convert the domain lower case and check whether it is valid
$domain = strtolower($domain);
$msg = "";
if(!validDomain($domain, $msg))
@@ -675,6 +658,12 @@ function JSON_error($message = null)
}
}
+ if(isset($_POST['type']) && strlen($_POST['type']) === 2 && $_POST['type'][1] === 'W')
+ {
+ // Apply wildcard-style formatting
+ $domain = "(\\.|^)".str_replace(".","\\.",$domain)."$";
+ }
+
// First try to delete any occurrences of this domain if we're in
// replace mode. Only do this when the domain to be replaced is in
// the default group! Otherwise, we would shuffle group settings and
@@ -721,7 +710,7 @@ function JSON_error($message = null)
// First execute INSERT OR IGNORE statement to create a record for
// this domain (ignore if already existing)
if (!$insert_stmt->execute()) {
- throw new Exception('While executing INSERT OT IGNORE: ' . $db->lastErrorMsg() . ' '.
+ throw new Exception('While executing INSERT OR IGNORE: ' . $db->lastErrorMsg() . ' '.
'Added ' . $added . " out of ". $total . " domains");
}
@@ -847,30 +836,29 @@ function JSON_error($message = null)
} elseif ($_POST['action'] == 'delete_domain') {
// Delete domain identified by ID
try {
+ $ids = json_decode($_POST['id']);
+
+ // Exploit prevention: Ensure all entries in the ID array are integers
+ verify_ID_array($ids);
+
$db->query('BEGIN TRANSACTION;');
- $stmt = $db->prepare('DELETE FROM domainlist_by_group WHERE domainlist_id=:id');
+ // Delete from: domainlist_by_group
+ $stmt = $db->prepare('DELETE FROM domainlist_by_group WHERE domainlist_id IN ('.implode(",",$ids).')');
if (!$stmt) {
throw new Exception('While preparing domainlist_by_group statement: ' . $db->lastErrorMsg());
}
- if (!$stmt->bindValue(':id', intval($_POST['id']), SQLITE3_INTEGER)) {
- throw new Exception('While binding id to domainlist_by_group statement: ' . $db->lastErrorMsg());
- }
-
if (!$stmt->execute()) {
throw new Exception('While executing domainlist_by_group statement: ' . $db->lastErrorMsg());
}
- $stmt = $db->prepare('DELETE FROM domainlist WHERE id=:id');
+ // Delete from: domainlist
+ $stmt = $db->prepare('DELETE FROM domainlist WHERE id IN ('.implode(",",$ids).')');
if (!$stmt) {
throw new Exception('While preparing domainlist statement: ' . $db->lastErrorMsg());
}
- if (!$stmt->bindValue(':id', intval($_POST['id']), SQLITE3_INTEGER)) {
- throw new Exception('While binding id to domainlist statement: ' . $db->lastErrorMsg());
- }
-
if (!$stmt->execute()) {
throw new Exception('While executing domainlist statement: ' . $db->lastErrorMsg());
}
@@ -1008,7 +996,7 @@ function JSON_error($message = null)
if (!$stmt->execute()) {
if ($db->lastErrorCode() == 19) {
- // ErrorCode 19 is "Constraint violation", here the unique constraint of `address`
+ // ErrorCode 19 is "Constraint violation", here the unique constraint of `address`
// is violated (https://www.sqlite.org/rescode.html#constraint).
// If the list is already in database, add to ignored list, but don't throw error
$ignored++;
@@ -1128,30 +1116,31 @@ function JSON_error($message = null)
} elseif ($_POST['action'] == 'delete_adlist') {
// Delete adlist identified by ID
try {
+ // Accept only an array
+ $ids = json_decode($_POST['id']);
+
+ // Exploit prevention: Ensure all entries in the ID array are integers
+ verify_ID_array($ids);
+
$db->query('BEGIN TRANSACTION;');
- $stmt = $db->prepare('DELETE FROM adlist_by_group WHERE adlist_id=:id');
+ // Delete from: adlists_by_group
+ $stmt = $db->prepare('DELETE FROM adlist_by_group WHERE adlist_id IN ('.implode(",",$ids).')');
+
if (!$stmt) {
throw new Exception('While preparing adlist_by_group statement: ' . $db->lastErrorMsg());
}
- if (!$stmt->bindValue(':id', intval($_POST['id']), SQLITE3_INTEGER)) {
- throw new Exception('While binding id to adlist_by_group statement: ' . $db->lastErrorMsg());
- }
-
if (!$stmt->execute()) {
throw new Exception('While executing adlist_by_group statement: ' . $db->lastErrorMsg());
}
- $stmt = $db->prepare('DELETE FROM adlist WHERE id=:id');
+ // Delete from: adlists
+ $stmt = $db->prepare('DELETE FROM adlist WHERE id IN ('.implode(",",$ids).')');
if (!$stmt) {
throw new Exception('While preparing adlist statement: ' . $db->lastErrorMsg());
}
- if (!$stmt->bindValue(':id', intval($_POST['id']), SQLITE3_INTEGER)) {
- throw new Exception('While binding id to adlist statement: ' . $db->lastErrorMsg());
- }
-
if (!$stmt->execute()) {
throw new Exception('While executing adlist statement: ' . $db->lastErrorMsg());
}
diff --git a/scripts/pi-hole/php/header.php b/scripts/pi-hole/php/header.php
index 15a61e8ea..a6de08bea 100644
--- a/scripts/pi-hole/php/header.php
+++ b/scripts/pi-hole/php/header.php
@@ -590,7 +590,7 @@ function pidofFTL()
class="active">
-
-
+
class="active">
-
diff --git a/scripts/pi-hole/php/message.php b/scripts/pi-hole/php/message.php
index 67132ece7..f82841929 100644
--- a/scripts/pi-hole/php/message.php
+++ b/scripts/pi-hole/php/message.php
@@ -25,29 +25,13 @@
$QueriesDB = getQueriesDBFilename();
$db = SQLite3_connect($QueriesDB, SQLITE3_OPEN_READWRITE);
-function JSON_success($message = null)
-{
- header('Content-type: application/json');
- echo json_encode(array('success' => true, 'message' => $message));
-}
-
-function JSON_error($message = null)
-{
- header('Content-type: application/json');
- $response = array('success' => false, 'message' => $message);
- if (isset($_POST['action'])) {
- array_push($response, array('action' => $_POST['action']));
- }
- echo json_encode($response);
-}
-
// Delete message identified by IDs
if ($_POST['action'] == 'delete_message' && isset($_POST['id'])) {
try {
$ids = json_decode($_POST['id']);
if(!is_array($ids))
throw new Exception('Invalid payload: id is not an array');
- // Explot prevention: Ensure all entries in the ID array are integers
+ // Exploit prevention: Ensure all entries in the ID array are integers
foreach($ids as $value) {
if (!is_numeric($value))
throw new Exception('Invalid payload: id contains non-numeric entries');
diff --git a/scripts/pi-hole/php/network.php b/scripts/pi-hole/php/network.php
index 4709d283e..02af7b2d2 100644
--- a/scripts/pi-hole/php/network.php
+++ b/scripts/pi-hole/php/network.php
@@ -1,4 +1,3 @@
-
true, 'message' => $message));
-}
-
-function JSON_error($message = null)
-{
- header('Content-type: application/json');
- $response = array('success' => false, 'message' => $message);
- if (isset($_POST['action'])) {
- array_push($response, array('action' => $_POST['action']));
- }
- echo json_encode($response);
-}
-
if ($_POST['action'] == 'delete_network_entry' && isset($_POST['id'])) {
// Delete netwwork and network_addresses table entry identified by ID
try {
diff --git a/scripts/pi-hole/php/queryads.php b/scripts/pi-hole/php/queryads.php
index 9818f2698..084787dbf 100644
--- a/scripts/pi-hole/php/queryads.php
+++ b/scripts/pi-hole/php/queryads.php
@@ -27,9 +27,8 @@ function echoEvent($datatext) {
{
// Is this a valid domain?
$url = idn_to_ascii($_GET["domain"]);
- if(!validDomain($url))
- {
- echoEvent("$url is an invalid domain!");
+ if (!validDomain($url)) {
+ echoEvent(htmlentities($url)." is an invalid domain!");
die();
}
}
diff --git a/scripts/pi-hole/php/savesettings.php b/scripts/pi-hole/php/savesettings.php
index 6b502d6aa..60da6b019 100644
--- a/scripts/pi-hole/php/savesettings.php
+++ b/scripts/pi-hole/php/savesettings.php
@@ -187,572 +187,559 @@ function addStaticDHCPLease($mac, $ip, $hostname) {
}
}
- // Read available DNS server list
- $DNSserverslist = readDNSserversList();
+// Read available DNS server list
+$DNSserverslist = readDNSserversList();
- $error = "";
- $success = "";
+$error = "";
+$success = "";
- if(isset($_POST["field"]))
- {
- // Handle CSRF
- check_csrf(isset($_POST["token"]) ? $_POST["token"] : "");
-
- // Process request
- switch ($_POST["field"]) {
- // Set DNS server
- case "DNS":
-
- $DNSservers = [];
- // Add selected predefined servers to list
- foreach ($DNSserverslist as $key => $value)
- {
- foreach(["v4_1", "v4_2", "v6_1", "v6_2"] as $type)
- {
- // Skip if this IP type does not
- // exist (e.g. IPv4-only or only
- // one IPv6 address upstream
- // server)
- if(!array_key_exists($type, $value))
- continue;
-
- // If server exists and is set
- // (POST), we add it to the
- // array of DNS servers
- $server = str_replace(".", "_", $value[$type]);
- if(array_key_exists("DNSserver".$server, $_POST))
- array_push($DNSservers, $value[$type]);
- }
- }
-
- // Test custom server fields
- for($i=1;$i<=4;$i++)
- {
- if(array_key_exists("custom".$i,$_POST))
- {
- $exploded = explode("#", $_POST["custom".$i."val"], 2);
- $IP = trim($exploded[0]);
-
- if(!validIP($IP))
- {
- $error .= "IP (".htmlspecialchars($IP).") is invalid! ";
- }
- else
- {
- if(count($exploded) > 1)
- {
- $port = trim($exploded[1]);
- if(!is_numeric($port))
- {
- $error .= "Port (".htmlspecialchars($port).") is invalid! ";
- }
- else
- {
- $IP .= "#".$port;
- }
- }
-
- array_push($DNSservers,$IP);
- }
- }
- }
- $DNSservercount = count($DNSservers);
-
- // Check if at least one DNS server has been added
- if($DNSservercount < 1)
- {
- $error .= "No DNS server has been selected. ";
- }
-
- // Check if domain-needed is requested
- if(isset($_POST["DNSrequiresFQDN"]))
- {
- $extra = "domain-needed ";
- }
- else
- {
- $extra = "domain-not-needed ";
- }
-
- // Check if domain-needed is requested
- if(isset($_POST["DNSbogusPriv"]))
- {
- $extra .= "bogus-priv ";
- }
- else
- {
- $extra .= "no-bogus-priv ";
- }
-
- // Check if DNSSEC is requested
- if(isset($_POST["DNSSEC"]))
- {
- $extra .= "dnssec";
- }
- else
- {
- $extra .= "no-dnssec";
- }
-
- // Check if rev-server is requested
- if(isset($_POST["rev_server"]))
- {
- // Validate CIDR IP
- $cidr = trim($_POST["rev_server_cidr"]);
- if (!validCIDRIP($cidr))
- {
- $error .= "Conditional forwarding subnet (\"".htmlspecialchars($cidr)."\") is invalid! ".
- "This field requires CIDR notation for local subnets (e.g., 192.168.0.0/16). ";
- }
-
- // Validate target IP
- $target = trim($_POST["rev_server_target"]);
- if (!validIP($target))
- {
- $error .= "Conditional forwarding target IP (\"".htmlspecialchars($target)."\") is invalid! ";
- }
-
- // Validate conditional forwarding domain name (empty is okay)
- $domain = trim($_POST["rev_server_domain"]);
- if(strlen($domain) > 0 && !validDomain($domain))
- {
- $error .= "Conditional forwarding domain name (\"".htmlspecialchars($domain)."\") is invalid! ";
- }
-
- if(!$error)
- {
- $extra .= " rev-server ".$cidr." ".$target." ".$domain;
- }
- }
-
- // Check if DNSinterface is set
- if(isset($_POST["DNSinterface"]))
- {
- if($_POST["DNSinterface"] === "single")
- {
- $DNSinterface = "single";
- }
- elseif($_POST["DNSinterface"] === "bind")
- {
- $DNSinterface = "bind";
- }
- elseif($_POST["DNSinterface"] === "all")
- {
- $DNSinterface = "all";
- }
- else
- {
- $DNSinterface = "local";
- }
- }
- else
- {
- // Fallback
- $DNSinterface = "local";
- }
- pihole_execute("-a -i ".$DNSinterface." -web");
-
- // Add rate-limiting settings
- if(isset($_POST["rate_limit_count"]) && isset($_POST["rate_limit_interval"]))
- {
- // Restart of FTL is delayed
- pihole_execute("-a ratelimit " . intval($_POST["rate_limit_count"]) . " " . intval($_POST["rate_limit_interval"]) . " false");
- }
-
- // If there has been no error we can save the new DNS server IPs
- if(!strlen($error))
- {
- $IPs = implode (",", $DNSservers);
- $return = pihole_execute("-a setdns \"".$IPs."\" ".$extra);
- $success .= htmlspecialchars(end($return))." ";
- $success .= "The DNS settings have been updated (using ".$DNSservercount." DNS servers)";
- }
- else
- {
- $error .= "The settings have been reset to their previous values";
- }
-
- break;
-
- // Set query logging
- case "Logging":
-
- if($_POST["action"] === "Disable")
- {
- pihole_execute("-l off");
- $success .= "Logging has been disabled and logs have been flushed";
- }
- elseif($_POST["action"] === "Disable-noflush")
- {
- pihole_execute("-l off noflush");
- $success .= "Logging has been disabled, your logs have not been flushed";
- }
- else
- {
- pihole_execute("-l on");
- $success .= "Logging has been enabled";
- }
-
- break;
-
- // Set domains to be excluded from being shown in Top Domains (or Ads) and Top Clients
- case "API":
-
- // Explode the contents of the textareas into PHP arrays
- // \n (Unix) and \r\n (Win) will be considered as newline
- // array_filter( ... ) will remove any empty lines
- $domains = array_filter(preg_split('/\r\n|[\r\n]/', $_POST["domains"]));
- $clients = array_filter(preg_split('/\r\n|[\r\n]/', $_POST["clients"]));
-
- $domainlist = "";
- $first = true;
- foreach($domains as $domain)
- {
- if(!validDomainWildcard($domain) || validIP($domain))
- {
- $error .= "Top Domains/Ads entry ".htmlspecialchars($domain)." is invalid (use only domains)! ";
- }
- if(!$first)
- {
- $domainlist .= ",";
- }
- else
- {
- $first = false;
- }
- $domainlist .= $domain;
- }
-
- $clientlist = "";
- $first = true;
- foreach($clients as $client)
- {
- if(!validDomainWildcard($client) && !validIP($client))
- {
- $error .= "Top Clients entry ".htmlspecialchars($client)." is invalid (use only host names and IP addresses)! ";
- }
- if(!$first)
- {
- $clientlist .= ",";
- }
- else
- {
- $first = false;
- }
- $clientlist .= $client;
- }
-
- // Set Top Lists options
- if(!strlen($error))
- {
- // All entries are okay
- pihole_execute("-a setexcludedomains ".$domainlist);
- pihole_execute("-a setexcludeclients ".$clientlist);
- $success .= "The API settings have been updated ";
- }
- else
- {
- $error .= "The settings have been reset to their previous values";
- }
-
- // Set query log options
- if(isset($_POST["querylog-permitted"]) && isset($_POST["querylog-blocked"]))
- {
- pihole_execute("-a setquerylog all");
- $success .= "All entries will be shown in Query Log";
- }
- elseif(isset($_POST["querylog-permitted"]))
- {
- pihole_execute("-a setquerylog permittedonly");
- $success .= "Only permitted will be shown in Query Log";
- }
- elseif(isset($_POST["querylog-blocked"]))
- {
- pihole_execute("-a setquerylog blockedonly");
- $success .= "Only blocked entries will be shown in Query Log";
- }
- else
- {
- pihole_execute("-a setquerylog nothing");
- $success .= "No entries will be shown in Query Log";
- }
-
- break;
-
- case "speedtest":
-
- if (isset($_POST["speedtestmode"])) {
- exec('sudo pihole -a -sm ' . $_POST["speedtestmode"]);
- } else {
- // # code...
- }
-
- if (isset($_POST["speedtestschedule"])) {
- exec('sudo pihole -a -s ' . $_POST["speedtestschedule"]);
- } else {
- // # code...
+if(isset($_POST["field"]))
+{
+ // Handle CSRF
+ check_csrf(isset($_POST["token"]) ? $_POST["token"] : "");
+
+ // Process request
+ switch ($_POST["field"]) {
+ // Set DNS server
+ case "DNS":
+
+ $DNSservers = [];
+ // Add selected predefined servers to list
+ foreach ($DNSserverslist as $key => $value)
+ {
+ foreach(["v4_1", "v4_2", "v6_1", "v6_2"] as $type)
+ {
+ // Skip if this IP type does not
+ // exist (e.g. IPv4-only or only
+ // one IPv6 address upstream
+ // server)
+ if(!array_key_exists($type, $value))
+ continue;
+
+ // If server exists and is set
+ // (POST), we add it to the
+ // array of DNS servers
+ $server = str_replace(".", "_", $value[$type]);
+ if(array_key_exists("DNSserver".$server, $_POST))
+ array_push($DNSservers, $value[$type]);
}
-
- if (isset($_POST["clearspeedtests"])) {
- if ($_POST["clearspeedtests"] == "yes")
- exec('sudo pihole -a -sc');
- } else {
- // # code...
+ }
+
+ // Test custom server fields
+ for($i=1;$i<=4;$i++)
+ {
+ if(array_key_exists("custom".$i,$_POST))
+ {
+ $exploded = explode("#", $_POST["custom".$i."val"], 2);
+ $IP = trim($exploded[0]);
+
+ if(!validIP($IP))
+ {
+ $error .= "IP (".htmlspecialchars($IP).") is invalid! ";
+ }
+ else
+ {
+ if(count($exploded) > 1)
+ {
+ $port = trim($exploded[1]);
+ if(!is_numeric($port))
+ {
+ $error .= "Port (".htmlspecialchars($port).") is invalid! ";
+ }
+ else
+ {
+ $IP .= "#".$port;
+ }
+ }
+
+ array_push($DNSservers,$IP);
+ }
+ }
+ }
+ $DNSservercount = count($DNSservers);
+
+ // Check if at least one DNS server has been added
+ if($DNSservercount < 1)
+ {
+ $error .= "No DNS server has been selected. ";
+ }
+
+ // Check if domain-needed is requested
+ if(isset($_POST["DNSrequiresFQDN"]))
+ {
+ $extra = "domain-needed ";
+ }
+ else
+ {
+ $extra = "domain-not-needed ";
+ }
+
+ // Check if domain-needed is requested
+ if(isset($_POST["DNSbogusPriv"]))
+ {
+ $extra .= "bogus-priv ";
+ }
+ else
+ {
+ $extra .= "no-bogus-priv ";
+ }
+
+ // Check if DNSSEC is requested
+ if(isset($_POST["DNSSEC"]))
+ {
+ $extra .= "dnssec";
+ }
+ else
+ {
+ $extra .= "no-dnssec";
+ }
+
+ // Check if rev-server is requested
+ if(isset($_POST["rev_server"]))
+ {
+ // Validate CIDR IP
+ $cidr = trim($_POST["rev_server_cidr"]);
+ if (!validCIDRIP($cidr))
+ {
+ $error .= "Conditional forwarding subnet (\"".htmlspecialchars($cidr)."\") is invalid! ".
+ "This field requires CIDR notation for local subnets (e.g., 192.168.0.0/16). ";
}
- if (isset($_POST["speedtestserver"]) && is_numeric($_POST["speedtestserver"])) {
- exec('sudo pihole -a -ss ' . $_POST["speedtestserver"]);
- } else {
- exec('sudo pihole -a -ss');
+ // Validate target IP
+ $target = trim($_POST["rev_server_target"]);
+ if (!validIP($target))
+ {
+ $error .= "Conditional forwarding target IP (\"".htmlspecialchars($target)."\") is invalid! ";
}
+ // Validate conditional forwarding domain name (empty is okay)
+ $domain = trim($_POST["rev_server_domain"]);
+ if(strlen($domain) > 0 && !validDomain($domain))
+ {
+ $error .= "Conditional forwarding domain name (\"".htmlspecialchars($domain)."\") is invalid! ";
+ }
- if (isset($_POST["speedtestdays"])) {
- exec('sudo pihole -a -sd ' . $_POST["speedtestdays"]);
- } else {
- // # code...
+ if(!$error)
+ {
+ $extra .= " rev-server ".$cidr." ".$target." ".$domain;
+ }
+ }
+
+ // Check if DNSinterface is set
+ if(isset($_POST["DNSinterface"]))
+ {
+ if($_POST["DNSinterface"] === "single")
+ {
+ $DNSinterface = "single";
+ }
+ elseif($_POST["DNSinterface"] === "bind")
+ {
+ $DNSinterface = "bind";
+ }
+ elseif($_POST["DNSinterface"] === "all")
+ {
+ $DNSinterface = "all";
+ }
+ else
+ {
+ $DNSinterface = "local";
+ }
+ }
+ else
+ {
+ // Fallback
+ $DNSinterface = "local";
+ }
+ pihole_execute("-a -i ".$DNSinterface." -web");
+
+ // Add rate-limiting settings
+ if(isset($_POST["rate_limit_count"]) && isset($_POST["rate_limit_interval"]))
+ {
+ // Restart of FTL is delayed
+ pihole_execute("-a ratelimit " . intval($_POST["rate_limit_count"]) . " " . intval($_POST["rate_limit_interval"]) . " false");
+ }
+
+ // If there has been no error we can save the new DNS server IPs
+ if(!strlen($error))
+ {
+ $IPs = implode (",", $DNSservers);
+ $return = pihole_execute("-a setdns \"".$IPs."\" ".$extra);
+ $success .= htmlspecialchars(end($return))." ";
+ $success .= "The DNS settings have been updated (using ".$DNSservercount." DNS servers)";
+ }
+ else
+ {
+ $error .= "The settings have been reset to their previous values";
+ }
+
+ break;
+
+ // Set query logging
+ case "Logging":
+
+ if($_POST["action"] === "Disable")
+ {
+ pihole_execute("-l off");
+ $success .= "Logging has been disabled and logs have been flushed";
+ }
+ elseif($_POST["action"] === "Disable-noflush")
+ {
+ pihole_execute("-l off noflush");
+ $success .= "Logging has been disabled, your logs have not been flushed";
+ }
+ else
+ {
+ pihole_execute("-l on");
+ $success .= "Logging has been enabled";
+ }
+
+ break;
+
+ // Set domains to be excluded from being shown in Top Domains (or Ads) and Top Clients
+ case "API":
+
+ // Explode the contents of the textareas into PHP arrays
+ // \n (Unix) and \r\n (Win) will be considered as newline
+ // array_filter( ... ) will remove any empty lines
+ $domains = array_filter(preg_split('/\r\n|[\r\n]/', $_POST["domains"]));
+ $clients = array_filter(preg_split('/\r\n|[\r\n]/', $_POST["clients"]));
+
+ $domainlist = "";
+ $first = true;
+ foreach($domains as $domain)
+ {
+ if(!validDomainWildcard($domain) || validIP($domain))
+ {
+ $error .= "Top Domains/Ads entry ".htmlspecialchars($domain)." is invalid (use only domains)! ";
+ }
+ if(!$first)
+ {
+ $domainlist .= ",";
+ }
+ else
+ {
+ $first = false;
}
+ $domainlist .= $domain;
+ }
+
+ $clientlist = "";
+ $first = true;
+ foreach($clients as $client)
+ {
+ if(!validDomainWildcard($client) && !validIP($client))
+ {
+ $error .= "Top Clients entry ".htmlspecialchars($client)." is invalid (use only host names and IP addresses)! ";
+ }
+ if(!$first)
+ {
+ $clientlist .= ",";
+ }
+ else
+ {
+ $first = false;
+ }
+ $clientlist .= $client;
+ }
+
+ // Set Top Lists options
+ if(!strlen($error))
+ {
+ // All entries are okay
+ pihole_execute("-a setexcludedomains ".$domainlist);
+ pihole_execute("-a setexcludeclients ".$clientlist);
+ $success .= "The API settings have been updated ";
+ }
+ else
+ {
+ $error .= "The settings have been reset to their previous values";
+ }
+
+ // Set query log options
+ if(isset($_POST["querylog-permitted"]) && isset($_POST["querylog-blocked"]))
+ {
+ pihole_execute("-a setquerylog all");
+ $success .= "All entries will be shown in Query Log";
+ }
+ elseif(isset($_POST["querylog-permitted"]))
+ {
+ pihole_execute("-a setquerylog permittedonly");
+ $success .= "Only permitted will be shown in Query Log";
+ }
+ elseif(isset($_POST["querylog-blocked"]))
+ {
+ pihole_execute("-a setquerylog blockedonly");
+ $success .= "Only blocked entries will be shown in Query Log";
+ }
+ else
+ {
+ pihole_execute("-a setquerylog nothing");
+ $success .= "No entries will be shown in Query Log";
+ }
+
+ break;
+
+ case "webUI":
+ $adminemail = trim($_POST["adminemail"]);
+ if(strlen($adminemail) == 0 || !isset($adminemail))
+ {
+ $adminemail = '';
+ }
+ if(strlen($adminemail) > 0 && !validEmail($adminemail))
+ {
+ $error .= "Administrator email address (".htmlspecialchars($adminemail).") is invalid! ";
+ }
+ else
+ {
+ pihole_execute('-a -e \''.$adminemail.'\'');
+ }
+ if(isset($_POST["boxedlayout"]))
+ {
+ pihole_execute('-a layout boxed');
+ }
+ else
+ {
+ pihole_execute('-a layout traditional');
+ }
+ if(isset($_POST["webtheme"]))
+ {
+ global $available_themes;
+ if(array_key_exists($_POST["webtheme"], $available_themes))
+ exec('sudo pihole -a theme '.$_POST["webtheme"]);
+ }
+ $success .= "The webUI settings have been updated";
+ break;
+
+ case "poweroff":
+ pihole_execute("-a poweroff");
+ $success = "The system will poweroff in 5 seconds...";
+ break;
+
+ case "reboot":
+ pihole_execute("-a reboot");
+ $success = "The system will reboot in 5 seconds...";
+ break;
+
+ case "restartdns":
+ pihole_execute("-a restartdns");
+ $success = "The DNS server has been restarted";
+ break;
+
+ case "flushlogs":
+ pihole_execute("-f");
+ $success = "The Pi-hole log file has been flushed";
+ break;
+
+ case "DHCP":
+
+ if(isset($_POST["addstatic"]))
+ {
+ $mac = trim($_POST["AddMAC"]);
+ $ip = trim($_POST["AddIP"]);
+ $hostname = trim($_POST["AddHostname"]);
+
+ addStaticDHCPLease($mac, $ip, $hostname);
+ break;
+ }
+
+ if(isset($_POST["removestatic"]))
+ {
+ $mac = $_POST["removestatic"];
+ if(!validMAC($mac))
+ {
+ $error .= "MAC address (".htmlspecialchars($mac).") is invalid! ";
+ }
+ $mac = strtoupper($mac);
- $success .= "The Speedtest settings have been updated";
+ if(!strlen($error))
+ {
+ pihole_execute("-a removestaticdhcp ".$mac);
+ $success .= "The static address with MAC address ".htmlspecialchars($mac)." has been removed";
+ }
break;
+ }
+
+ if(isset($_POST["active"]))
+ {
+ // Validate from IP
+ $from = $_POST["from"];
+ if (!validIP($from))
+ {
+ $error .= "From IP (".htmlspecialchars($from).") is invalid! ";
+ }
- case "webUI":
- $adminemail = trim($_POST["adminemail"]);
- if(strlen($adminemail) == 0 || !isset($adminemail))
- {
- $adminemail = '';
- }
- if(strlen($adminemail) > 0 && !validEmail($adminemail))
- {
- $error .= "Administrator email address (".htmlspecialchars($adminemail).") is invalid! ";
- }
- else
- {
- pihole_execute('-a -e \''.$adminemail.'\'');
- }
- if(isset($_POST["boxedlayout"]))
- {
- pihole_execute('-a layout boxed');
- }
- else
- {
- pihole_execute('-a layout traditional');
- }
- if(isset($_POST["webtheme"]))
- {
- global $available_themes;
- if(array_key_exists($_POST["webtheme"], $available_themes))
- exec('sudo pihole -a theme '.$_POST["webtheme"]);
- }
-
-
- $success .= "The webUI settings have been updated";
- break;
-
- case "poweroff":
- pihole_execute("-a poweroff");
- $success = "The system will poweroff in 5 seconds...";
- break;
-
- case "reboot":
- pihole_execute("-a reboot");
- $success = "The system will reboot in 5 seconds...";
- break;
-
- case "restartdns":
- pihole_execute("-a restartdns");
- $success = "The DNS server has been restarted";
- break;
-
- case "flushlogs":
- pihole_execute("-f");
- $success = "The Pi-hole log file has been flushed";
- break;
-
- case "DHCP":
-
- if(isset($_POST["addstatic"]))
- {
- $mac = trim($_POST["AddMAC"]);
- $ip = trim($_POST["AddIP"]);
- $hostname = trim($_POST["AddHostname"]);
-
- addStaticDHCPLease($mac, $ip, $hostname);
- break;
- }
-
- if(isset($_POST["removestatic"]))
- {
- $mac = $_POST["removestatic"];
- if(!validMAC($mac))
- {
- $error .= "MAC address (".htmlspecialchars($mac).") is invalid! ";
- }
- $mac = strtoupper($mac);
-
- if(!strlen($error))
- {
- pihole_execute("-a removestaticdhcp ".$mac);
- $success .= "The static address with MAC address ".htmlspecialchars($mac)." has been removed";
- }
- break;
- }
-
- if(isset($_POST["active"]))
- {
- // Validate from IP
- $from = $_POST["from"];
- if (!validIP($from))
- {
- $error .= "From IP (".htmlspecialchars($from).") is invalid! ";
- }
-
- // Validate to IP
- $to = $_POST["to"];
- if (!validIP($to))
- {
- $error .= "To IP (".htmlspecialchars($to).") is invalid! ";
- }
-
- // Validate router IP
- $router = $_POST["router"];
- if (!validIP($router))
- {
- $error .= "Router IP (".htmlspecialchars($router).") is invalid! ";
- }
-
- $domain = $_POST["domain"];
-
- // Validate Domain name
- if(!validDomain($domain))
- {
- $error .= "Domain name ".htmlspecialchars($domain)." is invalid! ";
- }
-
- $leasetime = $_POST["leasetime"];
-
- // Validate Lease time length
- if(!is_numeric($leasetime) || intval($leasetime) < 0)
- {
- $error .= "Lease time ".htmlspecialchars($leasetime)." is invalid! ";
- }
-
- if(isset($_POST["useIPv6"]))
- {
- $ipv6 = "true";
- $type = "(IPv4 + IPv6)";
- }
- else
- {
- $ipv6 = "false";
- $type = "(IPv4)";
- }
-
- if(isset($_POST["DHCP_rapid_commit"]))
- {
- $rapidcommit = "true";
- }
- else
- {
- $rapidcommit = "false";
- }
-
- if(!strlen($error))
- {
- pihole_execute("-a enabledhcp ".$from." ".$to." ".$router." ".$leasetime." ".$domain." ".$ipv6." ".$rapidcommit);
- $success .= "The DHCP server has been activated ".htmlspecialchars($type);
- }
- }
- else
- {
- pihole_execute("-a disabledhcp");
- $success = "The DHCP server has been deactivated";
- }
-
- break;
-
- case "privacyLevel":
- $level = intval($_POST["privacylevel"]);
- if($level >= 0 && $level <= 4)
- {
- // Check if privacylevel is already set
- if (isset($piholeFTLConf["PRIVACYLEVEL"])) {
- $privacylevel = intval($piholeFTLConf["PRIVACYLEVEL"]);
- } else {
- $privacylevel = 0;
- }
-
- // Store privacy level
- pihole_execute("-a privacylevel ".$level);
-
- if($privacylevel > $level)
- {
- pihole_execute("-a restartdns");
- $success .= "The privacy level has been decreased and the DNS resolver has been restarted";
- }
- elseif($privacylevel < $level)
- {
- $success .= "The privacy level has been increased";
- }
- else
- {
- $success .= "The privacy level has not been changed";
- }
- }
- else
- {
- $error .= "Invalid privacy level (".$level.")!";
- }
- break;
- // Flush network table
- case "flusharp":
- $output = pihole_execute("arpflush quiet");
- $error = "";
- if(is_array($output))
- {
- $error = implode(" ", $output);
- }
- if(strlen($error) == 0)
- {
- $success .= "The network table has been flushed";
- }
- break;
-
- default:
- // Option not found
- $debug = true;
- break;
- }
- }
+ // Validate to IP
+ $to = $_POST["to"];
+ if (!validIP($to))
+ {
+ $error .= "To IP (".htmlspecialchars($to).") is invalid! ";
+ }
- // Credit: http://stackoverflow.com/a/5501447/2087442
- function formatSizeUnits($bytes)
- {
- if ($bytes >= 1073741824)
- {
- $bytes = number_format($bytes / 1073741824, 2) . ' GB';
- }
- elseif ($bytes >= 1048576)
- {
- $bytes = number_format($bytes / 1048576, 2) . ' MB';
- }
- elseif ($bytes >= 1024)
- {
- $bytes = number_format($bytes / 1024, 2) . ' kB';
- }
- elseif ($bytes > 1)
- {
- $bytes = $bytes . ' bytes';
- }
- elseif ($bytes == 1)
- {
- $bytes = $bytes . ' byte';
- }
- else
- {
- $bytes = '0 bytes';
- }
+ // Validate router IP
+ $router = $_POST["router"];
+ if (!validIP($router))
+ {
+ $error .= "Router IP (".htmlspecialchars($router).") is invalid! ";
+ }
- return $bytes;
- }
-?>
+ $domain = $_POST["domain"];
+
+ // Validate Domain name
+ if(!validDomain($domain))
+ {
+ $error .= "Domain name ".htmlspecialchars($domain)." is invalid! ";
+ }
+
+ $leasetime = $_POST["leasetime"];
+
+ // Validate Lease time length
+ if(!is_numeric($leasetime) || intval($leasetime) < 0)
+ {
+ $error .= "Lease time ".htmlspecialchars($leasetime)." is invalid! ";
+ }
+
+ if(isset($_POST["useIPv6"]))
+ {
+ $ipv6 = "true";
+ $type = "(IPv4 + IPv6)";
+ }
+ else
+ {
+ $ipv6 = "false";
+ $type = "(IPv4)";
+ }
+
+ if(isset($_POST["DHCP_rapid_commit"]))
+ {
+ $rapidcommit = "true";
+ }
+ else
+ {
+ $rapidcommit = "false";
+ }
+
+ if(!strlen($error))
+ {
+ pihole_execute("-a enabledhcp ".$from." ".$to." ".$router." ".$leasetime." ".$domain." ".$ipv6." ".$rapidcommit);
+ $success .= "The DHCP server has been activated ".htmlspecialchars($type);
+ }
+ }
+ else
+ {
+ pihole_execute("-a disabledhcp");
+ $success = "The DHCP server has been deactivated";
+ }
+
+ break;
+
+ case "privacyLevel":
+ $level = intval($_POST["privacylevel"]);
+ if($level >= 0 && $level <= 4)
+ {
+ // Check if privacylevel is already set
+ if (isset($piholeFTLConf["PRIVACYLEVEL"])) {
+ $privacylevel = intval($piholeFTLConf["PRIVACYLEVEL"]);
+ } else {
+ $privacylevel = 0;
+ }
+
+ // Store privacy level
+ pihole_execute("-a privacylevel ".$level);
+
+ if($privacylevel > $level)
+ {
+ pihole_execute("-a restartdns");
+ $success .= "The privacy level has been decreased and the DNS resolver has been restarted";
+ }
+ elseif($privacylevel < $level)
+ {
+ $success .= "The privacy level has been increased";
+ }
+ else
+ {
+ $success .= "The privacy level has not been changed";
+ }
+ }
+ else
+ {
+ $error .= "Invalid privacy level (".$level.")!";
+ }
+ break;
+
+ // Flush network table
+ case "flusharp":
+ $output = pihole_execute("arpflush quiet");
+ $error = "";
+ if(is_array($output))
+ {
+ $error = implode(" ", $output);
+ }
+ if(strlen($error) == 0)
+ {
+ $success .= "The network table has been flushed";
+ }
+ break;
+
+
+ case "speedtest":
+
+ if (isset($_POST["speedtestmode"])) {
+ exec('sudo pihole -a -sm ' . trim($_POST["speedtestmode"]));
+ }
+
+ if (isset($_POST["speedtestschedule"])) {
+ exec('sudo pihole -a -s ' . trim($_POST["speedtestschedule"]));
+ }
+
+ if (isset($_POST["clearspeedtests"])) {
+ if (trim($_POST["clearspeedtests"]) == "yes")
+ exec('sudo pihole -a -sc');
+ }
+
+ if (isset($_POST["speedtestserver"]) && is_numeric($_POST["speedtestserver"])) {
+ exec('sudo pihole -a -ss ' . trim($_POST["speedtestserver"]));
+ }
+
+
+ if (isset($_POST["speedtestdays"])) {
+ exec('sudo pihole -a -sd ' .trim( $_POST["speedtestdays"]));
+ }
+
+ $success .= "The Speedtest settings have been updated";
+ break;
+ default:
+ // Option not found
+ $error = "Invalid option";
+ }
+}
+
+// Credit: http://stackoverflow.com/a/5501447/2087442
+function formatSizeUnits($bytes)
+{
+ if ($bytes >= 1073741824)
+ {
+ $bytes = number_format($bytes / 1073741824, 2) . ' GB';
+ }
+ elseif ($bytes >= 1048576)
+ {
+ $bytes = number_format($bytes / 1048576, 2) . ' MB';
+ }
+ elseif ($bytes >= 1024)
+ {
+ $bytes = number_format($bytes / 1024, 2) . ' kB';
+ }
+ elseif ($bytes > 1)
+ {
+ $bytes = $bytes . ' bytes';
+ }
+ elseif ($bytes == 1)
+ {
+ $bytes = $bytes . ' byte';
+ }
+ else
+ {
+ $bytes = '0 bytes';
+ }
+
+ return $bytes;
+}
diff --git a/scripts/pi-hole/php/tailLog.php b/scripts/pi-hole/php/tailLog.php
index f778d5616..e7eef3d2e 100644
--- a/scripts/pi-hole/php/tailLog.php
+++ b/scripts/pi-hole/php/tailLog.php
@@ -14,11 +14,11 @@
// files as large as the pihole.log
if(isset($_GET["FTL"]))
{
- $file = fopen("/var/log/pihole-FTL.log","r");
+ $file = fopen("/var/log/pihole/FTL.log","r");
}
else
{
- $file = fopen("/var/log/pihole.log","r");
+ $file = fopen("/var/log/pihole/pihole.log","r");
}
if(!$file)
diff --git a/scripts/pi-hole/php/teleporter.php b/scripts/pi-hole/php/teleporter.php
index 774d10c7f..84abf502b 100644
--- a/scripts/pi-hole/php/teleporter.php
+++ b/scripts/pi-hole/php/teleporter.php
@@ -89,7 +89,7 @@ function archive_restore_table($file, $table, $flush=false)
{
$tableExists = $db->querySingle("SELECT name FROM sqlite_master WHERE type='table' AND name='".$table."';");
if ($tableExists)
- {
+ {
$db->exec("DELETE FROM \"".$table."\"");
array_push($flushed_tables, $table);
}
@@ -354,6 +354,7 @@ function noun($num)
}
$fullfilename = sys_get_temp_dir()."/".$filename;
+
if(!move_uploaded_file($source, $fullfilename))
{
die("Failed moving ".htmlentities($source)." to ".htmlentities($fullfilename));
@@ -603,7 +604,10 @@ function noun($num)
$hostname = gethostname() ? str_replace(".", "_", gethostname())."-" : "";
$tarname = "pi-hole-".$hostname."teleporter_".date("Y-m-d_H-i-s").".tar";
$filename = $tarname.".gz";
- $archive_file_name = sys_get_temp_dir() ."/". $tarname;
+ $archive_file_name = tempnam(sys_get_temp_dir(), 'pihole_teleporter_'); //create a random file name in the system's tmp dir for the intermediat archive
+ unlink($archive_file_name); //remove intermediate file created by tempnam()
+ $archive_file_name .= ".tar"; // Append ".tar" extension
+
$archive = new PharData($archive_file_name);
if ($archive->isWritable() !== TRUE) {
@@ -631,7 +635,7 @@ function noun($num)
archive_add_file("/etc/","hosts","etc/");
archive_add_directory("/etc/dnsmasq.d/","dnsmasq.d/");
- $archive->compress(Phar::GZ); // Creates a gziped copy
+ $archive->compress(Phar::GZ); // Creates a gzipped copy
unlink($archive_file_name); // Unlink original tar file as it is not needed anymore
$archive_file_name .= ".gz"; // Append ".gz" extension to ".tar"
diff --git a/settings.php b/settings.php
index 2e2a89c61..cba562daf 100644
--- a/settings.php
+++ b/settings.php
@@ -9,8 +9,9 @@
require "scripts/pi-hole/php/savesettings.php";
require_once "scripts/pi-hole/php/FTL.php";
// Reread ini file as things might have been changed
+// DEFAULT_FTLCONFFILE is set in "scripts/pi-hole/php/FTL.php";
$setupVars = parse_ini_file("/etc/pihole/setupVars.conf");
-$piholeFTLConf = piholeFTLConfig(true);
+$piholeFTLConf = piholeFTLConfig(DEFAULT_FTLCONFFILE ,true);
// Handling of PHP internal errors
$last_error = error_get_last();
@@ -42,15 +43,6 @@
-
-
-
-
0) { ?>
×
@@ -75,10 +67,13 @@
} else {
$piHoleInterface = "unknown";
}
-if (isset($setupVars["IPV4_ADDRESS"])) {
- $piHoleIPv4 = $setupVars["IPV4_ADDRESS"];
-} else {
- $piHoleIPv4 = "unknown";
+
+// get the gateway IP
+$IPv4GW=getGateway()["ip"];
+
+// if the default gateway address is unknown or FTL is not running
+if ($IPv4GW == "0.0.0.0"|| $IPv4GW == -1) {
+ $IPv4GW = "unknown";
}
// DNS settings
@@ -435,11 +430,11 @@ function get_FTL_data($arg)
} else {
$DHCP = false;
// Try to guess initial settings
- if ($piHoleIPv4 !== "unknown") {
- $DHCPdomain = explode(".", $piHoleIPv4);
- $DHCPstart = $DHCPdomain[0] . "." . $DHCPdomain[1] . "." . $DHCPdomain[2] . ".201";
- $DHCPend = $DHCPdomain[0] . "." . $DHCPdomain[1] . "." . $DHCPdomain[2] . ".251";
- $DHCProuter = $DHCPdomain[0] . "." . $DHCPdomain[1] . "." . $DHCPdomain[2] . ".1";
+ if ($IPv4GW !== "unknown") {
+ $DHCPparts = explode(".", $IPv4GW);
+ $DHCPstart = $DHCPparts[0] . "." . $DHCPparts[1] . "." . $DHCPparts[2] . ".201";
+ $DHCPend = $DHCPparts[0] . "." . $DHCPparts[1] . "." . $DHCPparts[2] . ".251";
+ $DHCProuter = $IPv4GW;
} else {
$DHCPstart = "";
$DHCPend = "";
diff --git a/style/pi-hole.css b/style/pi-hole.css
index 85960018b..5d1a35701 100644
--- a/style/pi-hole.css
+++ b/style/pi-hole.css
@@ -620,12 +620,12 @@ li:not(.menu-open) .treeview-menu .warning-count {
}
#domainsTable tr {
display: flex;
- padding: 15px 0;
+ padding: 5px 0;
flex-wrap: wrap;
box-sizing: border-box;
align-items: end;
border: 1px solid rgba(127, 127, 127, 0.4);
- margin: 10px 0;
+ margin: 15px 0;
border-radius: 6px;
}
#domainsTable td {
@@ -634,14 +634,23 @@ li:not(.menu-open) .treeview-menu .warning-count {
display: block;
border: none;
box-sizing: border-box;
- order: 2;
+ order: 3;
text-align: right;
padding: 4px 8px;
}
- #domainsTable td:nth-child(2n) {
+ #domainsTable td:nth-child(2n + 1) {
width: calc(100% - 120px);
}
- #domainsTable td:first-child {
+ #domainsTable td:nth-child(1) {
+ width: 40px;
+ order: 0;
+ text-align: left;
+ padding: 5px 0 30px;
+ border-bottom: 1px solid rgba(127, 127, 127, 0.25);
+ margin-bottom: 5px;
+ flex: 0 0 auto;
+ }
+ #domainsTable td:nth-child(2) {
width: calc(100% - 40px);
order: 1;
text-align: left;
@@ -651,13 +660,13 @@ li:not(.menu-open) .treeview-menu .warning-count {
}
#domainsTable td:last-child {
width: 40px;
- order: 1;
+ order: 0;
padding-bottom: 10px;
border-bottom: 1px solid rgba(127, 127, 127, 0.25);
margin-bottom: 5px;
}
- #domainsTable td:nth-child(2),
- #domainsTable td:nth-child(4) {
+ #domainsTable td:nth-child(3),
+ #domainsTable td:nth-child(5) {
text-align: left;
}
#domainsTable td::before {
@@ -665,12 +674,16 @@ li:not(.menu-open) .treeview-menu .warning-count {
font-weight: bold;
font-size: smaller;
}
- #domainsTable td:nth-child(2)::before {
+ #domainsTable td:nth-child(3)::before {
content: "Type:";
}
- #domainsTable td:nth-child(4)::before {
+ #domainsTable td:nth-child(5)::before {
content: "Comment:";
}
+ #domainsTable tr.selected td.select-checkbox::after,
+ #domainsTable tr.selected th.select-checkbox::after {
+ transform: rotate(45deg) translate(5px, 1px);
+ }
}
@media screen and (min-width: 767px) {
#domainsTable select.form-control {
diff --git a/style/themes/default-dark.css b/style/themes/default-dark.css
index 93752a749..9e09b78cc 100644
--- a/style/themes/default-dark.css
+++ b/style/themes/default-dark.css
@@ -503,6 +503,11 @@ fieldset[disabled] .form-control {
background-color: #353c42;
opacity: 1;
}
+.dropdown-menu {
+ background-color: #22282d;
+ color: #bec5cb;
+ border: 1px solid #4c5761;
+}
.navbar-custom-menu > .navbar-nav > li > .dropdown-menu {
background-color: #4c5761;
color: #bec5cb;
diff --git a/taillog-FTL.php b/taillog-FTL.php
index 1e51660f7..047c69489 100644
--- a/taillog-FTL.php
+++ b/taillog-FTL.php
@@ -9,7 +9,7 @@
?>