From 9e254b13f3a1103a224f6759ac39c4398c46e0d9 Mon Sep 17 00:00:00 2001 From: Luke Bratch Date: Fri, 14 Apr 2017 15:16:34 +0100 Subject: Implement automatic record updating via HTTP(S) (sometimes called dynamic DNS) --- dns.php | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 95 insertions(+), 7 deletions(-) diff --git a/dns.php b/dns.php index cbef901..8a3454d 100644 --- a/dns.php +++ b/dns.php @@ -59,7 +59,7 @@ function sshrun($command) { return stream_get_contents($stream_out); } -function getzone($domain, $password) { +function getzone($domain, $password = null) { if (!preg_match('/^[0-9A-Za-z\.\-]*$/', $domain)) { die("invalid domain"); } @@ -69,19 +69,24 @@ function getzone($domain, $password) { $zonelines = explode("\n", $string); $zonehash = explode(" ", $zonelines[0]); - if (strtolower($zonehash[sizeof($zonehash) - 1]) == strtolower($password)) { + // Not all calls to this function need a password + if (!$password) { + return $string; + } else if (strtolower($zonehash[sizeof($zonehash) - 1]) == strtolower($password)) { return $string; } else { return; } } -function writezone($domain, $password, $zonetext) { +function writezone($domain, $zonetext, $password = null) { if (!preg_match('/^[0-9A-Za-z\.\-]*$/', $domain)) { die("invalid domain"); } - if (!getzone($domain, $password)) { + // Not all calls to this function need a password. If passwords are in use, + // check to make sure it is correct. + if ($password && !getzone($domain, $password)) { die("somehow the password went bad"); } @@ -93,7 +98,81 @@ function writezone($domain, $password, $zonetext) { // Main entry point -if (isset($_POST['domain']) && isset($_POST['password']) && !isset($_POST['zonetext'])) { +if (isset($_GET['mode']) && isset($_GET['zone']) && isset($_GET['hash'])) { + // Some sort of mode (at the moment only "update" is supported + if ($_GET['mode'] == "update") { + // An auto update is being requested. This is where one or more zone records + // are requested to be updated to the requester's IP address. + $zonetext = getzone($_GET['zone']); + // Only use \n for newlines + $zonetext = str_replace("\r", "", $zonetext); + // Split into individual lines + $zonelines = explode("\n", $zonetext); + $matches = array(); + // Find lines that ended with the hash provided (if any) + $i = 0; + foreach ($zonelines as $line) { + if (preg_match("/" . $_GET['hash'] . "$/", $line)) { + array_push($matches, array($i, $line)); + } + $i++; + } + // Go through the lines that matched the hash and see if we can find an update to do + foreach ($matches as $arrline) { + $line = $arrline[1]; + // Get rid of the comment + $exploded = explode(";", $line); + $record = $exploded[0]; + // Split into individual components + $parts = preg_split("/\s+/", $record); + // Make sure the last element isn't blank (would happen if the comment had a space before it) + while ($parts[sizeof($parts) - 1] == "") { + array_pop($parts); + } + // See if the record type on this line matches the request address type (e.g. an A record update must be requested by an IPv4 address) + if ($parts[sizeof($parts) - 2] == "A") { + // Regex for IPv4 address + if (preg_match("/\b((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){4}\b/", $_SERVER['REMOTE_ADDR'])) { + $parts[sizeof($parts) - 1] = $_SERVER['REMOTE_ADDR']; + } else { + die("A record and non-IPv4 request - bad."); + } + } else if ($parts[sizeof($parts) - 2] == "AAAA") { + // Regex for IPv6 address + if (preg_match("/(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))/", $_SERVER['REMOTE_ADDR'])) { + $parts[sizeof($parts) - 1] = $_SERVER['REMOTE_ADDR']; + } else { + die("AAAA record and non-IPv6 request - bad."); + } + } else { + die("Unrecognised record type!"); + } + // Update original zone file array with the new line at the correct position + $zonelines[$arrline[0]] = preg_replace("/[A-Za-z0-9:\.]+([[:space:]]*;)/", $parts[sizeof($parts) - 1] . "$1", $line); + } + // Next we need to turn the zone file array back into a string again + $newzonetext = ""; + // If the last element was blank, remove it since we'll add a new blank ourselves + if ($zonelines[sizeof($zonelines) - 1] == "") { + array_pop($zonelines); + } + // Do the actual converstion to string + foreach ($zonelines as $line) { + $newzonetext .= $line . "\n"; + } + // See if there was actually a change as a result of this request... + if ($zonetext == $newzonetext) { + // ...if not, abandon ship + echo "

no changes detected :)

"; + } else { + // ...if so, increment the serial number and... + $newzonetext = incrementserial($newzonetext)[0]; + // ...finally write the zone with the updated record(s) + writezone($_GET['zone'], $newzonetext); + echo "

record(s) updated :)

"; + } + } +} else if (isset($_POST['domain']) && isset($_POST['password']) && !isset($_POST['zonetext'])) { if (!preg_match('/^[0-9A-Za-z\.\-]*$/', $_POST['domain'])) { die("invalid domain"); } @@ -102,7 +181,16 @@ if (isset($_POST['domain']) && isset($_POST['password']) && !isset($_POST['zonet if ($zonefile = getzone($_POST['domain'], $password)) { ?> -

Your zonefile is below. Ensure the first line always ends with a SHA-256 hash of your chosen password.

+

1. Your zonefile is below.

+

2. Ensure the first line always ends with a SHA-256 hash of your chosen password.

+

3. To automatically update a particular record, give the line a comment ending with a secret ID (e.g. a SHA-256 hash), e.g.:

+
+test    300     IN      A       192.168.0.1 ; sha256 = 7f480e744a79953eb916b68f540e0eeec6f9cf23edf4aa08cc1cdf5f077c0f6f
+test    300     IN      AAAA    ::1 ; sha256 = b493d48364afe44d11c0165cf470a4164d1e2609911ef998be868d46ade3de4e
+    
+

And then do an HTTP(S) request to:

+
https://bladns.net/dns.php?mode=update&zone=yourdomain.tld&hash=yourid
+

Your record will be updated with the IP you did the request from.


all done :)

"; } else { ?> -- cgit v1.2.3