diff --git a/api_db.php b/api_db.php index 7687fb6e..00c6ba2e 100644 --- a/api_db.php +++ b/api_db.php @@ -8,6 +8,7 @@ $api = true; header('Content-type: application/json'); +require("scripts/pi-hole/php/database.php"); require("scripts/pi-hole/php/password.php"); require("scripts/pi-hole/php/auth.php"); check_cors(); @@ -48,7 +49,7 @@ function resolveHostname($clientip, $printIP) return $clientname; } -// Get posible non-standard location of FTL's database +// Get possible non-standard location of FTL's database $FTLsettings = parse_ini_file("/etc/pihole/pihole-FTL.conf"); if(isset($FTLsettings["DBFILE"])) { @@ -62,37 +63,7 @@ else // Needs package php5-sqlite, e.g. // sudo apt-get install php5-sqlite -function SQLite3_connect($trytoreconnect) -{ - global $DBFILE; - try - { - // connect to database - return new SQLite3($DBFILE, SQLITE3_OPEN_READONLY); - } - catch (Exception $exception) - { - // sqlite3 throws an exception when it is unable to connect, try to reconnect after 3 seconds - if($trytoreconnect) - { - sleep(3); - $db = SQLite3_connect(false); - } - } -} - -if(strlen($DBFILE) > 0) -{ - $db = SQLite3_connect(true); -} -else -{ - die("No database available"); -} -if(!$db) -{ - die("Error connecting to database"); -} +$db = SQLite3_connect($DBFILE); if(isset($_GET["network"]) && $auth) { diff --git a/scripts/pi-hole/js/list.js b/scripts/pi-hole/js/list.js index 530a3b3b..b21ba955 100644 --- a/scripts/pi-hole/js/list.js +++ b/scripts/pi-hole/js/list.js @@ -12,29 +12,24 @@ var token = $("#token").html(); var listType = $("#list-type").html(); var fullName = listType === "white" ? "Whitelist" : "Blacklist"; -function sub(index, entry, arg) { - var domain = $("#list #"+index); - var locallistType = listType; - if(arg === "regex") - { - locallistType = "regex"; - domain = $("#list-regex #"+index); - } - domain.hide("highlight"); - $.ajax({ - url: "scripts/pi-hole/php/sub.php", - method: "post", - data: {"domain":entry, "list":locallistType, "token":token}, - success: function(response) { - if(response.length !== 0){ - return; - } - domain.remove(); - }, - error: function(jqXHR, exception) { - alert("Failed to remove the domain!"); - domain.show({queue:true}); - } +function addListEntry(entry, index, list, button, type) +{ + var used = entry.enabled === "1" ? "used" : "not-used"; + var comment = entry.comment.length > 0 ? " - " + entry.comment : ""; + var date_added = new Date(parseInt(entry.date_added)*1000); + var date_modified = new Date(parseInt(entry.date_modified)*1000); + var tooltip = "Added: " + date_added.toLocaleString() + + "\nModified: " + date_modified.toLocaleString(); + list.append( + "
  • " + + "" + + entry.domain + comment + "" + + "
  • " + ); + // Handle button + $(button+" #"+index).on("click", "button", function() { + sub(index, entry.domain, type); }); } @@ -62,53 +57,39 @@ function refresh(fade) { { listw.html(""); } - var data = JSON.parse(response); - if(data.length === 0) { + if((listType === "black" && + response.blacklist.length === 0 && + response.regex.length === 0) || + (listType === "white" && + response.whitelist.length === 0)) + { $("h3").hide(); - if(listw) - { - listw.html("
    Your " + fullName + " is empty!
    "); - } - else - { - list.html("
    Your " + fullName + " is empty!
    "); - } + list.html("
    Your " + fullName + " is empty!
    "); } else { $("h3").show(); - data[0] = data[0].sort(); - data[0].forEach(function (entry, index) { - // Whitelist entry or Blacklist (exact entry) are in the zero-th - // array returned by get.php - list.append( - "
  • " + entry + - "
  • "); - // Handle button - $("#list #"+index+"").on("click", "button", function() { - sub(index, entry, "exact"); - }); + if(listType === "white") + { + data = response.whitelist.sort(); + data2 = []; // No regex data, use empty array + } + else if(listType === "black") + { + data = response.blacklist.sort(); + data2 = response.regex.sort(); + } + data.forEach(function (entry, index) + { + addListEntry(entry, index, list, "#list", "exact"); }); // Add regex domains if present in returned list data - if(data.length === 2) + data2.forEach(function (entry, index) { - data[1] = data[1].sort(); - data[1].forEach(function (entry, index) { - // Whitelist entry or Blacklist (exact entry) are in the zero-th - // array returned by get.php - listw.append( - "
  • " + entry + - "
  • "); - // Handle button - $("#list-regex #"+index+"").on("click", "button", function() { - sub(index, entry, "regex"); - }); - }); - } + addListEntry(entry, index, listw, "#list-regex", "regex"); + }); } list.fadeIn(100); if(listw) @@ -124,6 +105,32 @@ function refresh(fade) { window.onload = refresh(false); +function sub(index, entry, arg) { + var domain = $("#list #"+index); + var locallistType = listType; + if(arg === "regex") + { + locallistType = "regex"; + domain = $("#list-regex #"+index); + } + domain.hide("highlight"); + $.ajax({ + url: "scripts/pi-hole/php/sub.php", + method: "post", + data: {"domain":entry, "list":locallistType, "token":token}, + success: function(response) { + if(response.length !== 0){ + return; + } + domain.remove(); + }, + error: function(jqXHR, exception) { + alert("Failed to remove the domain!"); + domain.show({queue:true}); + } + }); +} + function add(arg) { var locallistType = listType; var domain = $("#domain"); @@ -152,7 +159,7 @@ function add(arg) { method: "post", data: {"domain":domain.val().trim(), "list":locallistType, "token":token}, success: function(response) { - if (!wild && response.indexOf(" already exists in ") !== -1) { + if (response.indexOf(" already exists in ") !== -1) { alWarning.show(); warn.html(response); alWarning.delay(8000).fadeOut(2000, function() { @@ -161,8 +168,7 @@ function add(arg) { alInfo.delay(8000).fadeOut(2000, function() { alInfo.hide(); }); - } else if (!wild && response.indexOf("] Pi-hole blocking is ") === -1 || - wild && response.length > 1) { + } else if (response.indexOf("DONE") === -1) { alFailure.show(); err.html(response); alFailure.delay(8000).fadeOut(2000, function() { diff --git a/scripts/pi-hole/php/add.php b/scripts/pi-hole/php/add.php index 762ea2cf..dbb9df2e 100644 --- a/scripts/pi-hole/php/add.php +++ b/scripts/pi-hole/php/add.php @@ -22,36 +22,36 @@ if($type !== "regex") { check_domain(); } +// Escape shell metacharacters +$domains = escapeshellcmd($_POST['domain']); + switch($type) { case "white": if(!isset($_POST["auditlog"])) - echo shell_exec("sudo pihole -w ${_POST['domain']}"); + echo shell_exec("sudo pihole -w --web ".$domains); else { - echo shell_exec("sudo pihole -w -n ${_POST['domain']}"); - echo shell_exec("sudo pihole -a audit ${_POST['domain']}"); + echo shell_exec("sudo pihole -w --web ".$domains); + echo shell_exec("sudo pihole -a audit ".$domains); } break; case "black": if(!isset($_POST["auditlog"])) - echo shell_exec("sudo pihole -b ${_POST['domain']}"); + echo shell_exec("sudo pihole -b --web ".$domains); else { - echo shell_exec("sudo pihole -b -n ${_POST['domain']}"); - echo shell_exec("sudo pihole -a audit ${_POST['domain']}"); + echo shell_exec("sudo pihole -b --web ".$domains); + echo shell_exec("sudo pihole -a audit ".$domains); } break; - case "wild": - // Escape "." so it won't be interpreted as the wildcard character - $domain = str_replace(".","\.",$_POST['domain']); - // Add regex filter for legacy wildcard behavior - add_regex("(^|\.)".$domain."$"); - break; case "regex": - add_regex($_POST['domain']); + echo shell_exec("sudo pihole --regex --web ".$domains); + break; + case "wild": + echo shell_exec("sudo pihole --wild --web ".$domains); break; case "audit": - echo exec("sudo pihole -a audit ${_POST['domain']}"); + echo exec("sudo pihole -a audit ".$domain); break; } diff --git a/scripts/pi-hole/php/database.php b/scripts/pi-hole/php/database.php new file mode 100644 index 00000000..3a453677 --- /dev/null +++ b/scripts/pi-hole/php/database.php @@ -0,0 +1,57 @@ + 0) + { + $db = SQLite3_connect_try($filename, $mode, true); + } + else + { + die("No database available"); + } + if(!$db) + { + die("Error connecting to database"); + } + return $db; +} +?> diff --git a/scripts/pi-hole/php/get.php b/scripts/pi-hole/php/get.php index 156622c7..d2b75b95 100644 --- a/scripts/pi-hole/php/get.php +++ b/scripts/pi-hole/php/get.php @@ -11,57 +11,60 @@ if(!isset($_GET['list'])) $listtype = $_GET['list']; -$basedir = "/etc/pihole/"; +require_once("func.php"); -require_once "func.php"; +require("database.php"); +$GRAVITYDB = getGravityDBFilename(); +$db = SQLite3_connect($GRAVITYDB); -switch ($listtype) { - case "white": - $list = array(getListContent("whitelist.txt")); - break; +function getTableContent($listname) { + global $db; + $entries = array(); + $results = $db->query("SELECT * FROM $listname"); - case "black": - $exact = getListContent("blacklist.txt"); - $regex = getListContent("regex.list"); - $list = array($exact, $regex); - break; - - default: - die("Invalid list parameter"); - break; -} - - -function getListContent($listname) { - global $basedir; - $rawList = file_get_contents(checkfile($basedir.$listname)); - $list = explode("\n", $rawList); - - // Get rid of empty lines and comments - for($i = sizeof($list)-1; $i >= 0; $i--) { - if(strlen($list[$i]) < 1 || $list[$i][0] === '#') - unset($list[$i]); - } - - // Re-index list after possible unset() activity - $newlist = array_values($list); - - return $newlist; + while($results !== false && $res = $results->fetchArray(SQLITE3_ASSOC)) + { + array_push($entries, $res); + } + return array($listname => $entries); } function filterArray(&$inArray) { - $outArray = array(); - foreach ($inArray as $key=>$value) { - if (is_array($value)) { - $outArray[htmlspecialchars($key)] = filterArray($value); - } else { - $outArray[htmlspecialchars($key)] = htmlspecialchars($value); - } - } - return $outArray; + $outArray = array(); + foreach ($inArray as $key => $value) + { + if (is_array($value)) + { + $outArray[htmlspecialchars($key)] = filterArray($value); + } + else + { + $outArray[htmlspecialchars($key)] = htmlspecialchars($value); + } + } + return $outArray; } +switch ($listtype) +{ + case "white": + $list = getTableContent("whitelist"); + break; + + case "black": + $exact = getTableContent("blacklist"); + $regex = getTableContent("regex"); + $list = array_merge($exact, $regex); + break; + + default: + die("Invalid list parameter"); + break; +} // Protect against XSS attacks -$list = filterArray($list); -echo json_encode(array_values($list)); +$output = filterArray($list); + +// Return results +header('Content-type: application/json'); +echo json_encode($output); diff --git a/scripts/pi-hole/php/savesettings.php b/scripts/pi-hole/php/savesettings.php index fc358d66..ed7f2556 100644 --- a/scripts/pi-hole/php/savesettings.php +++ b/scripts/pi-hole/php/savesettings.php @@ -155,37 +155,22 @@ function readDNSserversList() return $list; } +require_once("database.php"); $adlist = []; function readAdlists() { // Reset list $list = []; - $handle = @fopen("/etc/pihole/adlists.list", "r"); - if ($handle) + $db = SQLite3_connect(getGravityDBFilename()); + if ($db) { - while (($line = fgets($handle)) !== false) + $results = $db->query("SELECT * FROM adlists"); + + while($results !== false && $res = $results->fetchArray(SQLITE3_ASSOC)) { - if(strlen($line) < 3) - { - continue; - } - elseif($line[0] === "#") - { - // Comments start either with "##" or "# " - if($line[1] !== "#" && - $line[1] !== " ") - { - // Commented list - array_push($list, [false,rtrim(substr($line, 1))]); - } - } - else - { - // Active list - array_push($list, [true,rtrim($line)]); - } + array_push($list, $res); } - fclose($handle); + $db->close(); } return $list; } @@ -689,18 +674,18 @@ function readAdlists() if(isset($_POST["adlist-del-".$key])) { // Delete list - exec("sudo pihole -a adlist del ".escapeshellcmd($value[1])); + exec("sudo pihole -a adlist del ".escapeshellcmd($value["address"])); } - elseif(isset($_POST["adlist-enable-".$key]) && !$value[0]) + elseif(isset($_POST["adlist-enable-".$key]) && $value["enabled"] !== 1) { // Is not enabled, but should be - exec("sudo pihole -a adlist enable ".escapeshellcmd($value[1])); + exec("sudo pihole -a adlist enable ".escapeshellcmd($value["address"])); } - elseif(!isset($_POST["adlist-enable-".$key]) && $value[0]) + elseif(!isset($_POST["adlist-enable-".$key]) && $value["enabled"] === 1) { // Is enabled, but shouldn't be - exec("sudo pihole -a adlist disable ".escapeshellcmd($value[1])); + exec("sudo pihole -a adlist disable ".escapeshellcmd($value["address"])); } } diff --git a/scripts/pi-hole/php/sub.php b/scripts/pi-hole/php/sub.php index 1c9bc8fe..97c8f84b 100644 --- a/scripts/pi-hole/php/sub.php +++ b/scripts/pi-hole/php/sub.php @@ -16,43 +16,24 @@ if (empty($api)) { list_verify($type); } -// Don't check if the added item is a valid domain for regex expressions. Regex -// filters are validated by FTL on import and skipped if invalid +// Don't check if the added item is a valid domain for regex expressions. +// Regex filters are validated by FTL on import and skipped if invalid if($type !== "regex") { check_domain(); } +// Escape shell metacharacters +$domain = escapeshellcmd($_POST['domain']); + switch($type) { case "white": - exec("sudo pihole -w -q -d ${_POST['domain']}"); + exec("sudo pihole -w -q -d ".$domain); break; case "black": - exec("sudo pihole -b -q -d ${_POST['domain']}"); + exec("sudo pihole -b -q -d ".$domain); break; case "regex": - if(($list = file_get_contents($regexfile)) === FALSE) - { - $err = error_get_last()["message"]; - echo "Unable to read ${regexfile}
    Error message: $err"; - } - - // Remove the regex and any empty lines from the list - $list = explode("\n", $list); - $list = array_diff($list, array($_POST['domain'], "")); - $list = implode("\n", $list); - - if(file_put_contents($regexfile, $list."\n") === FALSE) - { - $err = error_get_last()["message"]; - echo "Unable to remove regex \"".htmlspecialchars($_POST['domain'])."\" from ${regexfile}
    Error message: $err"; - } - else - { - // Send SIGHUP to pihole-FTL using a frontend command - // to force reloading of the regex domains - // This will also wipe the resolver's cache - echo exec("sudo pihole restartdns reload"); - } + exec("sudo pihole --regex -q -d ".$domain); break; } diff --git a/settings.php b/settings.php index 3a4f8ffe..8c22b7ee 100644 --- a/settings.php +++ b/settings.php @@ -257,8 +257,11 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "blocklists" - + + + + @@ -266,10 +269,19 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "blocklists" $value) { ?> + + +
    EnabledEnabled ListAddedLast modifiedComment Delete
    - checked> + checked> - + " target="_new" id="adlist-text-"> + + + + + +
    - +
    diff --git a/style/pi-hole.css b/style/pi-hole.css index eec770c6..311731c5 100644 --- a/style/pi-hole.css +++ b/style/pi-hole.css @@ -24,6 +24,23 @@ .skin-blue .list-group-item:hover { background: #ddd; } + +.skin-blue .not-used { + background: #eee; +} + +.skin-blue .not-used:hover { + background: #c5c5c95; +} + +.skin-blue .used { + background: #fff; +} + +.skin-blue .used:hover { + background: #ddd; +} + @-webkit-keyframes Pulse{ from {color:#630030;-webkit-text-shadow:0 0 2px transparent;} 50% {color:#e33100;-webkit-text-shadow:0 0 5px #e33100;}