diff --git a/api_db.php b/api_db.php index e624296c..21f1106d 100644 --- a/api_db.php +++ b/api_db.php @@ -12,6 +12,9 @@ require("scripts/pi-hole/php/password.php"); require("scripts/pi-hole/php/auth.php"); check_cors(); +// Set maximum execution time to 10 minutes +ini_set("max_execution_time","600"); + $data = array(); // Get posible non-standard location of FTL's database @@ -62,20 +65,20 @@ if(!$db) if (isset($_GET['getAllQueries']) && $auth) { - if($_GET['getAllQueries'] === "empty") - { - $allQueries = array(); - } - else + $allQueries = array(); + if($_GET['getAllQueries'] !== "empty") { $from = intval($_GET["from"]); $until = intval($_GET["until"]); - $results = $db->query('SELECT timestamp,type,domain,client,status FROM queries WHERE timestamp >= '.$from.' AND timestamp <= '.$until.' ORDER BY timestamp ASC'); - $allQueries = array(); - while ($row = $results->fetchArray()) - { - $allQueries[] = [$row[0],$row[1] == 1 ? "IPv4" : "IPv6",$row[2],$row[3],$row[4]]; - } + $stmt = $db->prepare("SELECT timestamp, type, domain, client, status FROM queries WHERE timestamp >= :from AND timestamp <= :until ORDER BY timestamp ASC"); + $stmt->bindValue(":from", intval($from), SQLITE3_INTEGER); + $stmt->bindValue(":until", intval($until), SQLITE3_INTEGER); + $results = $stmt->execute(); + if(!is_bool($results)) + while ($row = $results->fetchArray()) + { + $allQueries[] = [$row[0],$row[1] == 1 ? "IPv4" : "IPv6",$row[2],$row[3],$row[4]]; + } } $result = array('data' => $allQueries); $data = array_merge($data, $result); @@ -87,23 +90,46 @@ if (isset($_GET['topClients']) && $auth) $limit = ""; if(isset($_GET["from"]) && isset($_GET["until"])) { - $limit = "WHERE timestamp >= ".$_GET["from"]." AND timestamp <= ".$_GET["until"]; + $limit = "WHERE timestamp >= :from AND timestamp <= :until"; } elseif(isset($_GET["from"]) && !isset($_GET["until"])) { - $limit = "WHERE timestamp >= ".$_GET["from"]; + $limit = "WHERE timestamp >= :from"; } elseif(!isset($_GET["from"]) && isset($_GET["until"])) { - $limit = "WHERE timestamp <= ".$_GET["until"]; + $limit = "WHERE timestamp <= :until"; } - $results = $db->query('SELECT client,count(client) FROM queries '.$limit.' GROUP by client order by count(client) desc limit 10'); + $stmt = $db->prepare('SELECT client,count(client) FROM queries '.$limit.' GROUP by client order by count(client) desc limit 20'); + $stmt->bindValue(":from", intval($_GET['from']), SQLITE3_INTEGER); + $stmt->bindValue(":until", intval($_GET['until']), SQLITE3_INTEGER); + $results = $stmt->execute(); + $clients = array(); - while ($row = $results->fetchArray()) - { - $clients[$row[0]] = intval($row[1]); - // var_dump($row); - } + + if(!is_bool($results)) + while ($row = $results->fetchArray()) + { + // Convert client to lower case + $c = strtolower($row[0]); + if(array_key_exists($c, $clients)) + { + // Entry already exists, add to it (might appear multiple times due to mixed capitalization in the database) + $clients[$c] += intval($row[1]); + } + else + { + // Entry does not yet exist + $clients[$c] = intval($row[1]); + } + } + + // Sort by number of hits + arsort($clients); + + // Extract only the first ten entries + $clients = array_slice($clients, 0, 10); + $result = array('top_sources' => $clients); $data = array_merge($data, $result); } @@ -114,22 +140,46 @@ if (isset($_GET['topDomains']) && $auth) if(isset($_GET["from"]) && isset($_GET["until"])) { - $limit = " AND timestamp >= ".$_GET["from"]." AND timestamp <= ".$_GET["until"]; + $limit = " AND timestamp >= :from AND timestamp <= :until"; } elseif(isset($_GET["from"]) && !isset($_GET["until"])) { - $limit = " AND timestamp >= ".$_GET["from"]; + $limit = " AND timestamp >= :from"; } elseif(!isset($_GET["from"]) && isset($_GET["until"])) { - $limit = " AND timestamp <= ".$_GET["until"]; + $limit = " AND timestamp <= :until"; } - $results = $db->query('SELECT domain,count(domain) FROM queries WHERE (STATUS == 2 OR STATUS == 3)'.$limit.' GROUP by domain order by count(domain) desc limit 10'); + $stmt = $db->prepare('SELECT domain,count(domain) FROM queries WHERE (STATUS == 2 OR STATUS == 3)'.$limit.' GROUP by domain order by count(domain) desc limit 20'); + $stmt->bindValue(":from", intval($_GET['from']), SQLITE3_INTEGER); + $stmt->bindValue(":until", intval($_GET['until']), SQLITE3_INTEGER); + $results = $stmt->execute(); + $domains = array(); - while ($row = $results->fetchArray()) - { - $domains[$row[0]] = intval($row[1]); - } + + if(!is_bool($results)) + while ($row = $results->fetchArray()) + { + // Convert client to lower case + $c = strtolower($row[0]); + if(array_key_exists($c, $domains)) + { + // Entry already exists, add to it (might appear multiple times due to mixed capitalization in the database) + $domains[$c] += intval($row[1]); + } + else + { + // Entry does not yet exist + $domains[$c] = intval($row[1]); + } + } + + // Sort by number of hits + arsort($domains); + + // Extract only the first ten entries + $domains = array_slice($domains, 0, 10); + $result = array('top_domains' => $domains); $data = array_merge($data, $result); } @@ -140,22 +190,28 @@ if (isset($_GET['topAds']) && $auth) if(isset($_GET["from"]) && isset($_GET["until"])) { - $limit = " AND timestamp >= ".$_GET["from"]." AND timestamp <= ".$_GET["until"]; + $limit = " AND timestamp >= :from AND timestamp <= :until"; } elseif(isset($_GET["from"]) && !isset($_GET["until"])) { - $limit = " AND timestamp >= ".$_GET["from"]; + $limit = " AND timestamp >= :from"; } elseif(!isset($_GET["from"]) && isset($_GET["until"])) { - $limit = " AND timestamp <= ".$_GET["until"]; + $limit = " AND timestamp <= :until"; } - $results = $db->query('SELECT domain,count(domain) FROM queries WHERE (STATUS == 1 OR STATUS == 4)'.$limit.' GROUP by domain order by count(domain) desc limit 10'); + $stmt = $db->prepare('SELECT domain,count(domain) FROM queries WHERE (STATUS == 1 OR STATUS == 4)'.$limit.' GROUP by domain order by count(domain) desc limit 10'); + $stmt->bindValue(":from", intval($_GET['from']), SQLITE3_INTEGER); + $stmt->bindValue(":until", intval($_GET['until']), SQLITE3_INTEGER); + $results = $stmt->execute(); + $addomains = array(); - while ($row = $results->fetchArray()) - { - $addomains[$row[0]] = intval($row[1]); - } + + if(!is_bool($results)) + while ($row = $results->fetchArray()) + { + $addomains[$row[0]] = intval($row[1]); + } $result = array('top_ads' => $addomains); $data = array_merge($data, $result); } @@ -163,21 +219,36 @@ if (isset($_GET['topAds']) && $auth) if (isset($_GET['getMinTimestamp']) && $auth) { $results = $db->query('SELECT MIN(timestamp) FROM queries'); - $result = array('mintimestamp' => $results->fetchArray()[0]); + + if(!is_bool($results)) + $result = array('mintimestamp' => $results->fetchArray()[0]); + else + $result = array(); + $data = array_merge($data, $result); } if (isset($_GET['getMaxTimestamp']) && $auth) { $results = $db->query('SELECT MAX(timestamp) FROM queries'); - $result = array('maxtimestamp' => $results->fetchArray()[0]); + + if(!is_bool($results)) + $result = array('maxtimestamp' => $results->fetchArray()[0]); + else + $result = array(); + $data = array_merge($data, $result); } if (isset($_GET['getQueriesCount']) && $auth) { $results = $db->query('SELECT COUNT(timestamp) FROM queries'); - $result = array('count' => $results->fetchArray()[0]); + + if(!is_bool($results)) + $result = array('count' => $results->fetchArray()[0]); + else + $result = array(); + $data = array_merge($data, $result); } @@ -194,15 +265,15 @@ if (isset($_GET['getGraphData']) && $auth) if(isset($_GET["from"]) && isset($_GET["until"])) { - $limit = " AND timestamp >= ".intval($_GET["from"])." AND timestamp <= ".intval($_GET["until"]); + $limit = " AND timestamp >= :from AND timestamp <= :until"; } elseif(isset($_GET["from"]) && !isset($_GET["until"])) { - $limit = " AND timestamp >= ".intval($_GET["from"]); + $limit = " AND timestamp >= :from"; } elseif(!isset($_GET["from"]) && isset($_GET["until"])) { - $limit = " AND timestamp <= ".intval($_GET["until"]); + $limit = " AND timestamp <= :until"; } $interval = 600; @@ -215,22 +286,36 @@ if (isset($_GET['getGraphData']) && $auth) } // Count permitted queries in intervals - $results = $db->query('SELECT (timestamp/'.$interval.')*'.$interval.' interval, COUNT(*) FROM queries WHERE (status == 2 OR status == 3)'.$limit.' GROUP by interval ORDER by interval'); + $stmt = $db->prepare('SELECT (timestamp/:interval)*:interval interval, COUNT(*) FROM queries WHERE (status != 0 )'.$limit.' GROUP by interval ORDER by interval'); + $stmt->bindValue(":from", intval($_GET['from']), SQLITE3_INTEGER); + $stmt->bindValue(":until", intval($_GET['until']), SQLITE3_INTEGER); + $stmt->bindValue(":interval", $interval, SQLITE3_INTEGER); + $results = $stmt->execute(); + $domains = array(); - while ($row = $results->fetchArray()) - { - $domains[$row[0]] = intval($row[1]); - } + + if(!is_bool($results)) + while ($row = $results->fetchArray()) + { + $domains[$row[0]] = intval($row[1]); + } $result = array('domains_over_time' => $domains); $data = array_merge($data, $result); // Count blocked queries in intervals - $results = $db->query('SELECT (timestamp/'.$interval.')*'.$interval.' interval, COUNT(*) FROM queries WHERE (status == 1 OR status == 4 OR status == 5)'.$limit.' GROUP by interval ORDER by interval'); + $stmt = $db->prepare('SELECT (timestamp/:interval)*:interval interval, COUNT(*) FROM queries WHERE (status == 1 OR status == 4 OR status == 5)'.$limit.' GROUP by interval ORDER by interval'); + $stmt->bindValue(":from", intval($_GET['from']), SQLITE3_INTEGER); + $stmt->bindValue(":until", intval($_GET['until']), SQLITE3_INTEGER); + $stmt->bindValue(":interval", $interval, SQLITE3_INTEGER); + $results = $stmt->execute(); + $addomains = array(); - while ($row = $results->fetchArray()) - { - $addomains[$row[0]] = intval($row[1]); - } + + if(!is_bool($results)) + while ($row = $results->fetchArray()) + { + $addomains[$row[0]] = intval($row[1]); + } $result = array('ads_over_time' => $addomains); $data = array_merge($data, $result); } diff --git a/db_graph.php b/db_graph.php index 9dbdba73..2642bf14 100644 --- a/db_graph.php +++ b/db_graph.php @@ -22,7 +22,6 @@ $token = $_SESSION['token'];

Compute graphical statistics from the Pi-hole query database

-
@@ -39,6 +38,11 @@ $token = $_SESSION['token'];
+ + +
diff --git a/db_lists.php b/db_lists.php index 0ddc9dda..c0e39408 100644 --- a/db_lists.php +++ b/db_lists.php @@ -39,6 +39,11 @@ $token = $_SESSION['token'];
+ + + + + +
@@ -103,31 +108,29 @@ $token = $_SESSION['token'];
-
- - - - - - - - - - - - - - - - - - - - - -
TimeTypeDomainClientStatusAction
TimeTypeDomainClientStatusAction
-
-
+ + + + + + + + + + + + + + + + + + + + + +
TimeTypeDomainClientStatusAction
TimeTypeDomainClientStatusAction
+
diff --git a/queries.php b/queries.php index f511f8d1..89ed91d7 100644 --- a/queries.php +++ b/queries.php @@ -88,18 +88,35 @@ if(strlen($showing) > 0) --> - - - - diff --git a/scripts/pi-hole/js/db_graph.js b/scripts/pi-hole/js/db_graph.js index d543f16b..8da24ad6 100644 --- a/scripts/pi-hole/js/db_graph.js +++ b/scripts/pi-hole/js/db_graph.js @@ -14,6 +14,8 @@ var from = moment(start__).utc().valueOf()/1000; var end__ = moment(); var until = moment(end__).utc().valueOf()/1000; +var timeoutWarning = $("#timeoutWarning"); + $(function () { $("#querytime").daterangepicker( { @@ -80,6 +82,7 @@ function compareNumbers(a, b) { function updateQueriesOverTime() { $("#queries-over-time .overlay").show(); + timeoutWarning.show(); $.getJSON("api_db.php?getGraphData&from="+from+"&until="+until, function(data) { // convert received objects to arrays @@ -135,6 +138,7 @@ function updateQueriesOverTime() { timeLineChart.options.scales.xAxes[0].display=true; $("#queries-over-time .overlay").hide(); + timeoutWarning.hide(); timeLineChart.update(); }); } diff --git a/scripts/pi-hole/js/db_lists.js b/scripts/pi-hole/js/db_lists.js index 75a21746..43e569f7 100644 --- a/scripts/pi-hole/js/db_lists.js +++ b/scripts/pi-hole/js/db_lists.js @@ -14,6 +14,9 @@ var from = moment(start__).utc().valueOf()/1000; var end__ = moment(); var until = moment(end__).utc().valueOf()/1000; +var timeoutWarning = $("#timeoutWarning"); +var listsStillLoading = 0; + $(function () { $("#querytime").daterangepicker( { @@ -97,6 +100,10 @@ function updateTopClientsChart() { } $("#client-frequency .overlay").hide(); + + listsStillLoading--; + if(listsStillLoading === 0) + timeoutWarning.hide(); }); } @@ -135,6 +142,10 @@ function updateTopDomainsChart() { } $("#domain-frequency .overlay").hide(); + + listsStillLoading--; + if(listsStillLoading === 0) + timeoutWarning.hide(); }); } @@ -171,10 +182,16 @@ function updateTopAdsChart() { } $("#ad-frequency .overlay").hide(); + + listsStillLoading--; + if(listsStillLoading === 0) + timeoutWarning.hide(); }); } $("#querytime").on("apply.daterangepicker", function(ev, picker) { + timeoutWarning.show(); + listsStillLoading = 3; updateTopClientsChart(); updateTopDomainsChart(); updateTopAdsChart(); diff --git a/scripts/pi-hole/js/db_queries.js b/scripts/pi-hole/js/db_queries.js index 8551e9dc..751081d4 100644 --- a/scripts/pi-hole/js/db_queries.js +++ b/scripts/pi-hole/js/db_queries.js @@ -16,6 +16,8 @@ var until = moment(end__).utc().valueOf()/1000; var instantquery = false; var daterange; +var timeoutWarning = $("#timeoutWarning"); + // Do we want to filter queries? var GETDict = {}; location.search.substr(1).split("&").forEach(function(item) {GETDict[item.split("=")[0]] = item.split("=")[1];}); @@ -141,6 +143,7 @@ function handleAjaxError( xhr, textStatus, error ) { var reloadCallback = function() { + timeoutWarning.hide(); statistics = [0,0,0,0]; var data = tableApi.rows().data(); for (var i = 0; i < data.length; i++) { @@ -171,6 +174,7 @@ var reloadCallback = function() }; function refreshTableData() { + timeoutWarning.show(); var APIstring = "api_db.php?getAllQueries&from="+from+"&until="+until; statistics = [0,0,0]; tableApi.ajax.url(APIstring).load(reloadCallback); @@ -201,13 +205,13 @@ $(document).ready(function() { else if (data[4] === 2) { $(row).css("color","green"); - $("td:eq(4)", row).html( "OK (forwarded)" ); + $("td:eq(4)", row).html( "OK (forwarded)" ); $("td:eq(5)", row).html( "" ); } else if (data[4] === 3) { $(row).css("color","green"); - $("td:eq(4)", row).html( "OK (cached)" ); + $("td:eq(4)", row).html( "OK (cached)" ); $("td:eq(5)", row).html( "" ); // statistics[1]++; @@ -215,20 +219,20 @@ $(document).ready(function() { else if (data[4] === 4) { $(row).css("color","red"); - $("td:eq(4)", row).html( "Pi-holed (wildcard)" ); + $("td:eq(4)", row).html( "Pi-holed (wildcard)" ); $("td:eq(5)", row).html( "" ); // statistics[3]++; } else { - $("td:eq(4)", row).html( "Unknown ("+data[4]+")" ); + $("td:eq(4)", row).html( "Unknown ("+data[4]+")" ); $("td:eq(5)", row).html( "" ); } // statistics[0]++; }, dom: "<'row'<'col-sm-12'f>>" + "<'row'<'col-sm-4'l><'col-sm-8'p>>" + - "<'row'<'col-sm-12'tr>>" + + "<'row'<'col-sm-12'<'table-responsive'tr>>>" + "<'row'<'col-sm-5'i><'col-sm-7'p>>", "ajax": {"url": APIstring, "error": handleAjaxError }, "autoWidth" : false, @@ -236,12 +240,12 @@ $(document).ready(function() { "deferRender": true, "order" : [[0, "desc"]], "columns": [ - { "width" : "20%", "render": function (data, type, full, meta) { if(type === "display"){return moment.unix(data).format("Y-MM-DD HH:mm:ss z");}else{return data;} }}, + { "width" : "15%", "render": function (data, type, full, meta) { if(type === "display"){return moment.unix(data).format("Y-MM-DD []HH:mm:ss z");}else{return data;} }}, { "width" : "10%" }, { "width" : "40%" }, + { "width" : "20%" }, { "width" : "10%" }, - { "width" : "10%" }, - { "width" : "10%" }, + { "width" : "5%" }, ], "lengthMenu": [[10, 25, 50, 100, -1], [10, 25, 50, 100, "All"]], "columnDefs": [ { diff --git a/scripts/pi-hole/js/queries.js b/scripts/pi-hole/js/queries.js index 984215bc..722f8543 100644 --- a/scripts/pi-hole/js/queries.js +++ b/scripts/pi-hole/js/queries.js @@ -22,62 +22,76 @@ function refreshData() { function add(domain,list) { var token = $("#token").html(); - var alInfo = $("#alInfo"); - var alList = $("#alList"); - var alDomain = $("#alDomain"); - alDomain.html(domain); - var alSuccess = $("#alSuccess"); - var alFailure = $("#alFailure"); - var err = $("#err"); + var alertModal = $("#alertModal"); + var alProcessing = alertModal.find(".alProcessing"); + var alSuccess = alertModal.find(".alSuccess"); + var alFailure = alertModal.find(".alFailure"); + var alNetworkErr = alertModal.find(".alFailure #alNetErr"); + var alCustomErr = alertModal.find(".alFailure #alCustomErr"); + var alList = "#alList"; + var alDomain = "#alDomain"; - if(list === "white") - { - alList.html("Whitelist"); - } - else - { - alList.html("Blacklist"); + // Exit the function here if the Modal is already shown (multiple running interlock) + if (alertModal.css("display") !== "none") { + return; } - alInfo.show(); - alSuccess.hide(); - alFailure.hide(); - $.ajax({ - url: "scripts/pi-hole/php/add.php", - method: "post", - data: {"domain":domain, "list":list, "token":token}, - success: function(response) { - if (response.indexOf("not a valid argument") >= 0 || response.indexOf("is not a valid domain") >= 0) - { - alFailure.show(); - err.html(response); - alFailure.delay(4000).fadeOut(2000, function() { alFailure.hide(); }); + var listtype; + if (list === "white") { + listtype = "Whitelist"; + } else { + listtype = "Blacklist"; + } + alProcessing.children(alDomain).html(domain); + alProcessing.children(alList).html(listtype); + alertModal.modal("show"); + + // add Domain to List after Modal has faded in + alertModal.one("shown.bs.modal", function() { + $.ajax({ + url: "scripts/pi-hole/php/add.php", + method: "post", + data: {"domain":domain, "list":list, "token":token}, + success: function(response) { + alProcessing.hide(); + if (response.indexOf("not a valid argument") >= 0 || + response.indexOf("is not a valid domain") >= 0 || + response.indexOf("Wrong token") >= 0) + { + // Failure + alNetworkErr.hide(); + alCustomErr.html(response.replace("[✗]", "")); + alFailure.fadeIn(1000); + setTimeout(function() { alertModal.modal("hide"); }, 3000); + } + else + { + // Success + alSuccess.children(alDomain).html(domain); + alSuccess.children(alList).html(listtype); + alSuccess.fadeIn(1000); + setTimeout(function() { alertModal.modal("hide"); }, 2000); + } + }, + error: function(jqXHR, exception) { + // Network Error + alProcessing.hide(); + alNetworkErr.show(); + alFailure.fadeIn(1000); + setTimeout(function() { alertModal.modal("hide"); }, 3000); } - else - { - alSuccess.show(); - alSuccess.delay(1000).fadeOut(2000, function() { alSuccess.hide(); }); - } - alInfo.delay(1000).fadeOut(2000, function() { - alInfo.hide(); - alList.html(""); - alDomain.html(""); - }); - }, - error: function(jqXHR, exception) { - alFailure.show(); - err.html(""); - alFailure.delay(1000).fadeOut(2000, function() { - alFailure.hide(); - }); - alInfo.delay(1000).fadeOut(2000, function() { - alInfo.hide(); - alList.html(""); - alDomain.html(""); - }); - } + }); + }); + + // Reset Modal after it has faded out + alertModal.one("hidden.bs.modal", function() { + alProcessing.show(); + alSuccess.add(alFailure).hide(); + alProcessing.add(alSuccess).children(alDomain).html("").end().children(alList).html(""); + alCustomErr.html(""); }); } + function handleAjaxError( xhr, textStatus, error ) { if ( textStatus === "timeout" ) { @@ -135,54 +149,84 @@ $(document).ready(function() { { $(row).css("color","red"); $("td:eq(4)", row).html( "Pi-holed" ); - $("td:eq(5)", row).html( "" ); + $("td:eq(6)", row).html( "" ); } else if (data[4] === "2") { $(row).css("color","green"); - $("td:eq(4)", row).html( "OK (forwarded)" ); - $("td:eq(5)", row).html( "" ); + $("td:eq(4)", row).html( "OK (forwarded)" ); + $("td:eq(6)", row).html( "" ); } else if (data[4] === "3") { $(row).css("color","green"); - $("td:eq(4)", row).html( "OK (cached)" ); - $("td:eq(5)", row).html( "" ); - + $("td:eq(4)", row).html( "OK (cached)" ); + $("td:eq(6)", row).html( "" ); } else if (data[4] === "4") { $(row).css("color","red"); - $("td:eq(4)", row).html( "Pi-holed (wildcard)" ); - $("td:eq(5)", row).html( "" ); + $("td:eq(4)", row).html( "Pi-holed (wildcard)" ); + $("td:eq(6)", row).html( "" ); } else if (data[4] === "5") { $(row).css("color","red"); - $("td:eq(4)", row).html( "Pi-holed (blacklist)" ); - $("td:eq(5)", row).html( "" ); + $("td:eq(4)", row).html( "Pi-holed (blacklist)" ); + $("td:eq(6)", row).html( "" ); } else { $("td:eq(4)", row).html( "Unknown" ); - $("td:eq(5)", row).html( "" ); + $("td:eq(6)", row).html( "" ); + } + if (data[5] === "1") + { + $("td:eq(5)", row).css("color","green"); + $("td:eq(5)", row).html( "SECURE" ); + } + else if (data[5] === "2") + { + $("td:eq(5)", row).css("color","orange"); + $("td:eq(5)", row).html( "INSECURE" ); + } + else if (data[5] === "3") + { + $("td:eq(5)", row).css("color","red"); + $("td:eq(5)", row).html( "BOGUS" ); + } + else if (data[5] === "4") + { + $("td:eq(5)", row).css("color","red"); + $("td:eq(5)", row).html( "ABANDONED" ); + } + else if (data[5] === "5") + { + $("td:eq(5)", row).css("color","red"); + $("td:eq(5)", row).html( "?" ); + } + else + { + $("td:eq(5)", row).css("color","black"); + $("td:eq(5)", row).html( "-" ); } }, dom: "<'row'<'col-sm-12'f>>" + "<'row'<'col-sm-4'l><'col-sm-8'p>>" + - "<'row'<'col-sm-12'tr>>" + + "<'row'<'col-sm-12'<'table-responsive'tr>>>" + "<'row'<'col-sm-5'i><'col-sm-7'p>>", "ajax": {"url": APIstring, "error": handleAjaxError }, "autoWidth" : false, "processing": true, "order" : [[0, "desc"]], "columns": [ - { "width" : "20%", "render": function (data, type, full, meta) { if(type === "display"){return moment.unix(data).format("Y-MM-DD HH:mm:ss z");}else{return data;} }}, - { "width" : "10%" }, - { "width" : "40%", "render": $.fn.dataTable.render.text() }, - { "width" : "10%", "render": $.fn.dataTable.render.text() }, + { "width" : "15%", "render": function (data, type, full, meta) { if(type === "display"){return moment.unix(data).format("Y-MM-DD []HH:mm:ss z");}else{return data;} }}, { "width" : "10%" }, + { "width" : "37%", "render": $.fn.dataTable.render.text() }, + { "width" : "8%", "render": $.fn.dataTable.render.text() }, { "width" : "10%" }, + { "width" : "5%" }, + { "width" : "10%" } ], "lengthMenu": [[10, 25, 50, 100, -1], [10, 25, 50, 100, "All"]], "columnDefs": [ { diff --git a/scripts/pi-hole/js/settings.js b/scripts/pi-hole/js/settings.js index 0edae1bc..257ab7d4 100644 --- a/scripts/pi-hole/js/settings.js +++ b/scripts/pi-hole/js/settings.js @@ -85,7 +85,7 @@ $(".confirm-flushlogs").confirm({ }); $(".confirm-disablelogging").confirm({ - text: "Note that disabling query logging will render graphs on the web user interface useless. Are you sure you want to disable your logging?", + text: "Note that disabling query logging will render graphs on the web user interface useless. Are you sure you want to disable logging and flush your Pi-hole logs?", title: "Confirmation required", confirm(button) { $("#disablelogsform").submit(); @@ -93,7 +93,7 @@ $(".confirm-disablelogging").confirm({ cancel(button) { // nothing to do }, - confirmButton: "Yes, disable logs", + confirmButton: "Yes, disable logs and flush my logs", cancelButton: "No, go back", post: true, confirmButtonClass: "btn-danger", @@ -101,6 +101,23 @@ $(".confirm-disablelogging").confirm({ dialogClass: "modal-dialog modal-mg" }); +$(".confirm-disablelogging-noflush").confirm({ + text: "Note that disabling query logging will render graphs on the web user interface useless after this point. Are you sure you want to disable logging?", + title: "Confirmation required", + confirm(button) { + $("#disablelogsform-noflush").submit(); + }, + cancel(button) { + // nothing to do + }, + confirmButton: "Yes, disable logs", + cancelButton: "No, go back", + post: true, + confirmButtonClass: "btn-warning", + cancelButtonClass: "btn-success", + dialogClass: "modal-dialog modal-mg" +}); + $(".api-token").confirm({ text: "Make sure that nobody else can scan this code around you. They will have full access to the API without having to know the password. Note that the generation of the QR code will take some time.", title: "Confirmation required", diff --git a/scripts/pi-hole/php/auth.php b/scripts/pi-hole/php/auth.php index 85d38f8f..0b32c732 100644 --- a/scripts/pi-hole/php/auth.php +++ b/scripts/pi-hole/php/auth.php @@ -6,7 +6,7 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -require('func.php'); +require_once('func.php'); $ERRORLOG = getenv('PHP_ERROR_LOG'); if (empty($ERRORLOG)) { $ERRORLOG = '/var/log/lighttpd/error.log'; @@ -92,26 +92,6 @@ function check_csrf($token) { session_start(); } - // Credit: http://php.net/manual/en/function.hash-equals.php#119576 - if(!function_exists('hash_equals')) { - function hash_equals($known_string, $user_string) { - $ret = 0; - - if (strlen($known_string) !== strlen($user_string)) { - $user_string = $known_string; - $ret = 1; - } - - $res = $known_string ^ $user_string; - - for ($i = strlen($res) - 1; $i >= 0; --$i) { - $ret |= ord($res[$i]); - } - - return !$ret; - } - } - if(!isset($_SESSION['token']) || empty($token) || !hash_equals($_SESSION['token'], $token)) { log_and_die("Wrong token"); } @@ -119,7 +99,7 @@ function check_csrf($token) { function check_domain() { if(isset($_POST['domain'])){ - $domains = preg_split('\s+', $_POST['domain']); + $domains = preg_split('/\s+/', $_POST['domain']); foreach($domains as $domain) { $validDomain = is_valid_domain_name($domain); @@ -146,7 +126,7 @@ function list_verify($type) { require("password.php"); if($wrongpassword || !$auth) { - log_and_die("Wrong password - ".htmlspecialchars($type)."listing of ${_POST['domain']} not permitted"); + log_and_die("Wrong password - ".htmlspecialchars($type)."listing of ".htmlspecialchars($_POST['domain'])." not permitted"); } } else diff --git a/scripts/pi-hole/php/debug.php b/scripts/pi-hole/php/debug.php index 39d5c8ea..27a23545 100644 --- a/scripts/pi-hole/php/debug.php +++ b/scripts/pi-hole/php/debug.php @@ -18,10 +18,12 @@ $token = isset($_GET["token"]) ? $_GET["token"] : ""; check_csrf($token); function echoEvent($datatext) { + $data = htmlspecialchars($datatext); + if(!isset($_GET["IE"])) - echo "data: ".implode("\ndata: ", explode("\n", $datatext))."\n\n"; + echo "data: ".implode("\ndata: ", explode("\n", $data))."\n\n"; else - echo $datatext; + echo $data; } if(isset($_GET["upload"])) diff --git a/scripts/pi-hole/php/func.php b/scripts/pi-hole/php/func.php index f3beaf9e..bc353218 100644 --- a/scripts/pi-hole/php/func.php +++ b/scripts/pi-hole/php/func.php @@ -25,4 +25,24 @@ function checkfile($filename) { } } +// Credit: http://php.net/manual/en/function.hash-equals.php#119576 +if(!function_exists('hash_equals')) { + function hash_equals($known_string, $user_string) { + $ret = 0; + + if (strlen($known_string) !== strlen($user_string)) { + $user_string = $known_string; + $ret = 1; + } + + $res = $known_string ^ $user_string; + + for ($i = strlen($res) - 1; $i >= 0; --$i) { + $ret |= ord($res[$i]); + } + + return !$ret; + } +} + ?> diff --git a/scripts/pi-hole/php/password.php b/scripts/pi-hole/php/password.php index 77ee2863..ad8cbd08 100644 --- a/scripts/pi-hole/php/password.php +++ b/scripts/pi-hole/php/password.php @@ -6,6 +6,8 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ + require_once('func.php'); + // Start a new PHP session (or continue an existing one) session_start(); @@ -37,7 +39,7 @@ if(isset($_POST["pw"])) { $postinput = hash('sha256',hash('sha256',$_POST["pw"])); - if($postinput == $pwhash) + if(hash_equals($pwhash, $postinput)) { $_SESSION["hash"] = $pwhash; @@ -57,13 +59,13 @@ // Compare auth hash with saved hash else if (isset($_SESSION["hash"])) { - if($_SESSION["hash"] == $pwhash) + if(hash_equals($pwhash, $_SESSION["hash"])) $auth = true; } // API can use the hash to get data without logging in via plain-text password else if (isset($api) && isset($_GET["auth"])) { - if($_GET["auth"] == $pwhash) + if(hash_equals($pwhash, $_GET["auth"])) $auth = true; } else diff --git a/scripts/pi-hole/php/savesettings.php b/scripts/pi-hole/php/savesettings.php index ad1b0eae..64914e77 100644 --- a/scripts/pi-hole/php/savesettings.php +++ b/scripts/pi-hole/php/savesettings.php @@ -123,7 +123,7 @@ function isinserverlist($addr) { "Norton" => ["v4_1" => "199.85.126.10", "v4_2" => "199.85.127.10"], "Comodo" => ["v4_1" => "8.26.56.26", "v4_2" => "8.20.247.20"], "DNS.WATCH" => ["v4_1" => "84.200.69.80", "v4_2" => "84.200.70.40", "v6_1" => "2001:1608:10:25:0:0:1c04:b12f", "v6_2" => "2001:1608:10:25:0:0:9249:d69b"], - "Quad9" => ["v4_1" => "9.9.9.9", "v6_1" => "2620:fe::fe"] + "Quad9" => ["v4_1" => "9.9.9.9", "v4_2" => "149.112.112.112", "v6_1" => "2620:fe::fe"] ]; $adlist = []; @@ -278,7 +278,12 @@ function readAdlists() if($_POST["action"] === "Disable") { exec("sudo pihole -l off"); - $success .= "Logging has been disabled"; + $success .= "Logging has been disabled and logs have been flushed"; + } + elseif($_POST["action"] === "Disable-noflush") + { + exec("sudo pihole -l off noflush"); + $success .= "Logging has been disabled, your logs have not been flushed"; } else { @@ -320,9 +325,9 @@ function readAdlists() $first = true; foreach($clients as $client) { - if(!validDomainWildcard($client)) + if(!validDomainWildcard($client) && !validIP($client)) { - $error .= "Top Clients entry ".htmlspecialchars($client)." is invalid (use only IP addresses)!
"; + $error .= "Top Clients entry ".htmlspecialchars($client)." is invalid (use only host names and IP addresses)!
"; } if(!$first) { diff --git a/scripts/pi-hole/php/teleporter.php b/scripts/pi-hole/php/teleporter.php index 0427b7c0..9efe7fd1 100644 --- a/scripts/pi-hole/php/teleporter.php +++ b/scripts/pi-hole/php/teleporter.php @@ -176,6 +176,7 @@ else archive_add_file("/etc/pihole/","blacklist.txt"); archive_add_file("/etc/pihole/","adlists.list"); archive_add_file("/etc/pihole/","setupVars.conf"); + archive_add_file("/etc/pihole/","auditlog.list"); archive_add_directory("/etc/dnsmasq.d/"); $archive["wildcardblocking.txt"] = getWildcardListContent(); diff --git a/scripts/pi-hole/php/update_checker.php b/scripts/pi-hole/php/update_checker.php index 9bd4ab06..d479318a 100644 --- a/scripts/pi-hole/php/update_checker.php +++ b/scripts/pi-hole/php/update_checker.php @@ -44,12 +44,12 @@ else /********** Get Pi-hole FTL (not a git repository) **********/ $FTL_branch = $branches[2]; - if($FTL_branch !== "master") { + if(substr($versions[2], 0, 4) === "vDev") { $FTL_current = "vDev"; $FTL_commit = $versions[2]; } else { - $FTL_current = explode("-",$versions[2])[0]; + $FTL_current = $versions[2]; } // Get data from GitHub diff --git a/settings.php b/settings.php index a032c9ee..372f9393 100644 --- a/settings.php +++ b/settings.php @@ -288,6 +288,7 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "blocklists" @@ -344,7 +345,7 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "blocklists" if (isset($setupVars["PIHOLE_DOMAIN"])) { $piHoleDomain = $setupVars["PIHOLE_DOMAIN"]; } else { - $piHoleDomain = "local"; + $piHoleDomain = "lan"; } ?>
@@ -785,14 +786,14 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "blocklists"
+ ?>>Never forward non-FQDNs
+ ?>>Never forward reverse lookups for private IP ranges

Note that enabling these two options may increase your privacy @@ -807,7 +808,7 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "blocklists"

Validate DNS replies and cache DNSSEC data. When forwarding DNS queries, Pi-hole requests the DNSSEC records needed to validate - the replies. Use Google or Norton DNS servers when activating + the replies. Use Google, Norton, DNS.WATCH or Quad9 DNS servers when activating DNSSEC. Note that the size of your log might increase significantly when enabling DNSSEC. A DNSSEC resolver test can be found here.

@@ -1067,7 +1068,7 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "blocklists" - Pi-hole hostname + Pi-hole hostname: @@ -1148,7 +1149,7 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "blocklists"
- + @@ -1159,7 +1160,13 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "blocklists"
-
+
+ + + +
+ +
@@ -1187,6 +1194,11 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "blocklists" +
+ + + +
diff --git a/style/pi-hole.css b/style/pi-hole.css index 3fc896c8..541d5c6e 100644 --- a/style/pi-hole.css +++ b/style/pi-hole.css @@ -36,12 +36,47 @@ a.lookatme { -webkit-animation-iteration-count: infinite; animation-iteration-count: infinite; } -#all-queries { - table-layout: fixed; + +.table-responsive { + -webkit-overflow-scrolling: touch; } -#all-queries tbody { - word-wrap: break-word; + +/* Optimize Queries-Table for small screens */ +#all-queries td:nth-of-type(1), /* Time column */ +#all-queries td:nth-of-type(5) { /* Status column */ + white-space: nowrap; } +#all-queries td:nth-of-type(3) { /* Domain column */ + min-width: 200px; + word-break: break-all; + white-space: pre-wrap; +} +#all-queries_info { /* Allow Info String to wrap (useful while filtering entries on small screen) */ + white-space: unset; +} +#all-queries_wrapper .pagination > li > a { /* adjust the buttons width */ + padding-left: 6px; + padding-right: 6px; + min-width: 34px; + text-align: center; +} +@media screen and (max-width: 500px), + screen and (min-width: 767px) and (max-width: 1000px) { + /* Hide "Previous" & "Next"-Buttons in Pagination */ + #all-queries_wrapper .pagination > li.previous, + #all-queries_wrapper .pagination > li.next { + display: none; + } + #all-queries_wrapper .pagination > li:nth-of-type(2) a { + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + } + #all-queries_wrapper .pagination > li:nth-last-of-type(2) a { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + } +} + .main-header>.navbar { height: 50px; } @@ -50,3 +85,33 @@ a.lookatme { color: red; font-weight: bold; } + +.vertical-alignment-helper { + display: table; + width: 100%; + height: 100%; + pointer-events: none; +} +.vertical-alignment-helper > .vertical-align-center { + display: table-cell; + vertical-align: middle; +} +.vertical-alignment-helper > .vertical-align-center > .modal-content { + width: 250px; + margin-left: auto; + margin-right: auto; + word-wrap: break-word; + pointer-events: all; +} + +.alSpinner { + top: 0.1em; + left: 0.1em; + width: 0.8em; + height: 0.8em; + border-radius: 50%; + border: 4px solid silver; + border-right-color: transparent; + -webkit-animation: fa-spin 1s infinite linear; + animation: fa-spin 1s infinite linear; +}