diff --git a/cname_records.php b/cname_records.php
new file mode 100644
index 00000000..e6f98197
--- /dev/null
+++ b/cname_records.php
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+
+ Updating CNAME records...
+
+
+
+ Success! The list will refresh.
+
+
+
+ Failure! Something went wrong, see output below:
+
+
+
+ At least one domain was already present, see output below:
+
+
+
+
+
+
diff --git a/scripts/pi-hole/js/customcname.js b/scripts/pi-hole/js/customcname.js
new file mode 100644
index 00000000..49aebab2
--- /dev/null
+++ b/scripts/pi-hole/js/customcname.js
@@ -0,0 +1,116 @@
+/* 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. */
+
+var table;
+
+function showAlert(type, message) {
+ var alertElement = null;
+ var messageElement = null;
+
+ switch (type) {
+ case "info":
+ alertElement = $("#alInfo");
+ break;
+ case "success":
+ alertElement = $("#alSuccess");
+ break;
+ case "warning":
+ alertElement = $("#alWarning");
+ messageElement = $("#warn");
+ break;
+ case "error":
+ alertElement = $("#alFailure");
+ messageElement = $("#err");
+ break;
+ default:
+ return;
+ }
+
+ if (messageElement !== null) messageElement.html(message);
+
+ alertElement.fadeIn(200);
+ alertElement.delay(8000).fadeOut(2000);
+}
+
+$(document).ready(function () {
+ $("#btnAdd").on("click", addCustomCNAME);
+
+ table = $("#customCNAMETable").DataTable({
+ ajax: "scripts/pi-hole/php/customcname.php?action=get",
+ columns: [{}, {}, { orderable: false, searchable: false }],
+ columnDefs: [
+ {
+ targets: 2,
+ render: function (data, type, row) {
+ return (
+ '"
+ );
+ }
+ }
+ ],
+ drawCallback: function () {
+ $(".deleteCustomCNAME").on("click", deleteCustomCNAME);
+ }
+ });
+ // Disable autocorrect in the search box
+ var input = document.querySelector("input[type=search]");
+ input.setAttribute("autocomplete", "off");
+ input.setAttribute("autocorrect", "off");
+ input.setAttribute("autocapitalize", "off");
+ input.setAttribute("spellcheck", false);
+});
+
+function addCustomCNAME() {
+ var target = $("#target").val();
+ var domain = $("#domain").val();
+
+ showAlert("info");
+ $.ajax({
+ url: "scripts/pi-hole/php/customcname.php",
+ method: "post",
+ dataType: "json",
+ data: { action: "add", target: target, domain: domain },
+ success: function (response) {
+ if (response.success) {
+ showAlert("success");
+ table.ajax.reload();
+ } else showAlert("error", response.message);
+ },
+ error: function () {
+ showAlert("error", "Error while adding this custom CNAME record");
+ }
+ });
+}
+
+function deleteCustomCNAME() {
+ var target = $(this).attr("data-target");
+ var domain = $(this).attr("data-domain");
+
+ showAlert("info");
+ $.ajax({
+ url: "scripts/pi-hole/php/customcname.php",
+ method: "post",
+ dataType: "json",
+ data: { action: "delete", domain: domain, target: target },
+ success: function (response) {
+ if (response.success) {
+ showAlert("success");
+ table.ajax.reload();
+ } else showAlert("error", response.message);
+ },
+ error: function (jqXHR, exception) {
+ showAlert("error", "Error while deleting this custom CNAME record");
+ console.log(exception);
+ }
+ });
+}
diff --git a/scripts/pi-hole/php/customcname.php b/scripts/pi-hole/php/customcname.php
new file mode 100644
index 00000000..fcc5e862
--- /dev/null
+++ b/scripts/pi-hole/php/customcname.php
@@ -0,0 +1,141 @@
+domain, $entry->target ];
+
+ return [ "data" => $data ];
+ }
+
+ function getCustomCNAMEEntries()
+ {
+ global $customCNAMEFile;
+
+ $entries = [];
+
+ $handle = fopen($customCNAMEFile, "r");
+ if ($handle)
+ {
+ while (($line = fgets($handle)) !== false) {
+ $line = str_replace("cname=","", $line);
+ $line = str_replace("\r","", $line);
+ $line = str_replace("\n","", $line);
+ $explodedLine = explode (",", $line);
+
+ if (count($explodedLine) <= 1)
+ continue;
+
+ $data = new \stdClass();
+ $data->domains = array_slice($explodedLine, 0, -1);
+ $data->domain = implode(",", $data->domains);
+ $data->target = $explodedLine[count($explodedLine)-1];
+ $entries[] = $data;
+ }
+
+ fclose($handle);
+ }
+
+ return $entries;
+ }
+
+ function addCustomCNAMEEntry()
+ {
+ try
+ {
+ $target = !empty($_REQUEST['target']) ? $_REQUEST['target']: "";
+ $domain = !empty($_REQUEST['domain']) ? $_REQUEST['domain']: "";
+
+ if (empty($target))
+ return errorJsonResponse("Target must be set");
+
+ if (empty($domain))
+ return errorJsonResponse("Domain must be set");
+
+ // Check if each submitted domain is valid
+ $domains = array_map('trim', explode(",", $domain));
+ foreach ($domains as $d) {
+ if (!is_valid_domain_name($d))
+ return errorJsonResponse("Domain '$d' is not valid");
+ }
+
+ $existingEntries = getCustomCNAMEEntries();
+
+ // Check if a record for one of the domains already exists
+ foreach ($existingEntries as $entry)
+ foreach ($domains as $d)
+ if (in_array($d, $entry->domains))
+ return errorJsonResponse("There is already a CNAME record for '$d'");
+
+ exec("sudo pihole -a addcustomcname ".$domain." ".$target);
+
+ return successJsonResponse();
+ }
+ catch (\Exception $ex)
+ {
+ return errorJsonResponse($ex->getMessage());
+ }
+ }
+
+ function deleteCustomCNAMEEntry()
+ {
+ try
+ {
+ $target = !empty($_REQUEST['target']) ? $_REQUEST['target']: "";
+ $domain = !empty($_REQUEST['domain']) ? $_REQUEST['domain']: "";
+
+ if (empty($target))
+ return errorJsonResponse("Target must be set");
+
+ if (empty($domain))
+ return errorJsonResponse("Domain must be set");
+
+ $existingEntries = getCustomCNAMEEntries();
+
+ $found = false;
+ foreach ($existingEntries as $entry)
+ if ($entry->domain == $domain)
+ if ($entry->target == $target) {
+ $found = true;
+ break;
+ }
+
+ if (!$found)
+ return errorJsonResponse("This domain/ip association does not exist");
+
+ exec("sudo pihole -a removecustomcname ".$domain." ".$target);
+
+ return successJsonResponse();
+ }
+ catch (\Exception $ex)
+ {
+ return errorJsonResponse($ex->getMessage());
+ }
+ }
+
+ function successJsonResponse($message = "")
+ {
+ return [ "success" => true, "message" => $message ];
+ }
+
+ function errorJsonResponse($message = "")
+ {
+ return [ "success" => false, "message" => $message ];
+ }
+?>
diff --git a/scripts/pi-hole/php/header.php b/scripts/pi-hole/php/header.php
index 38f920aa..96a88465 100644
--- a/scripts/pi-hole/php/header.php
+++ b/scripts/pi-hole/php/header.php
@@ -608,10 +608,25 @@ if($auth) {
- class="active">
-
- Local DNS Records
-
+ active">
+
+
+
+
+ Local DNS
+
+