FindPage
View Source:
dyndns
Note:
You are viewing an old version of this page.
View the current version.
Using isc-bind-9 * Create an update key Create a key in /etc/namedb/ or the base config dir of your bind installation. <code brush="bash"> [cyberleo@mtumishi ~]$ dnssec-keygen -a HMAC-MD5 -b 512 -n HOST update Kupdate.+157+16663 [cyberleo@mtumishi ~]$ </code> This will create two files (?) -- The numbers in the names will vary, depending on.. I dunno why. Read up on dnssec-keygen for more details. <code brush="bash"> -rw------- 1 cyberleo users 115 Dec 11 08:25 Kupdate.+157+16663.key -rw------- 1 cyberleo users 145 Dec 11 08:25 Kupdate.+157+16663.private </code> Make those two files readable by your webserver user. <code brush="bash"> chown root:www Kupdate.+157+16663.* chmod 640 Kupdate.+157+16663.* </code> We will need to take the generated key (the base64-encoded hash present in either of the two files) and format it so that it will fit into Bind's config file. <code brush="bash"> [cyberleo@mtumishi ~]$ cat Kupdate.+157+16663.private Private-key-format: v1.2 Algorithm: 157 (HMAC_MD5) Key: KtxcPgvCCn5unobfWXejTT8JKcqcFBjM2hx/sToM8DERlcy6ZkQTdLJf2Hru3qSlcFIfs5cE7pSRNtsTO1TWdg== [cyberleo@mtumishi ~]$ cat > update.key <<EOF key "update" { algorithm hmac-md5; secret "KtxcPgvCCn5unobfWXejTT8JKcqcFBjM2hx/sToM8DERlcy6ZkQTdLJf2Hru3qSlcFIfs5cE7pSRNtsTO1TWdg=="; }; EOF [cyberleo@mtumishi ~]$ </code> * Configure your zone I picked a subdomain to control, which would not get propagated to my slaves. To ensure the zone shows up properly, I had to create a and ns records for the root of the subdomain in my main zone. The A record points the root of the zone (dyn.cyberleo.net) to the server that will house the update script. The NS record ensures that anyone who asks my slave about any entries under this subdomain is told to ask the master, instead of being given a 'doesn't exist' response. <code brush="plain"> $ORIGIN cyberleo.net. dyn 3600 IN A 69.72.129.14 ;Cl=2 dyn 3600 IN NS ns1.cyberleo.net. ;Cl=2 </code> The subdomain itself is set up as a standard dynamic zone in bind. <code brush="plain"> include "/etc/namedb/update.key"; zone "dyn.cyberleo.net" { type master; file "dynamic/dyn.cyberleo.net"; allow-update { key update; }; allow-transfer { 127.0.0.1; }; }; </code> * Server update script Drop this on your webserver, in a vhost directory assigned to the zone you just configured. Pay attention to the zone and sec variables. sec is a rudimentary shared secret that is used to validate updates. Also keep in mind the ordering of the variables down there, where it computes the md5 hash. This is important, because it prevents someone from using any information in the submission to change the zone, and prevents the submission from being accepted an hour later. The security token (sec) can also be used by itself to change any zone, so be careful with it! <code brush="php"> <?php $ns = 'localhost'; $zone='dyn.cyberleo.net'; $ttl = 30; $now = gmdate('YmdH'); $sec = "SecuritySecret"; $nsupdate_key = "/etc/namedb/Kupdate.+157+16663.private"; define("SIGNATURE", "nsupdate/1.0.1 at {$zone} port 80"); function nsupdate($host, $zone, $addr, $ttl = NULL, $ns = NULL) { if (NULL === $ttl) $ttl = $GLOBALS['ttl']; if (NULL === $ns) $ns = $GLOBALS['ns']; if ($addr && !preg_match( '/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/', $addr)) failed("invalid rdata format: bad dotted quad"); $script = <<<EOF server {$ns} zone {$zone} update delete {$host}.${zone} EOF; if ($addr) $script.= <<<EOF update add {$host}.{$zone} {$ttl} IN A {$addr} EOF; $script.= <<<EOF send EOF; $script_file = tempnam("/tmp", "nsupdate."); file_put_contents($script_file, $script); $cmd = sprintf("nsupdate -k %s < %s 2>&1", $GLOBALS['nsupdate_key'], $script_file); exec($cmd, $res, $exit); unlink($script_file); if ($res === FALSE || $exit != 0) { failed(implode("\n", $res)); } updated($host, $addr); } function unauthorized() { $SIGNATURE = SIGNATURE; header('HTTP/1.0 401 Unauthorized'); echo <<<EOF <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> <html> <head> <title>401 Unauthorized</title> </head> <body> <h1>Unauthorized</h1> <p>I was not able to determine your authorization for the requested resource.</p> <hr> <address>{$SIGNATURE}</address> </body> </html> EOF; die(); } function updated($host, $addr) { $SIGNATURE = SIGNATURE; if ($addr) $response = "Host {$host} is now at address {$addr}"; else $response = "Host {$host} is now removed"; echo <<<EOF <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> <html> <head> <title>Updated</title> </head> <body> <h1>Updated</h1> <p>{$response}</p> <hr> <address>{$SIGNATURE}</address> </body> </html> EOF; die(); } function failed($syndrome) { $SIGNATURE = SIGNATURE; header("HTTP/1.0 500 Update Failure"); echo <<<EOF <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> <html> <head> <title>500 Update Failure</title> </head> <body> <h1>Update Failure</h1> <p>Operation failed during update.</p> <pre>{$syndrome}</pre> <hr> <address>{$SIGNATURE}</address> </body> </html> EOF; die(); } function form() { $SIGNATURE = SIGNATURE; echo <<<EOF <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> <html> <head> <title>Update Form</title> </head> <body> <h1>Update Form</h1> <form method="get"> <table> <tr> <td><label for="host">Hostname:</label></td> <td><input type="text" name="host" id="host" /></td> </tr> <tr> <td><label for="addr">Address:</label></td> <td><input type="text" name="addr" id="addr" /></td> </tr> <tr> <td><label for="key">Password:</label></td> <td><input type="password" name="key" id="key" /></td> </tr> <tr> <td> </td> <td> <input type="hidden" name="verbose" id="verbose" value="true" /> <input type="submit" value="Submit" /><input type="reset" value="Reset" /> </td> </tr> </table> </form> <hr> <address>{$SIGNATURE}</address> </body> </html> EOF; die(); } if (!isset($_GET['host'])) form(); $host = isset($_GET['host'])? $_GET['host']: ""; $addr = isset($_GET['addr'])? $_GET['addr']: ""; $key = isset($_GET['key'])? $_GET['key']: ""; // Remove zone if it was included if (substr($host, -strlen($zone), strlen($zone)) == $zone) $host = substr($host, 0, (strlen($host) - (strlen($zone) + 1))); // Validate the key $mykey = md5(sprintf("%s|%s|%s|%s|%s", $host, $addr, $now, $zone, $sec)); if ($key != $mykey && $key != $sec) unauthorized(); nsupdate($host, $zone, $addr); </code> * Client submission script And last, a client submission script, to send updates to the server whenever an IP change is detected. *Note that this does not assign the publicly routable IP to the hostname, but instead the supplied IP, because I'm using this inside a private network* <code brush="bash"> #!/usr/bin/env bash host="$(hostname)" key="" domain="dyn.cyberleo.net" dyndns_cache="/tmp/dyn-${domain}" service_url="http://%s/?host=%s&addr=%s&key=%s" now="$(date -u '+%Y%m%d%H')" sec="SecuritySecret" do_help() { echo "Updates IP with dyndns registry" echo "Flags:" echo " -v - Enable verbose operation" exit 64 } find_md5() { if which md5 >/dev/null 2>&1 then echo "$(which md5)" else if which md5sum >/dev/null 2>&1 then echo "$(which md5sum) | awk '{print \$1}'" else echo "md5/md5sum not available!" >&2 echo "cat > /dev/null" fi fi } find_wget() { [ -n "${quiet}" ] && flags="-q" if which wget >/dev/null 2>&1 then echo "wget ${flags} -O-" else if which fetch >/dev/null 2>&1 then echo "fetch ${flags} -o-" else if which lynx >/dev/null 2>&1 then echo "lynx -dump" else echo "No HTTP wrapper found!" >&2 echo "echo" fi fi fi } find_ipaddr() { # Try and find the default route ${bin_route} | egrep '^[0-9]+' | awk '{print "dest=" $1 " gateway=" $2 " mask=" $3 " iface=" $8 }' | while read line do eval ${line} if [ "${dest}" = "0.0.0.0" -a "${mask}" = "0.0.0.0" ] then # Hope this is my default route! ${bin_ifconfig} ${iface} | grep 'inet addr:' | head -n 1 | sed -e 's/^[ ]\+inet addr:\([0-9.]\+\).*$/\1/' break; fi done } while [ -n "${1}" ] do case "${1}" in -q) quiet="yes" ;; *) do_help ;; esac done bin_route="/bin/netstat -rn" bin_ifconfig="/sbin/ifconfig" bin_md5="$(find_md5)" bin_wget="$(find_wget)" bin_host="/usr/bin/host" host="${host%%.${domain}}" host="${host%%.cyberleo.net}" ipaddr="$(find_ipaddr)" dyndns_cache_modtime="$(stat -c '%Y' "${dyndns_cache}" 2>/dev/null)" dyndns_cache_modtime="${dyndns_cache_modtime:-0}" dyndns_cache_age="$(( $(date '+%s') - ${dyndns_cache_modtime} ))" oldip="$(cat "${dyndns_cache}" 2>/dev/null)" if [ "${dyndns_cache_age}" -le 86400 -a "${oldip}" = "${ipaddr}" ] then # Cachefile is less than a day old, and the IP hasn't changed exit 0 fi [ -z "${key}" ] && key="$(echo -n "${host}|${ipaddr}|${now}|${domain}|${sec}" | eval ${bin_md5})" service_url="$(printf "${service_url}" "${domain}" "${host}" "${ipaddr}" "${key}")" ${bin_wget} "${service_url}" # Check DNS to make sure we're where we need to be. newip="$(${bin_host} ${host}.${domain} | sed -e 's/^.* \([0-9.]\+\)$/\1/g')" if [ "${newip}" = "${ipaddr}" ] then # Update cache, so that we don't run this whole thing again until something changes. echo "${ipaddr}" > "${dyndns_cache}" fi </code>