Added config file support + various smaler changes
new functionality: - ability to load a config file (no more hard-coding settings) that can be optional, required or ignored - added RecordTTL setting (used to be hard-coded to 1h) Fixes: - ExpireAfter now also accepts just a number of seconds (without unit s) Other changes: - AllowDebugKey now defaults to 'off' - UpdateTXT and DeleteTXT no longer need a trailing whitespace - sanitized perodSeconds so that it only does a single multiplication
This commit is contained in:
91
dyndns.pl
91
dyndns.pl
@@ -26,12 +26,13 @@ use POSIX;
|
||||
use Net::DNS;
|
||||
use feature 'state';
|
||||
|
||||
my $ConfigFile = 'optional'; # hardcoded, either optional, required or ignore
|
||||
|
||||
##############################
|
||||
# Configuration section
|
||||
# Configuration section (defaults, can be set in config file)
|
||||
my $DNSServer = '192.168.1.1'; # DNS Server to communicate with (use IP!)
|
||||
my $ExpandCNAMEs = 1; # CNAME levels to expand (0 to disable)
|
||||
my $AllowDebugKey = 'on'; # Debuging, 'off' to disable, '' for always on
|
||||
my $AllowDebugKey = 'off'; # Debuging, 'off' to disable, '' for always on
|
||||
# and other values to enable with debug= param.
|
||||
my $AuthMode = 'remote'; # either 'static', 'remote' or 'both'
|
||||
my $StaticSigner = ''; # required for AuthMode 'static' or 'both'
|
||||
@@ -40,8 +41,9 @@ my $RequireRR = ''; # Require existing record of this type for upd.
|
||||
my $ExpireAfter = '1w'; # Expire time for registrations in minutes,
|
||||
# hours, weeks or seconds. format: [0-9]+[mhws]?
|
||||
my @ReplaceRR = ('A', 'AAAA', 'TXT'); # Records types to replace in update
|
||||
my $UpdateTXT = 'Last DynDNS update on ';
|
||||
my $DeleteTXT = 'DynDNS cleared on ';
|
||||
my $RecordTTL = '1h'; # TTL for created records, format: [0-9]+[mhws]?
|
||||
my $UpdateTXT = 'Last DynDNS update on'; # if set add TXT with this+date on update
|
||||
my $DeleteTXT = 'DynDNS cleared on'; # if set add TXT with this+date on delete
|
||||
|
||||
|
||||
##############################
|
||||
@@ -60,16 +62,10 @@ sub is_ipv6 { return $_[0]=~/^([0-9a-fA-F]{0,4}(:|$)){3,8}/i; }
|
||||
sub periodSeconds($) {
|
||||
my ($number, $units) = ($_[0]=~/^(\d+)([smhdw])?$/);
|
||||
if($number && $units && $units cmp 's') {
|
||||
$number *= 60; # Convert to minutes
|
||||
if($units cmp 'm') {
|
||||
$number *= 60; # Convert to hours
|
||||
if($units cmp 'h') {
|
||||
$number *= 24; # Convert to days
|
||||
if($units cmp 'd') {
|
||||
$number *= 7; # Convert to weeks
|
||||
}
|
||||
}
|
||||
}
|
||||
$number *= ($units eq 'm') ? 60 # Seconds per minute
|
||||
: ($units cmp 'h') ? 3600 # Seconds per hour
|
||||
: ($units cmp 'd') ? 86400 # Seconds per day
|
||||
: 604800; # Seconds per week
|
||||
}
|
||||
return $number;
|
||||
}
|
||||
@@ -163,6 +159,7 @@ sub get_authinfo($$) {
|
||||
sub DNS_Update($$$$$$$) {
|
||||
my ($dnsdomain, $dnshost, $ipv4, $ipv6, $signer, $key, $debug) = @_;
|
||||
my $dnsupdate = Net::DNS::Update->new($dnsdomain);
|
||||
my $ttl = periodSeconds($RecordTTL);
|
||||
|
||||
# If $RequireRR is set, ensure an records of specified type exist for the name
|
||||
$dnsupdate->push(pre => yxrrset("$dnshost. $RequireRR")) if($RequireRR);
|
||||
@@ -173,13 +170,14 @@ sub DNS_Update($$$$$$$) {
|
||||
}
|
||||
|
||||
# Add new A and AAAA record based on whether ipv4 and ipv6 address provided
|
||||
$dnsupdate->push(update=>rr_add("$dnshost. 3600 A $ipv4")) if($ipv4);
|
||||
$dnsupdate->push(update=>rr_add("$dnshost. 3600 AAAA $ipv6")) if($ipv6);
|
||||
$dnsupdate->push(update=>rr_add("$dnshost. $ttl A $ipv4")) if($ipv4);
|
||||
$dnsupdate->push(update=>rr_add("$dnshost. $ttl AAAA $ipv6")) if($ipv6);
|
||||
|
||||
# Always add a new TXT record with the timestamp of the last update
|
||||
my $txt = ($ipv4 or $ipv6) ? $UpdateTXT : $DeleteTXT;
|
||||
$dnsupdate->push(update=>rr_add($dnshost. '. 3600 TXT "' . $txt . localtime()
|
||||
. '"')) if($txt);
|
||||
# Add a TXT record with the timestamp of the last update, if required
|
||||
if (my $txt = ($ipv4 or $ipv6) ? $UpdateTXT : $DeleteTXT) {
|
||||
my $timestamp = localtime();
|
||||
$dnsupdate->push(update=>rr_add("$dnshost. $ttl TXT \"$txt $timestamp\""));
|
||||
}
|
||||
|
||||
# Sign the request with the signer and key
|
||||
$dnsupdate->sign_tsig($signer, $key);
|
||||
@@ -326,24 +324,59 @@ sub handle_list($$$$$$) {
|
||||
|
||||
my $cgi = CGI->new;
|
||||
|
||||
##############################
|
||||
# Load configuration, if desired
|
||||
if ($ConfigFile cmp 'ignore') {
|
||||
my $CFGFile = $0;
|
||||
$CFGFile =~ s/(\.pl)?$/.cfg/;
|
||||
if (open (CONFIG, $CFGFile)) {
|
||||
my %CONFIG = (
|
||||
allow_debug_key => \$AllowDebugKey, dns_server => \$DNSServer,
|
||||
expand_cnames => \$ExpandCNAMEs, auth_mode => \$AuthMode,
|
||||
static_signer => \$StaticSigner, static_key => \$StaticKey,
|
||||
require_rr => \$RequireRR, replace_rr => \@ReplaceRR,
|
||||
update_txt => \$UpdateTXT, delete_txt => \$DeleteTXT,
|
||||
expire_after => \$ExpireAfter, record_ttl => \$RecordTTL,
|
||||
);
|
||||
|
||||
while (<CONFIG>) {
|
||||
chomp; s/^\s+//; s/\s*(#.*)?$//; # trim whitespace & comments
|
||||
next unless length; # skip empty lines
|
||||
my ($key, $value) = split(/\s*=\s*/, $_, 2); # split key and value
|
||||
my $dst = $CONFIG{$key}; # get destination for value
|
||||
if (ref $dst eq 'SCALAR') { $$dst = $value; } # store scalar value
|
||||
elsif (ref $dst eq 'CODE') { &$dst($value); } # call setter function
|
||||
elsif (ref $dst cmp 'ARRAY') { die "Invalid $key in $CFGFile\n"; }
|
||||
else { @$dst = split(/\s*,\s*/, $value); } # split and store array
|
||||
}
|
||||
close CONFIG;
|
||||
} elsif ($ConfigFile eq 'required') {
|
||||
die "unable to load configuration from $CFGFile: $!, aborting\n"
|
||||
}
|
||||
}
|
||||
|
||||
##############################
|
||||
# Validate Configuration
|
||||
my $CE = 'Configuration Error:';
|
||||
die "$CE \$AuthMode '$AuthMode' is unsupported must be remote, static or both\n"
|
||||
die "$CE ConfigFile must be optional, required or ignore, not '$ConfigFile'\n"
|
||||
unless $ConfigFile=~/optional|required|ignore/;
|
||||
die "$CE AuthMode '$AuthMode' is unsupported must be remote, static or both\n"
|
||||
unless $AuthMode=~/remote|static|both/;
|
||||
die "$CE \$StaticSigner must be set for \$AuthMode '$AuthMode'\n"
|
||||
die "$CE StaticSigner must be set for \$AuthMode '$AuthMode'\n"
|
||||
unless ($StaticSigner or $AuthMode eq 'remote');
|
||||
die "$CE \$StaticKey must be set for \$AuthMode '$AuthMode'\n"
|
||||
die "$CE StaticKey must be set for \$AuthMode '$AuthMode'\n"
|
||||
unless ($StaticKey or $AuthMode eq 'remote');
|
||||
die "$CE \$RequireRR is set to unsupported type '$RequireRR'\n"
|
||||
die "$CE RequireRR is set to unsupported type '$RequireRR'\n"
|
||||
if ($RequireRR and not $DNS_label{$RequireRR});
|
||||
die "$CE \$ExpireAfter '$ExpireAfter' is not supported\n"
|
||||
unless ($ExpireAfter=~/^\d+[smhw]$/);
|
||||
die "$CE \$UpdateTXT must be set when \$ExpireAfter is set\n"
|
||||
die "$CE RecordTTL '$RecordTTL' is not supported\n"
|
||||
unless ($RecordTTL=~/^\d+[smhw]?$/);
|
||||
die "$CE ExpireAfter '$ExpireAfter' is not supported\n"
|
||||
unless ($ExpireAfter=~/^\d+[smhw]?$/);
|
||||
die "$CE UpdateTXT must be set when \$ExpireAfter is set\n"
|
||||
if($ExpireAfter and not $UpdateTXT);
|
||||
foreach my $rrtype (@ReplaceRR) {
|
||||
die "$CE \$ReplaceRR contains unsupported type '$rrtype'\n"
|
||||
unless ($DNS_label{$rrtype});
|
||||
die "$CE ReplaceRR contains unsupported type '$rrtype'\n"
|
||||
unless exists $DNS_label{$rrtype};
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user