Inhaltsverzeichnis

HOW-TO: be your own DDNS provider

Summary

This how-to explains a way to build your own dynamic DNS server.

Main

Hi, since some time my DDNS provider has problems which cause the loss of the connection to my home server.
To prevent this loss I've read some manpages and build my own DDNS server.

:!: This is not a how-to about DNS, bind or any other software I've used. :!:

Warranty

There is no warranty that this how-to works for any system.
I'm just providing these information because it worked for me this way.
If you have questions you can leave a message here but I decide whether I'll answer and help or not.

Requirements:

Configurations

You have to change all 'dyndns.example.org' to your domain.

bind

  1. the bind user requires write-access to bind working directory:
    // named.conf
    options {
      ...
      ; working directory of bind
      directory "/var/named";
      ...
    };
    chmod 770 /var/named/
  2. we generate a TSIG-key in a new directory which is used to verify the server and client:
    mkdir -p /etc/named/
    cd /etc/named/
    dnssec-keygen -a hmac-sha512 -b 512 -n HOST dyndns.example.org
     
    # webserver-group needs read access to file containing TSIG-key
    chown root:<webserver-group> /etc/named/Kdyndns.example.org*.private
    chmod 640 /etc/named/Kdyndns.example.org*.private
     
    # get and remember the key
    grep Key /etc/named/Kdyndns.example.org*.private
  3. create zone in named.conf
    key mykey {
      algorithm hmac-sha512;
      secret "the-generated-key";
    };
     
    zone "dyndns.example.org" IN {
      type master;
      file "dyndns.example.org.zone";
      allow-query { any; };
      allow-transfer { none; };
      allow-update { key mykey; };
    };
  4. create zone-file
    /var/named/dyndns.example.org.zone
    $ORIGIN .
    $TTL 86400  ; 1 day
    dyndns.example.org    IN SOA  localhost. root.localhost. (
            52         ; serial
            3600       ; refresh (1 hour)
            900        ; retry (15 minutes)
            604800     ; expire (1 week)
            86400      ; minimum (1 day)
            )
          NS  localhost.
    $ORIGIN dyndns.example.org.

Webserver

Create a subdomain (dyndns.example.org) and a vhost for the updating script.
For security purpose and compatibility of the php-script the vhost has to be protected by http-authentication.
For Lighttpd you can use the script provided here to generate the users.
Save this PHP-script in the vhost-directory:

index.php
<?php
  // configuration of user and domain
  $user_domain = array( 'user' => array('subdomain','sub2'), 'user2' => array('sub4') );
  // main domain for dynamic DNS
  $dyndns = "dyndns.example.org";
  // DNS server to send update to
  $dnsserver = "localhost";
  // port of DNS server
  $dnsport = "";
 
  // short sanity check for given IP
  function checkip($ip)
  {
    $iptupel = explode(".", $ip);
    foreach ($iptupel as $value)
    {
      if ($value < 0 || $value > 255)
        return false;
      }
    return true;
  }
 
  // retrieve IP
  $ip = $_SERVER['REMOTE_ADDR'];
  // retrieve user
  if ( isset($_SERVER['REMOTE_USER']) )
  {
    $user = $_SERVER['REMOTE_USER'];
  }
  else if ( isset($_SERVER['PHP_AUTH_USER']) )
  {
    $user = $_SERVER['PHP_AUTH_USER'];
  }
  else
  {
    syslog(LOG_WARN, "No user given by connection from $ip");
    exit(0);
  }
 
  // open log session
  openlog("DDNS-Provider", LOG_PID | LOG_PERROR, LOG_LOCAL0);
 
  // check for given domain
  if ( isset($_POST['DOMAIN']) )
  {
    $subdomain = $_POST['DOMAIN'];
  }
  else if ( isset($_GET['DOMAIN']) )
  {
    $subdomain = $_GET['DOMAIN'];
  }
  else
  {
    syslog(LOG_WARN, "User $user from $ip didn't provide any domain");
    exit(0);
  }
 
  // check for needed variables
  if ( isset($subdomain) && isset($ip) && isset($user) )
  {
    // short sanity check for given IP
    if ( preg_match("/^(\d{1,3}\.){3}\d{1,3}$/", $ip) && checkip($ip) && $ip != "0.0.0.0" && $ip != "255.255.255.255" )
    {
      // short sanity check for given domain
      if ( preg_match("/^[\w\d-_\*\.]+$/", $subdomain) )
      {
        // check whether user is allowed to change domain
        if ( in_array("*", $user_domain[$user]) or in_array($subdomain, $user_domain[$user]) )
        {
          if ( $subdomain != "-" )
            $subdomain = $subdomain . '.';
          else
            $subdomain = '';
 
          // shell escape all values
          $subdomain = escapeshellcmd($subdomain);
          $user = escapeshellcmd($user);
          $ip = escapeshellcmd($ip);
 
          // prepare command
          $data = "<<EOF
server $dnsserver $dnsport
zone $dyndns
update delete $subdomain$user.$dyndns A
update add $subdomain$user.$dyndns 300 A $ip
send
EOF";
          // run DNS update
          exec("/usr/bin/nsupdate -k /etc/named/K$dyndns*.private $data", $cmdout, $ret);
          // check whether DNS update was successful
          if ($ret != 0)
          {
            syslog(LOG_INFO, "Changing DNS for $subdomain$user.$dyndns to $ip failed with code $ret");
          }
        }
        else
        {
          syslog(LOG_INFO, "Domain $subdomain is not allowed for $user from $ip");
        }
      }
      else
      {
        syslog(LOG_INFO, "Domain $subdomain for $user from $ip with $subdomain was wrong");
      }
    }
    else
    {
      syslog(LOG_INFO, "IP $ip for $user from $ip with $subdomain was wrong");
    }
  }
  else
  {
    syslog(LOG_INFO, "DDNS change for $user from $ip with $subdomain failed because of missing values");
  }
  // close log session
  closelog();
?>

Usage

If you've configured all correctly you can update domains using this command:

wget --no-check-certificate --http-user="user" --http-passwd="password" --post-data "DOMAIN=example" -q https://dyndns.example.com

Some examples:

Script configuration:

$user_domain = array( 'user' => array('subdomain') );
$dyndns = "dyndns.example.org"

Result:
The user 'user' can update the IP for the domain subdomain.user.dyndns.example.org.

Script configuration:

$user_domain = array( 'user' => array('subdomain'), 'user2' => array('test', 'foobar') );
$dyndns = "dyndns.example.org"

Result:
The user 'user' can update the IP for the domain subdomain.user.dyndns.example.org.
The user 'user2' can update the IP for the domains test.user2.dyndns.example.org and foobar.user2.dyndns.example.org.

Script configuration:

$user_domain = array( 'user' => array('*'), 'user2' => array('test', 'foobar') );
$dyndns = "dyndns.example.org"

Result:
The user 'user' can update the IP for the wildcard domain *.user.dyndns.example.org which means all subdomains of user.dyndns.example.org are resolved to the IP set for *.
The user 'user2' can update the IP for the domains test.user2.dyndns.example.org and foobar.user2.dyndns.example.org.

Script configuration:

$user_domain = array( 'user' => array('-','subdomain'), 'user2' => array('test', 'foobar') );
$dyndns = "dyndns.example.org"

Result:
The user 'user' can update the IP for the domains subdomain.user.dyndns.example.org and user.dyndns.example.org.
The user 'user2' can update the IP for the domains test.user2.dyndns.example.org and foobar.user2.dyndns.example.org.

Sources

http://www.bind9.net/manuals
http://www.oceanwave.com/technical-resources/unix-admin/nsupdate.html