Commit 99ed087a8d4baeaa36242f45a4b6329e55d5d5ea

Authored by Frederik Lindenaar
1 parent de0f5780

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
Showing 1 changed file with 62 additions and 29 deletions
dyndns.pl
... ... @@ -26,12 +26,13 @@ use POSIX;
26 26 use Net::DNS;
27 27 use feature 'state';
28 28  
  29 +my $ConfigFile = 'optional'; # hardcoded, either optional, required or ignore
29 30  
30 31 ##############################
31   -# Configuration section
  32 +# Configuration section (defaults, can be set in config file)
32 33 my $DNSServer = '192.168.1.1'; # DNS Server to communicate with (use IP!)
33 34 my $ExpandCNAMEs = 1; # CNAME levels to expand (0 to disable)
34   -my $AllowDebugKey = 'on'; # Debuging, 'off' to disable, '' for always on
  35 +my $AllowDebugKey = 'off'; # Debuging, 'off' to disable, '' for always on
35 36 # and other values to enable with debug= param.
36 37 my $AuthMode = 'remote'; # either 'static', 'remote' or 'both'
37 38 my $StaticSigner = ''; # required for AuthMode 'static' or 'both'
... ... @@ -40,8 +41,9 @@ my $RequireRR = ''; # Require existing record of this type for upd.
40 41 my $ExpireAfter = '1w'; # Expire time for registrations in minutes,
41 42 # hours, weeks or seconds. format: [0-9]+[mhws]?
42 43 my @ReplaceRR = ('A', 'AAAA', 'TXT'); # Records types to replace in update
43   -my $UpdateTXT = 'Last DynDNS update on ';
44   -my $DeleteTXT = 'DynDNS cleared on ';
  44 +my $RecordTTL = '1h'; # TTL for created records, format: [0-9]+[mhws]?
  45 +my $UpdateTXT = 'Last DynDNS update on'; # if set add TXT with this+date on update
  46 +my $DeleteTXT = 'DynDNS cleared on'; # if set add TXT with this+date on delete
45 47  
46 48  
47 49 ##############################
... ... @@ -60,16 +62,10 @@ sub is_ipv6 { return $_[0]=~/^([0-9a-fA-F]{0,4}(:|$)){3,8}/i; }
60 62 sub periodSeconds($) {
61 63 my ($number, $units) = ($_[0]=~/^(\d+)([smhdw])?$/);
62 64 if($number && $units && $units cmp 's') {
63   - $number *= 60; # Convert to minutes
64   - if($units cmp 'm') {
65   - $number *= 60; # Convert to hours
66   - if($units cmp 'h') {
67   - $number *= 24; # Convert to days
68   - if($units cmp 'd') {
69   - $number *= 7; # Convert to weeks
70   - }
71   - }
72   - }
  65 + $number *= ($units eq 'm') ? 60 # Seconds per minute
  66 + : ($units cmp 'h') ? 3600 # Seconds per hour
  67 + : ($units cmp 'd') ? 86400 # Seconds per day
  68 + : 604800; # Seconds per week
73 69 }
74 70 return $number;
75 71 }
... ... @@ -163,6 +159,7 @@ sub get_authinfo($$) {
163 159 sub DNS_Update($$$$$$$) {
164 160 my ($dnsdomain, $dnshost, $ipv4, $ipv6, $signer, $key, $debug) = @_;
165 161 my $dnsupdate = Net::DNS::Update->new($dnsdomain);
  162 + my $ttl = periodSeconds($RecordTTL);
166 163  
167 164 # If $RequireRR is set, ensure an records of specified type exist for the name
168 165 $dnsupdate->push(pre => yxrrset("$dnshost. $RequireRR")) if($RequireRR);
... ... @@ -173,13 +170,14 @@ sub DNS_Update($$$$$$$) {
173 170 }
174 171  
175 172 # Add new A and AAAA record based on whether ipv4 and ipv6 address provided
176   - $dnsupdate->push(update=>rr_add("$dnshost. 3600 A $ipv4")) if($ipv4);
177   - $dnsupdate->push(update=>rr_add("$dnshost. 3600 AAAA $ipv6")) if($ipv6);
  173 + $dnsupdate->push(update=>rr_add("$dnshost. $ttl A $ipv4")) if($ipv4);
  174 + $dnsupdate->push(update=>rr_add("$dnshost. $ttl AAAA $ipv6")) if($ipv6);
178 175  
179   - # Always add a new TXT record with the timestamp of the last update
180   - my $txt = ($ipv4 or $ipv6) ? $UpdateTXT : $DeleteTXT;
181   - $dnsupdate->push(update=>rr_add($dnshost. '. 3600 TXT "' . $txt . localtime()
182   - . '"')) if($txt);
  176 + # Add a TXT record with the timestamp of the last update, if required
  177 + if (my $txt = ($ipv4 or $ipv6) ? $UpdateTXT : $DeleteTXT) {
  178 + my $timestamp = localtime();
  179 + $dnsupdate->push(update=>rr_add("$dnshost. $ttl TXT \"$txt $timestamp\""));
  180 + }
183 181  
184 182 # Sign the request with the signer and key
185 183 $dnsupdate->sign_tsig($signer, $key);
... ... @@ -327,23 +325,58 @@ sub handle_list($$$$$$) {
327 325 my $cgi = CGI->new;
328 326  
329 327 ##############################
  328 +# Load configuration, if desired
  329 +if ($ConfigFile cmp 'ignore') {
  330 + my $CFGFile = $0;
  331 + $CFGFile =~ s/(\.pl)?$/.cfg/;
  332 + if (open (CONFIG, $CFGFile)) {
  333 + my %CONFIG = (
  334 + allow_debug_key => \$AllowDebugKey, dns_server => \$DNSServer,
  335 + expand_cnames => \$ExpandCNAMEs, auth_mode => \$AuthMode,
  336 + static_signer => \$StaticSigner, static_key => \$StaticKey,
  337 + require_rr => \$RequireRR, replace_rr => \@ReplaceRR,
  338 + update_txt => \$UpdateTXT, delete_txt => \$DeleteTXT,
  339 + expire_after => \$ExpireAfter, record_ttl => \$RecordTTL,
  340 + );
  341 +
  342 + while (<CONFIG>) {
  343 + chomp; s/^\s+//; s/\s*(#.*)?$//; # trim whitespace & comments
  344 + next unless length; # skip empty lines
  345 + my ($key, $value) = split(/\s*=\s*/, $_, 2); # split key and value
  346 + my $dst = $CONFIG{$key}; # get destination for value
  347 + if (ref $dst eq 'SCALAR') { $$dst = $value; } # store scalar value
  348 + elsif (ref $dst eq 'CODE') { &$dst($value); } # call setter function
  349 + elsif (ref $dst cmp 'ARRAY') { die "Invalid $key in $CFGFile\n"; }
  350 + else { @$dst = split(/\s*,\s*/, $value); } # split and store array
  351 + }
  352 + close CONFIG;
  353 + } elsif ($ConfigFile eq 'required') {
  354 + die "unable to load configuration from $CFGFile: $!, aborting\n"
  355 + }
  356 +}
  357 +
  358 +##############################
330 359 # Validate Configuration
331 360 my $CE = 'Configuration Error:';
332   -die "$CE \$AuthMode '$AuthMode' is unsupported must be remote, static or both\n"
  361 +die "$CE ConfigFile must be optional, required or ignore, not '$ConfigFile'\n"
  362 + unless $ConfigFile=~/optional|required|ignore/;
  363 +die "$CE AuthMode '$AuthMode' is unsupported must be remote, static or both\n"
333 364 unless $AuthMode=~/remote|static|both/;
334   -die "$CE \$StaticSigner must be set for \$AuthMode '$AuthMode'\n"
  365 +die "$CE StaticSigner must be set for \$AuthMode '$AuthMode'\n"
335 366 unless ($StaticSigner or $AuthMode eq 'remote');
336   -die "$CE \$StaticKey must be set for \$AuthMode '$AuthMode'\n"
  367 +die "$CE StaticKey must be set for \$AuthMode '$AuthMode'\n"
337 368 unless ($StaticKey or $AuthMode eq 'remote');
338   -die "$CE \$RequireRR is set to unsupported type '$RequireRR'\n"
  369 +die "$CE RequireRR is set to unsupported type '$RequireRR'\n"
339 370 if ($RequireRR and not $DNS_label{$RequireRR});
340   -die "$CE \$ExpireAfter '$ExpireAfter' is not supported\n"
341   - unless ($ExpireAfter=~/^\d+[smhw]$/);
342   -die "$CE \$UpdateTXT must be set when \$ExpireAfter is set\n"
  371 +die "$CE RecordTTL '$RecordTTL' is not supported\n"
  372 + unless ($RecordTTL=~/^\d+[smhw]?$/);
  373 +die "$CE ExpireAfter '$ExpireAfter' is not supported\n"
  374 + unless ($ExpireAfter=~/^\d+[smhw]?$/);
  375 +die "$CE UpdateTXT must be set when \$ExpireAfter is set\n"
343 376 if($ExpireAfter and not $UpdateTXT);
344 377 foreach my $rrtype (@ReplaceRR) {
345   - die "$CE \$ReplaceRR contains unsupported type '$rrtype'\n"
346   - unless ($DNS_label{$rrtype});
  378 + die "$CE ReplaceRR contains unsupported type '$rrtype'\n"
  379 + unless exists $DNS_label{$rrtype};
347 380 }
348 381  
349 382  
... ...