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 Net::DNS;
use feature 'state'; 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 $DNSServer = '192.168.1.1'; # DNS Server to communicate with (use IP!)
my $ExpandCNAMEs = 1; # CNAME levels to expand (0 to disable) 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. # and other values to enable with debug= param.
my $AuthMode = 'remote'; # either 'static', 'remote' or 'both' my $AuthMode = 'remote'; # either 'static', 'remote' or 'both'
my $StaticSigner = ''; # required for AuthMode 'static' 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, my $ExpireAfter = '1w'; # Expire time for registrations in minutes,
# hours, weeks or seconds. format: [0-9]+[mhws]? # hours, weeks or seconds. format: [0-9]+[mhws]?
my @ReplaceRR = ('A', 'AAAA', 'TXT'); # Records types to replace in update my @ReplaceRR = ('A', 'AAAA', 'TXT'); # Records types to replace in update
my $UpdateTXT = 'Last DynDNS update on '; my $RecordTTL = '1h'; # TTL for created records, format: [0-9]+[mhws]?
my $DeleteTXT = 'DynDNS cleared on '; 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($) { sub periodSeconds($) {
my ($number, $units) = ($_[0]=~/^(\d+)([smhdw])?$/); my ($number, $units) = ($_[0]=~/^(\d+)([smhdw])?$/);
if($number && $units && $units cmp 's') { if($number && $units && $units cmp 's') {
$number *= 60; # Convert to minutes $number *= ($units eq 'm') ? 60 # Seconds per minute
if($units cmp 'm') { : ($units cmp 'h') ? 3600 # Seconds per hour
$number *= 60; # Convert to hours : ($units cmp 'd') ? 86400 # Seconds per day
if($units cmp 'h') { : 604800; # Seconds per week
$number *= 24; # Convert to days
if($units cmp 'd') {
$number *= 7; # Convert to weeks
}
}
}
} }
return $number; return $number;
} }
@@ -163,6 +159,7 @@ sub get_authinfo($$) {
sub DNS_Update($$$$$$$) { sub DNS_Update($$$$$$$) {
my ($dnsdomain, $dnshost, $ipv4, $ipv6, $signer, $key, $debug) = @_; my ($dnsdomain, $dnshost, $ipv4, $ipv6, $signer, $key, $debug) = @_;
my $dnsupdate = Net::DNS::Update->new($dnsdomain); 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 # If $RequireRR is set, ensure an records of specified type exist for the name
$dnsupdate->push(pre => yxrrset("$dnshost. $RequireRR")) if($RequireRR); $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 # 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. $ttl A $ipv4")) if($ipv4);
$dnsupdate->push(update=>rr_add("$dnshost. 3600 AAAA $ipv6")) if($ipv6); $dnsupdate->push(update=>rr_add("$dnshost. $ttl AAAA $ipv6")) if($ipv6);
# Always add a new TXT record with the timestamp of the last update # Add a TXT record with the timestamp of the last update, if required
my $txt = ($ipv4 or $ipv6) ? $UpdateTXT : $DeleteTXT; if (my $txt = ($ipv4 or $ipv6) ? $UpdateTXT : $DeleteTXT) {
$dnsupdate->push(update=>rr_add($dnshost. '. 3600 TXT "' . $txt . localtime() my $timestamp = localtime();
. '"')) if($txt); $dnsupdate->push(update=>rr_add("$dnshost. $ttl TXT \"$txt $timestamp\""));
}
# Sign the request with the signer and key # Sign the request with the signer and key
$dnsupdate->sign_tsig($signer, $key); $dnsupdate->sign_tsig($signer, $key);
@@ -326,24 +324,59 @@ sub handle_list($$$$$$) {
my $cgi = CGI->new; 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 # Validate Configuration
my $CE = 'Configuration Error:'; 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/; 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'); 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'); 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}); if ($RequireRR and not $DNS_label{$RequireRR});
die "$CE \$ExpireAfter '$ExpireAfter' is not supported\n" die "$CE RecordTTL '$RecordTTL' is not supported\n"
unless ($ExpireAfter=~/^\d+[smhw]$/); unless ($RecordTTL=~/^\d+[smhw]?$/);
die "$CE \$UpdateTXT must be set when \$ExpireAfter is set\n" 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); if($ExpireAfter and not $UpdateTXT);
foreach my $rrtype (@ReplaceRR) { foreach my $rrtype (@ReplaceRR) {
die "$CE \$ReplaceRR contains unsupported type '$rrtype'\n" die "$CE ReplaceRR contains unsupported type '$rrtype'\n"
unless ($DNS_label{$rrtype}); unless exists $DNS_label{$rrtype};
} }