<?php
require_once('.ddns_config.php');

/**
 * @author Gernot WALZL
 */
class Authenticator {

    protected $config = null;

    function __construct($config) {
        $this->config = $config;
    }

    function authenticate() {
        header('WWW-Authenticate: Basic');
        header('HTTP/1.0 401 Unauthorized');
        return false;  // if user hits cancel
    }

    function verify($username, $password) {
        $result = false;
        if (isset($this->config->pass_hashes[$username])) {
            $pass_hash = $this->config->pass_hashes[$username];
            $result = password_verify($password, $pass_hash);
        }
        return $result;
    }

}

/**
 * @author Gernot WALZL
 */
class NSUpdater {

    protected $config = null;

    function __construct($config) {
        $this->config = $config;
    }

    function update_record($record) {
        $handle = popen('/usr/bin/nsupdate', 'w');
        if ($handle) {
            fwrite($handle, 'server '.$this->config->server."\n");
            fwrite($handle, 'zone '.$this->config->zone."\n");
            fwrite($handle, 'key rndc-key '.$this->config->key."\n");
            fwrite($handle, 'update delete '.$record['host'].' '.
                $record['type']."\n");
            fwrite($handle, 'update add '.$record['host'].' '.
                $record['ttl'].' '.$record['type'].' '.$record['addr']."\n");
            fwrite($handle, 'send');
            pclose($handle);
        }
    }

}

/**
 * @author Gernot WALZL
 */
class DDNSHTML {

    function println($msg) {
        print($msg."\n");
    }

    function print_head($title='DDNS') {
        $this->println('<!DOCTYPE html>');
        $this->println('<html lang="en">');
        $this->println('<head>');
        $this->println('<meta charset="utf-8" />');
        $this->println('<title>'.$title.'</title>');
        $this->println('</head>');
        $this->println('<body>');
    }

    function print_foot() {
        $this->println('</body>');
        $this->println('</html>');
    }

    function print_h1() {
        $this->println('<h1>Dynamic DNS</h1>');
    }

    function print_navigation() {
        $this->println('<ul>');
        $this->println('<li><a href="'.$_SERVER['PHP_SELF'].'?checkip">'.
            'Check current IP</a></li>');
        $this->println('<li><a href="'.$_SERVER['PHP_SELF'].'?update">'.
            'Update DNS</a></li>');
        $this->println('<li><a href="'.$_SERVER['PHP_SELF'].'?client">'.
            'Download Client</a></li>');
        $this->println('</ul>');
    }

    function print_unauthorized() {
        $this->println('<h1>Unauthorized</h1>');
        $this->println('<p>This server could not verify that you are '.
            'authorized to access the document requested.</p>');
    }

    function print_form_update($zone, $hostnames) {
        $this->println('<form method="post" '.
            'action="'.$_SERVER['PHP_SELF'].'?update">');
        $this->println('<fieldset>');
        $this->println('<legend>Update</legend>');
        $this->println('<table>');
        $this->println('<tr>');
        $this->println('<td><label for="zone">Zone:</label></td>');
        $this->println('<td><input type="text" id="zone" name="zone" '.
            'value="'.$zone.'" disabled /></td>');
        $this->println('</tr>');
        $this->println('<tr>');
        $this->println('<td><label for="hostname">Hostname:</label></td>');
        $this->println('<td><select id="hostname" name="hostname">');
        foreach ($hostnames as $hostname) {
            $this->println('<option value="'.$hostname.'">'.$hostname.'</option>');
        }
        $this->println('</select></td>');
        $this->println('</tr>');
        $this->println('<tr>');
        $this->println('<td><label for="addr">Address:</label></td>');
        $this->println('<td><input type="text" id="addr" name="addr" '.
            'value="'.$_SERVER['REMOTE_ADDR'].'" /></td>');
        $this->println('</tr>');
        $this->println('</table>');
        $this->println('<input type="submit" value="Submit" />');
        $this->println('</fieldset>');
        $this->println('</form>');
    }

    function print_record($record) {
        $this->println('<pre>');
        $this->println($record['host'].'.  '.
            $record['ttl'].'  '.
            $record['class'].'  '.
            $record['type'].'  '.
            $record['addr']);
        $this->println('</pre>');
    }

}


function client () {
    header('Content-Type: application/x-sh');
    header('Content-Disposition: attachment; filename="ddnsc.sh"');
?>
#!/bin/sh
# ddnsc.sh
# 2020-05-31
# by Gernot Walzl

URL="https://gernot-walzl.at/ddns.php"
USER="username"
PASS="password"
HOST="$(hostname)"
if [ -r /etc/ddnsc.conf ]; then
  . /etc/ddnsc.conf
fi

print_usage () {
  echo "Usage: $0 {checkip, update [addr], daemon [interval]}"
}

checkip () {
  wget -4 -q -O - "${URL}?checkip"
}

update () {
  local POST_DATA="hostname=$HOST"
  if [ ! -z "$1" ]; then
    POST_DATA="${POST_DATA}&addr=$1"
  fi
  wget -4 -q -O - \
    --http-user="$USER" --http-password="$PASS" \
    --post-data "$POST_DATA" "${URL}?update"
}

daemon () {
  local INTERVAL="$1"
  if [ -z "$INTERVAL" ]; then
    INTERVAL=600
  fi
  local CURRENT_ADDR;
  local UPDATED_ADDR;
  while :; do
    CURRENT_ADDR="$(checkip)"
    if [ "$CURRENT_ADDR" != "$UPDATED_ADDR" ]; then
      if update "$CURRENT_ADDR" > /dev/null ; then
        UPDATED_ADDR="$CURRENT_ADDR"
      fi
    fi
    sleep "$INTERVAL";
  done
}

case "$1" in
'checkip')
  checkip
  ;;
'update')
  update "$2"
  ;;
'daemon')
  daemon "$2" &
  ;;
*)
  print_usage
esac
<?php
}


if (isset($_GET['checkip'])) {
    print($_SERVER['REMOTE_ADDR']."\n");
} else if (isset($_GET['update'])) {
    $html = new DDNSHTML();
    $config = new DDNSConfig();
    $auth = new Authenticator($config);
    $user = '';
    if (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) {
        if ($auth->verify($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'])) {
            $user = $_SERVER['PHP_AUTH_USER'];
        }
    }
    if (empty($user)) {
        if (!$auth->authenticate()) {
            $html->print_head('401 Unauthorized');
            $html->print_unauthorized();
            $html->print_foot();
        }
    } else {
        $hostname = '';
        if (isset($_GET['hostname'])) {
            $hostname = $_GET['hostname'];
        } else if (isset($_POST['hostname'])) {
            $hostname = $_POST['hostname'];
        }
        $html->print_head();
        if (empty($hostname)) {
            $html->print_h1();
            $html->print_form_update($config->zone, $config->hostnames[$user]);
        } else {
            $arr_hostname = explode('.', $hostname, 2);
            $hostname = $arr_hostname[0];
            if (!empty($hostname) &&
                    in_array($hostname, $config->hostnames[$user])) {
                $addr = $_SERVER['REMOTE_ADDR'];
                if (isset($_GET['addr'])) {
                    $addr = $_GET['addr'];
                } else if (isset($_POST['addr'])) {
                    $addr = $_POST['addr'];
                }
                $in_addr = inet_pton($addr);
                if ($in_addr) {
                    $record = array();
                    $record['host'] = $hostname.'.'.$config->zone;
                    $record['ttl'] = $config->ttl;
                    $record['class'] = 'IN';
                    $record['type'] = 'A';
                    if (strlen($in_addr) == 16) {
                        $record['type'] = 'AAAA';
                    }
                    $record['addr'] = $addr;
                    $updater = new NSUpdater($config);
                    $updater->update_record($record);
                    $html->print_record($record);
                } else {
                    $html->println('<p>ERROR: Given address is not valid.</p>');
                }
            } else {
                $html->println('<p>ERROR: Hostname not accepted.</p>');
            }
        }
        $html->print_foot();
    }
} else if (isset($_GET['client'])) {
    client();
} else {
    $html = new DDNSHTML();
    $html->print_head();
    $html->print_h1();
    $html->print_navigation();
    $html->print_foot();
}

?>