diff --git a/dyndns.pl b/dyndns.pl index 5c02b01..96e4d42 100755 --- a/dyndns.pl +++ b/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); @@ -327,23 +325,58 @@ 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}; }