Skip to content

Instantly share code, notes, and snippets.

@beat
Forked from fredsted/ddns-api.php
Last active Jan 21, 2022
Embed
What would you like to do?
Virtualmin Dynamic DNS (DDNS) server
<?php
// This script receives DDNS updates via HTTP and stores them into a JSON file for another script.
// example: http://server/ddns-api.php?code=DDNS_CODE&hostname=home = updates home.domain.com to IP of requester
// example: http://server/ddns-api.php?code=DDNS_CODE&hostname=home&myip=1.2.3.4 = updates home.domain.com to IP 1.2.3.4
// example: http://server/ddns-api.php?code=DDNS_CODE&hostname=home&myip6=1:2:3::4 = updates home.domain.com to IPv6
// example: http://server/ddns-api.php?code=DDNS_CODE&hostname=home&myip=1.2.3.4&myip6=1:2:3::4 = updates home.domain.com to IPv4 and IPv6 given
// example: http://server/ddns-api.php?code=DDNS_CODE&hostname=home&action=delete = deletes home.domain.com from DNS
define('DDNS_DATA_FILE', '/home/ddns/ddns.json');
define('DDNS_DOMAIN', 'example.com');
define('DDNS_CODE', 'MY-SECRET-API-CODE');
header("Content-type: text/plain");
class ddns {
public $name;
public $ip;
public $ip6;
public $ipreal;
public $lastupdate;
}
function print_data($data) {
foreach ($data as $dat) {
echo "{$dat->name}.".DDNS_DOMAIN.":\t\t{$dat->ip}\n";
}
}
function clean_ip($data) {
return preg_replace('/[^.0-9]/', '', $data);
}
function clean_ip6($data) {
return preg_replace('/[^:0-9]/', '', $data);
}
function clean_ip46($data) {
return preg_replace('/[^.:0-9]/', '', $data);
}
function clean_name($data) {
return preg_replace('/[^-a-z0-9]/', '', $data);
}
if (!isset($_GET['code']) || $_GET['code'] !== DDNS_CODE) {
echo 'no access';
die();
}
$data = json_decode(file_get_contents(DDNS_DATA_FILE));
if (!$data) {
$data= new stdClass();
}
if (!isset($_GET['hostname'])) {
echo "Not enough parameters. Available params:\n";
echo " &name name of subdomain\n";
echo " &ip the ip - can be left blank\n";
echo " &action the action of the command. default is update, can also be delete.\n";
echo "\n";
// print_data($data);
die();
}
if (isset($_GET['myip'])) {
$ip = clean_ip($_GET['myip']);
} else {
if (isset($_GET['myip6'])) {
$ip = '';
} else {
$ip = clean_ip($_SERVER['REMOTE_ADDR']);
}
}
if (isset($_GET['myip6'])) {
$ip6 = clean_ip6($_GET['myip6']);
} else {
$ip6 = '';
}
$ipreal = clean_ip46($_SERVER['REMOTE_ADDR']);
if (!isset($_GET['action']) || ($_GET['action'] == 'update')) {
$object = new ddns();
$object->name = clean_name($_GET['hostname']);
$object->ip = $ip;
$object->ip6 = $ip6;
$object->ipreal = $ipreal;
$object->lastupdate = date("Y-m-d H:i:s");
$data->{$object->name} = $object;
} elseif ($_GET['action'] == 'delete') {
unset($data->{clean_name($_GET['hostname'])});
}
file_put_contents(DDNS_DATA_FILE, json_encode($data));
// to debug only! print_data($data);
#!/usr/bin/php
<?php
// This script should run as cronjob under root and parses the DDNS data
// It sends mails on changes and if no updates are received since more than ALLOWED_TIME_BETWEEN_UPDATES seconds.
// Create empty ddns.json, ddns-old.json and logs/ddns.log files and give it the right ownership/permissions so
// the server process can write to it. In Virtualmin, thta would be the virtual server owner's user and group,
// with write permission for the owner and group, and no access for others. In standalone systems it may be www-data.
const DDNS_DATA_FILE = '/home/ddns/ddns.json';
const LOG_FILE = '/home/ddns/logs/ddns.log';
const OLD_DDNS_DATA_FILE = '/home/ddns/ddns-old.json';
const DDNS_DOMAIN = 'example.com';
const DOMAIN_POSTFIX = '.dyn';
const MAIL_CHANGES_TO = 'example@example.com';
const TTL_TIME = 180;
const ALLOWED_TIME_BETWEEN_UPDATES = 3600*24*3;
const EXEC_IT = true;
function clean_ip46($data) {
return preg_replace('/[^.:0-9]/', '', $data);
}
function is_empty_ip6($data) {
return ( '' === preg_replace('/[:0]/', '', $data) );
}
function clean_name($data) {
return preg_replace('/[^-a-z0-9]/', '', $data);
}
$dns_entries_raw = file_get_contents(DDNS_DATA_FILE);
$old_entries_raw = file_get_contents(OLD_DDNS_DATA_FILE);
if ( $dns_entries_raw !== $old_entries_raw ) {
file_put_contents( OLD_DDNS_DATA_FILE, $dns_entries_raw );
}
$dns_entries = json_decode($dns_entries_raw);
$old_entries = json_decode($old_entries_raw);
$domain = DDNS_DOMAIN;
$virtualmin_bin = "/usr/sbin/virtualmin";
$modify_dns_cmd = "modify-dns --domain $domain";
$command_remove = "$virtualmin_bin $modify_dns_cmd --remove-record";
$command_add = "$virtualmin_bin $modify_dns_cmd --add-record-with-ttl";
$output = '';
$changes_subject = 'IP address changes on';
$changes_body = '';
foreach ($dns_entries as $index => $entry) {
if ( isset( $old_entries->$index ) ) {
$old_entry = $old_entries->$index;
if ( ( $old_entry->name === $entry->name )
&& ( $old_entry->ip === $entry->ip )
&& ( $old_entry->ip6 === $entry->ip6 ) )
{
$time_stamp = strtotime($entry->lastupdate);
if ( $time_stamp < time()-ALLOWED_TIME_BETWEEN_UPDATES ) {
$changes_subject .= ' UPDATE ISSUE FOR ' . $entry->name . DOMAIN_POSTFIX . '.' . DDNS_DOMAIN;
$changes_body .= $entry->name . DOMAIN_POSTFIX . '.' . DDNS_DOMAIN
. ' has updates issue. Last updated on ' . $entry->lastupdate . ' to IPv4 ' . $entry->ip . ' / IPv6 ' . $entry->ip6 . ' (from real IP ' . $entry->ipreal . ')' . "\n";
}
continue;
}
} else {
$old_entry = new stdClass();
$old_entry->ip = '';
$old_entry->ip6 = '';
}
$clean_full_domain = clean_name( $entry->name ) . DOMAIN_POSTFIX;
$clean_ip = clean_ip46( $entry->ip );
$clean_ip6 = clean_ip46( $entry->ip6 );
if ( ( strpos( $clean_ip, '.' ) !== false ) && ( $old_entry->ip !== $entry->ip ) ) {
// Remove old DNS thing.
$command = $command_remove . ' "' . $clean_full_domain . ' A"';
$output .= "-> " . $command . "\n";
if (EXEC_IT) {
$output .= shell_exec($command) . "\n";
}
// Add new DNS thing.
$command = $command_add . ' "' . $clean_full_domain . ' A ' . TTL_TIME . ' ' . $clean_ip . '"';
$output .= "-> " . $command . "\n";
if (EXEC_IT) {
$output .= shell_exec($command) . "\n";
}
$changes_subject .= ' ' . $clean_full_domain . '.' . DDNS_DOMAIN;
$changes_body .= $clean_full_domain . '.' . DDNS_DOMAIN
. ' changed from IP ' . $old_entry->ip . ' to ' . $entry->ip . ' (from real IP ' . $entry->ipreal . ')' . "\n";
}
if ( ( strpos( $clean_ip6, ':' ) !== false ) && ! is_empty_ip6( $clean_ip6 ) && ( $old_entry->ip6 !== $entry->ip6 ) ) {
// Remove old DNS thing.
$command = $command_remove . ' "' . $clean_full_domain . ' AAAA"';
$output .= "-> " . $command . "\n";
if (EXEC_IT) {
$output .= shell_exec($command) . "\n";
}
// Add new DNS thing.
$command = $command_add . ' "' . $clean_full_domain . ' AAAA ' . TTL_TIME . ' ' . $clean_ip6 . '"';
$output .= "-> " . $command . "\n";
if (EXEC_IT) {
$output .= shell_exec($command) . "\n";
}
$changes_subject .= ' ' . $clean_full_domain . '.' . DDNS_DOMAIN;
$changes_body .= $clean_full_domain . '.' . DDNS_DOMAIN
. ' changed from IPv6 ' . $old_entry->ip6 . ' to ' . $entry->ip6 . ' (from real IP ' . $entry->ipreal . ')' . "\n";
} elseif ( ( $clean_ip6 !== '' ) && ! is_empty_ip6( $clean_ip6 )) {
$output .= 'ERROR IN IP ADDRESS FORMAT: ' . $entry->ip . ' FOR DOMAIN ' . $entry->name . "\n";
}
}
$last_changed_time = filectime(DDNS_DATA_FILE);
if ($last_changed_time && $last_changed_time < time() - ALLOWED_TIME_BETWEEN_UPDATES) {
$changes_subject .= ' DDNS FILE UPDATE ISSUE';
$changes_body .= DDNS_DATA_FILE . ' was last updated on ' . date('l jS \of F Y h:i:s A') . "\n\n";
}
if ($output!=='') {
$output = "\n\n\n---------------------\n"
. date('l jS \of F Y h:i:s A') . "\n\n"
. $output;
file_put_contents(LOG_FILE, $output, FILE_APPEND);
}
if ( $changes_body !== '' ) {
mail( MAIL_CHANGES_TO, $changes_subject, $changes_body);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment