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:
2019-08-02 14:36:40 +02:00
parent de0f57806c
commit 99ed087a8d

View File

@@ -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};
}