<?php /* Pi-hole: A black hole for Internet advertisements * (c) 2017 Pi-hole, LLC (https://pi-hole.net) * Network-wide ad blocking via your own hardware. * * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ // Sanitize SERVER_NAME output $serverName = htmlspecialchars($_SERVER["SERVER_NAME"]); // Remove external ipv6 brackets if any $serverName = preg_replace('/^\[(.*)\]$/', '${1}', $serverName); if (!is_file("/etc/pihole/setupVars.conf")) die("[ERROR] File not found: <code>/etc/pihole/setupVars.conf</code>"); // Get values from setupVars.conf $setupVars = parse_ini_file("/etc/pihole/setupVars.conf"); $svPasswd = !empty($setupVars["WEBPASSWORD"]); $svEmail = (!empty($setupVars["ADMIN_EMAIL"]) && filter_var($setupVars["ADMIN_EMAIL"], FILTER_VALIDATE_EMAIL)) ? $setupVars["ADMIN_EMAIL"] : ""; unset($setupVars); // Set landing page location, found within /var/www/html/ $landPage = "../landing.php"; // Define array for hostnames to be accepted as self address for splash page $authorizedHosts = [ "localhost" ]; if (!empty($_SERVER["FQDN"])) { // If setenv.add-environment = ("fqdn" => "true") is configured in lighttpd, // append $serverName to $authorizedHosts array_push($authorizedHosts, $serverName); } else if (!empty($_SERVER["VIRTUAL_HOST"])) { // Append virtual hostname to $authorizedHosts array_push($authorizedHosts, $_SERVER["VIRTUAL_HOST"]); } // Set which extension types render as Block Page (Including "" for index.ext) $validExtTypes = array("asp", "htm", "html", "php", "rss", "xml", ""); // Get extension of current URL $currentUrlExt = pathinfo($_SERVER["REQUEST_URI"], PATHINFO_EXTENSION); // Set mobile friendly viewport $viewPort = '<meta name="viewport" content="width=device-width, initial-scale=1">'; // Set response header function setHeader($type = "x") { header("X-Pi-hole: A black hole for Internet advertisements."); if (isset($type) && $type === "js") header("Content-Type: application/javascript"); } // Determine block page type if ($serverName === "pi.hole" || (!empty($_SERVER["VIRTUAL_HOST"]) && $serverName === $_SERVER["VIRTUAL_HOST"])) { // Redirect to Web Interface exit(header("Location: /admin")); } elseif (filter_var($serverName, FILTER_VALIDATE_IP) || in_array($serverName, $authorizedHosts)) { // When directly browsing via IP or authorized hostname // Render splash/landing page based off presence of $landPage file // Unset variables so as to not be included in $landPage or $splashPage unset($svPasswd, $svEmail, $authorizedHosts, $validExtTypes, $currentUrlExt); // If $landPage file is present if (is_file(getcwd()."/$landPage")) { unset($serverName, $viewPort); // unset extra variables not to be included in $landpage include $landPage; exit(); } // If $landPage file was not present, Set Splash Page output $splashPage = <<<EOT <!doctype html> <html lang='en'> <head> <meta charset='utf-8'> $viewPort <title>● $serverName</title> <link rel='stylesheet' href='/pihole/blockingpage.css'> <link rel='shortcut icon' href='/admin/img/favicons/favicon.ico' type='image/x-icon'> </head> <body id='splashpage'> <div id="pihole_card"> <img src='/admin/img/logo.svg' alt='Pi-hole logo' id="pihole_logo_splash" /> <p>Pi-<strong>hole</strong>: Your black hole for Internet advertisements</p> <a href='/admin'>Did you mean to go to the admin panel?</a> </div> </body> </html> EOT; exit($splashPage); } elseif ($currentUrlExt === "js") { // Serve Pi-hole JavaScript for blocked domains requesting JS exit(setHeader("js").'var x = "Pi-hole: A black hole for Internet advertisements."'); } elseif (strpos($_SERVER["REQUEST_URI"], "?") !== FALSE && isset($_SERVER["HTTP_REFERER"])) { // Serve blank image upon receiving REQUEST_URI w/ query string & HTTP_REFERRER // e.g: An iframe of a blocked domain exit(setHeader().'<!doctype html> <html lang="en"> <head> <meta charset="utf-8"><script>window.close();</script> </head> <body> <img src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs="> </body> </html>'); } elseif (!in_array($currentUrlExt, $validExtTypes) || substr_count($_SERVER["REQUEST_URI"], "?")) { // Serve SVG upon receiving non $validExtTypes URL extension or query string // e.g: Not an iframe of a blocked domain, such as when browsing to a file/query directly // QoL addition: Allow the SVG to be clicked on in order to quickly show the full Block Page $blockImg = '<a href="/"> <svg xmlns="http://www.w3.org/2000/svg" width="110" height="16"> <circle cx="8" cy="8" r="7" fill="none" stroke="rgba(152,2,2,.5)" stroke-width="2"/> <path fill="rgba(152,2,2,.5)" d="M11.526 3.04l1.414 1.415-8.485 8.485-1.414-1.414z"/> <text x="19.3" y="12" opacity=".3" style="font:11px Arial"> Blocked by Pi-hole </text> </svg> </a>'; exit(setHeader()."<!doctype html> <html lang='en'> <head> <meta charset='utf-8'> $viewPort </head> <body>$blockImg</body> </html>"); } /* Start processing Block Page from here */ // Define admin email address text based off $svEmail presence $bpAskAdmin = !empty($svEmail) ? '<a href="mailto:'.$svEmail.'?subject=Site Blocked: '.$serverName.'"></a>' : "<span/>"; // Get possible non-standard location of FTL's database $FTLsettings = parse_ini_file("/etc/pihole/pihole-FTL.conf"); if (isset($FTLsettings["GRAVITYDB"])) { $gravityDBFile = $FTLsettings["GRAVITYDB"]; } else { $gravityDBFile = "/etc/pihole/gravity.db"; } // Connect to gravity.db try { $db = new SQLite3($gravityDBFile, SQLITE3_OPEN_READONLY); } catch (Exception $exception) { die("[ERROR]: Failed to connect to gravity.db"); } // Get all adlist addresses $adlistResults = $db->query("SELECT address FROM vw_adlist"); $adlistsUrls = array(); while ($row = $adlistResults->fetchArray()) { array_push($adlistsUrls, $row[0]); } if (empty($adlistsUrls)) die("[ERROR]: There are no adlists enabled"); // Get total number of blocklists (Including Whitelist, Blacklist & Wildcard lists) $adlistsCount = count($adlistsUrls) + 3; // Set query timeout ini_set("default_socket_timeout", 3); // Logic for querying blocklists function queryAds($serverName) { // Determine the time it takes while querying adlists $preQueryTime = microtime(true)-$_SERVER["REQUEST_TIME_FLOAT"]; // Determine which protocol should be used $protocol = "http"; if ( (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') || (isset($_SERVER['REQUEST_SCHEME']) && $_SERVER['REQUEST_SCHEME'] === 'https') || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') ) { $protocol = "https"; } // Format the URL $queryAdsURL = sprintf( "%s://127.0.0.1:%s/admin/scripts/pi-hole/php/queryads.php?domain=%s&bp", $protocol, $_SERVER["SERVER_PORT"], $serverName ); // Request the file and receive the response $queryAdsFile = file($queryAdsURL, FILE_IGNORE_NEW_LINES); // $queryAdsFile must be an array (to avoid PHP 8.0+ error) if (is_array($queryAdsFile)) { $queryAds = array_values(array_filter(preg_replace("/data:\s+/", "", $queryAdsFile))); } else { // if not an array, return an error message return array("0" => "error", "1" => "<br>Not an array: (".gettype($queryAdsFile).")<br>".print_r($queryAdsFile, true)); } $queryTime = sprintf("%.0f", (microtime(true)-$_SERVER["REQUEST_TIME_FLOAT"]) - $preQueryTime); // Exception Handling try { // Define Exceptions if (strpos($queryAds[0], "No exact results") !== FALSE) { // Return "none" into $queryAds array return array("0" => "none"); } else if ($queryTime >= ini_get("default_socket_timeout")) { // Connection Timeout throw new Exception ("Connection timeout (".ini_get("default_socket_timeout")."s)"); } elseif (!strpos($queryAds[0], ".") !== false) { // Unknown $queryAds output throw new Exception ("Unhandled error message (<code>$queryAds[0]</code>)"); } return $queryAds; } catch (Exception $e) { // Return exception as array return array("0" => "error", "1" => $e->getMessage()); } } // Get results of queryads.php exact search $queryAds = queryAds($serverName); // Pass error through to Block Page if ($queryAds[0] === "error") die("[ERROR]: Unable to parse results from <i>queryads.php</i>: <code>".$queryAds[1]."</code>"); // Count total number of matching blocklists $featuredTotal = count($queryAds); // Place results into key => value array $queryResults = null; foreach ($queryAds as $str) { $value = explode(" ", $str); @$queryResults[$value[0]] .= "$value[1]"; } // Determine if domain has been blacklisted, whitelisted, wildcarded or CNAME blocked if (strpos($queryAds[0], "blacklist") !== FALSE) { $notableFlagClass = "blacklist"; $adlistsUrls = array("π" => substr($queryAds[0], 2)); } elseif (strpos($queryAds[0], "whitelist") !== FALSE) { $notableFlagClass = "noblock"; $adlistsUrls = array("π" => substr($queryAds[0], 2)); $wlInfo = "recentwl"; } elseif (strpos($queryAds[0], "wildcard") !== FALSE) { $notableFlagClass = "wildcard"; $adlistsUrls = array("π" => substr($queryAds[0], 2)); } elseif ($queryAds[0] === "none") { $featuredTotal = "0"; $notableFlagClass = "noblock"; // QoL addition: Determine appropriate info message if CNAME exists // Suggests to the user that $serverName has a CNAME (alias) that may be blocked $dnsRecord = dns_get_record("$serverName")[0]; if (array_key_exists("target", $dnsRecord)) { $wlInfo = $dnsRecord['target']; } else { $wlInfo = "unknown"; } } // Set #bpOutput notification $wlOutputClass = (isset($wlInfo) && $wlInfo === "recentwl") ? $wlInfo : "hidden"; $wlOutput = (isset($wlInfo) && $wlInfo !== "recentwl") ? "<a href='http://$wlInfo'>$wlInfo</a>" : ""; // Get Pi-hole Core version $phVersion = exec("cd /etc/.pihole/ && git describe --long --tags"); // Print $execTime on development branches // Testing for - is marginally faster than "git rev-parse --abbrev-ref HEAD" if (explode("-", $phVersion)[1] != "0") $execTime = microtime(true)-$_SERVER["REQUEST_TIME_FLOAT"]; // Please Note: Text is added via CSS to allow an admin to provide a localized // language without the need to edit this file setHeader(); ?> <!doctype html> <!-- Pi-hole: A black hole for Internet advertisements * (c) 2017 Pi-hole, LLC (https://pi-hole.net) * Network-wide ad blocking via your own hardware. * * This file is copyright under the latest version of the EUPL. --> <html> <head> <meta charset="utf-8"> <?=$viewPort ?> <meta name="robots" content="noindex,nofollow"> <meta http-equiv="x-dns-prefetch-control" content="off"> <link rel="stylesheet" href="pihole/blockingpage.css"> <link rel="shortcut icon" href="admin/img/favicons/favicon.ico" type="image/x-icon"> <title>● <?=$serverName ?></title> <script src="admin/scripts/vendor/jquery.min.js"></script> <script> window.onload = function () { <?php // Remove href fallback from "Back to safety" button if ($featuredTotal > 0) { echo '$("#bpBack").removeAttr("href");'; // Enable whitelisting if JS is available echo '$("#bpWhitelist").prop("disabled", false);'; // Enable password input if necessary if (!empty($svPasswd)) { echo '$("#bpWLPassword").attr("placeholder", "Password");'; echo '$("#bpWLPassword").prop("disabled", false);'; } // Otherwise hide the input else { echo '$("#bpWLPassword").hide();'; } } ?> } </script> </head> <body id="blockpage"><div id="bpWrapper"> <header> <h1 id="bpTitle"> <a class="title" href="/"><?php //Website Blocked ?></a> </h1> <div class="spc"></div> <input id="bpAboutToggle" type="checkbox"> <div id="bpAbout"> <div class="aboutPH"> <div class="aboutImg"></div> <p>Open Source Ad Blocker <small>Designed for Raspberry Pi</small> </p> </div> <div class="aboutLink"> <a class="linkPH" href="https://docs.pi-hole.net/"><?php //About PH ?></a> <?php if (!empty($svEmail)) echo '<a class="linkEmail" href="mailto:'.$svEmail.'"></a>'; ?> </div> </div> <div id="bpAlt"> <label class="altBtn" for="bpAboutToggle"><?php //Why am I here? ?></label> </div> </header> <main> <div id="bpOutput" class="<?=$wlOutputClass ?>"><?=$wlOutput ?></div> <div id="bpBlock"> <p class="blockMsg"><?=$serverName ?></p> </div> <?php if(isset($notableFlagClass)) { ?> <div id="bpFlag"> <p class="flagMsg <?=$notableFlagClass ?>"></p> </div> <?php } ?> <div id="bpHelpTxt"><?=$bpAskAdmin ?></div> <div id="bpButtons" class="buttons"> <a id="bpBack" onclick="javascript:history.back()" href="about:home"></a> <?php if ($featuredTotal > 0) echo '<label id="bpInfo" for="bpMoreToggle"></label>'; ?> </div> <input id="bpMoreToggle" type="checkbox"> <div id="bpMoreInfo"> <span id="bpFoundIn"><span><?=$featuredTotal ?></span><?=$adlistsCount ?></span> <pre id='bpQueryOutput'><?php if ($featuredTotal > 0) foreach ($queryResults as $num => $value) { echo "<span>[$num]:</span>$adlistsUrls[$num]\n"; } ?></pre> <form id="bpWLButtons" class="buttons"> <input id="bpWLDomain" type="text" value="<?=$serverName ?>" disabled> <input id="bpWLPassword" type="password" placeholder="JavaScript disabled" disabled> <button id="bpWhitelist" type="button" disabled></button> </form> </div> </main> <footer><span><?=date("l g:i A, F dS"); ?>.</span> Pi-hole <?=$phVersion ?> (<?=gethostname()."/".$_SERVER["SERVER_ADDR"]; if (isset($execTime)) printf("/%.2fs", $execTime); ?>)</footer> </div> <script> function add() { $("#bpOutput").removeClass("hidden error exception"); $("#bpOutput").addClass("add"); var domain = "<?=$serverName ?>"; var pw = $("#bpWLPassword"); if(domain.length === 0) { return; } $.ajax({ url: "/admin/scripts/pi-hole/php/add.php", method: "post", data: {"domain":domain, "list":"white", "pw":pw.val()}, success: function(response) { if(response.indexOf("Pi-hole blocking") !== -1) { setTimeout(function(){window.location.reload(1);}, 10000); $("#bpOutput").removeClass("add"); $("#bpOutput").addClass("success"); $("#bpOutput").html(""); } else { $("#bpOutput").removeClass("add"); $("#bpOutput").addClass("error"); $("#bpOutput").html(""+response+""); } }, error: function(jqXHR, exception) { $("#bpOutput").removeClass("add"); $("#bpOutput").addClass("exception"); $("#bpOutput").html(""); } }); } <?php if ($featuredTotal > 0) { ?> $(document).keypress(function(e) { if(e.which === 13 && $("#bpWLPassword").is(":focus")) { add(); } }); $("#bpWhitelist").on("click", function() { add(); }); <?php } ?> </script> </body></html>