summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Bratch <luke@bratch.co.uk>2017-04-14 15:16:34 +0100
committerLuke Bratch <luke@bratch.co.uk>2017-04-14 15:16:34 +0100
commit9e254b13f3a1103a224f6759ac39c4398c46e0d9 (patch)
treee8fe03c278451cd3a87669eac7ba31502fdaa0f7
parent6d2eb372a4c5493029b8443c06a2b59b24893232 (diff)
Implement automatic record updating via HTTP(S) (sometimes called dynamic DNS)
-rw-r--r--dns.php102
1 files 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 "<p>no changes detected :)</p>";
+ } 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 "<p>record&#40;s&#41; updated :)</p>";
+ }
+ }
+} 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)) {
?>
- <p>Your zonefile is below. Ensure the first line always ends with a SHA-256 hash of your chosen password.</p>
+ <p>1. Your zonefile is below.</p>
+ <p>2. Ensure the first line always ends with a SHA-256 hash of your chosen password.</p>
+ <p>3. To automatically update a particular record, give the line a comment ending with a secret ID &#40;e.g. a SHA-256 hash&#41;, e.g.:</p>
+ <pre>
+test 300 IN A 192.168.0.1 ; sha256 = 7f480e744a79953eb916b68f540e0eeec6f9cf23edf4aa08cc1cdf5f077c0f6f
+test 300 IN AAAA ::1 ; sha256 = b493d48364afe44d11c0165cf470a4164d1e2609911ef998be868d46ade3de4e
+ </pre>
+ <p>And then do an HTTP&#40;S&#41; request to:</p>
+ <pre>https://bladns.net/dns.php?mode=update&zone=<strong>yourdomain.tld</strong>&hash=<strong>yourid</strong></pre>
+ <p>Your record will be updated with the IP you did the request from.</p>
<form action="dns.php" method="post">
<textarea rows="24" cols="80" name="zonetext" autofocus><?php echo $zonefile; ?></textarea><br>
<?php
@@ -127,7 +215,7 @@ if (isset($_POST['domain']) && isset($_POST['password']) && !isset($_POST['zonet
if (isset($_POST['increment'])) {
$zonetext = incrementserial($zonetext)[0];
}
- writezone($_POST['domain'], $_POST['password'], $zonetext);
+ writezone($_POST['domain'], $zonetext, $_POST['password']);
echo "<p>all done :)</p>";
} else {
?>