From f663788138ee017d728988252a0ace41cbdf5813 Mon Sep 17 00:00:00 2001 From: Frederik Lindenaar <frederik@lindenaar.nl> Date: Mon, 5 Aug 2019 15:49:55 +0200 Subject: [PATCH] Sanitized HTTP Error codes Implemented consistent output (normally provide an HTTP response when --- dyndns.pl | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------------------- 1 file changed, 53 insertions(+), 43 deletions(-) diff --git a/dyndns.pl b/dyndns.pl index 96e4d42..a3e83b3 100755 --- a/dyndns.pl +++ b/dyndns.pl @@ -117,19 +117,37 @@ sub DNS_get($;$) { } } +############################## +# Initialize CGI, determine whether debugging is on and declaire fail method +my $cgi = CGI->new; +my $debug = ($AllowDebugKey cmp 'off') && (($AllowDebugKey eq '') || ($AllowDebugKey eq $cgi->param('debug')) ); +my $SE = 'System Error'; +my $CE = 'Configuration Error'; +my $PE = 'Required parameter missing'; +sub fail($$;$) { + my ($errormsg, $debugmsg, $exitcode) = @_; + print $debug . "\n"; + print $cgi->header(-status=>$exitcode || 503, -type=>'text/plain'), + "ERROR - $errormsg" . ($debug ? ": $debugmsg\n" : "\n"); + exit 0; +} + + # Check whether the hostname provided is actually a CNAME, returns the host # it points to in case of a CNAME or the original hostname if it is not. -sub expand_CNAME($;$) { - my ($host, @found) = @_; - if(my $cname=DNS_get($host, 'CNAME')) { +sub expand_CNAME($) { + my ($host) = @_; + my @found; + while(my $cname = DNS_get($host, 'CNAME')) { push(@found, $host); - die $found[0]."has > $ExpandCNAMEs level deep CNAME chain, aborting!\n" + $host = $cname; + fail("DNS CNAME Not supported", "$found[0] points to a CNAME but this is not supported", 400) + unless($ExpandCNAMEs); + fail("DNS CNAME Chain Error", "$found[0] has > $ExpandCNAMEs level deep CNAME chain", 400) unless($#found < $ExpandCNAMEs); - foreach my $r (@found) { - die "found CNAME loop for $host, aborting!\n" if($cname eq $r); + foreach my $r (@found) { + fail("DNS CNAME Loop", "found CNAME loop for $found[0]", 400) if($cname eq $r); } - - return &expand_CNAME($cname, @found); } return $host; } @@ -145,11 +163,8 @@ sub get_authinfo($$) { || (($AuthMode eq 'both') ? $StaticSigner : undef)); # Ensure we have a value for signer and key, otherwise abort the processing - if($signer eq '' or $key eq '') { - print $cgi->header(-status=>400, -type=>'text/plain'), - "ERROR - No/incomplete authentication information provided\n"; - exit - } + fail($PE, "No/incomplete authentication information provided", 400) + if($signer eq '' or $key eq ''); # and return the values return($signer, $key); @@ -195,7 +210,7 @@ sub DNS_Update($$$$$$$) { $response->header->rcode . $debugmessage); } else { # REFUSED, FORMERR - return (400, "ERROR - DNS update for $dnshost failed: " . + return (403, "ERROR - DNS update for $dnshost failed: " . $response->header->rcode . $debugmessage); } } else { @@ -207,8 +222,8 @@ sub DNS_Update($$$$$$$) { ############################## # Handlers for the different requests -sub handle_update($$$$$$) { - my ($cgi, $mode, $host, $dnshost, $dnsdomain, $debug) = @_; +sub handle_update($$$$$) { + my ($mode, $host, $dnshost, $dnsdomain, $debug) = @_; my ($signer, $key) = get_authinfo($cgi, $host); # perform the action @@ -236,8 +251,8 @@ sub handle_update($$$$$$) { } -sub handle_expire($$$$$$) { - my ($cgi, $mode, $host, $dnshost, $dnsdomain, $debug) = @_; +sub handle_expire($$$$$) { + my ($mode, $host, $dnshost, $dnsdomain, $debug) = @_; my ($signer, $key) = get_authinfo($cgi, $host); my $debugmsg = ($debug) ? "\n" : ''; @@ -271,8 +286,8 @@ sub handle_expire($$$$$$) { } } -sub handle_view($$$$$$) { - my ($cgi, $mode, $host, $dnshost, $dnsdomain, $debug) = @_; +sub handle_view($$$$$) { + my ($mode, $host, $dnshost, $dnsdomain, $debug) = @_; my $title = "DynDNS Updater - $host"; print $cgi->header(-status=>200), $cgi->start_html(-title => $title), @@ -295,8 +310,8 @@ sub handle_view($$$$$$) { print $cgi->end_html(); } -sub handle_list($$$$$$) { - my ($cgi, $mode, $host, $dnshost, $dnsdomain, $debug) = @_; +sub handle_list($$$$$) { + my ($mode, $host, $dnshost, $dnsdomain, $debug) = @_; my $title = "DynDNS Updater - $dnsdomain"; print $cgi->header(-status=>200), @@ -322,7 +337,6 @@ sub handle_list($$$$$$) { print $cgi->end_html(); } -my $cgi = CGI->new; ############################## # Load configuration, if desired @@ -346,36 +360,36 @@ if ($ConfigFile cmp 'ignore') { 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 + elsif (ref $dst cmp 'ARRAY') { fail($SE, "Invalid config: $key"); } + 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" + fail($SE, "unable to load configuration from $CFGFile: $!"); } } + ############################## # Validate Configuration -my $CE = 'Configuration Error:'; -die "$CE ConfigFile must be optional, required or ignore, not '$ConfigFile'\n" +fail($CE, "ConfigFile must be optional, required or ignore, not '$ConfigFile'") unless $ConfigFile=~/optional|required|ignore/; -die "$CE AuthMode '$AuthMode' is unsupported must be remote, static or both\n" +fail($CE, "AuthMode '$AuthMode' is unsupported must be remote, static or both") unless $AuthMode=~/remote|static|both/; -die "$CE StaticSigner must be set for \$AuthMode '$AuthMode'\n" +fail($CE, "StaticSigner must be set for \$AuthMode '$AuthMode'") unless ($StaticSigner or $AuthMode eq 'remote'); -die "$CE StaticKey must be set for \$AuthMode '$AuthMode'\n" +fail($CE, "StaticKey must be set for \$AuthMode '$AuthMode'") unless ($StaticKey or $AuthMode eq 'remote'); -die "$CE RequireRR is set to unsupported type '$RequireRR'\n" +fail($CE, "RequireRR is set to unsupported type '$RequireRR'") if ($RequireRR and not $DNS_label{$RequireRR}); -die "$CE RecordTTL '$RecordTTL' is not supported\n" +fail($CE, "RecordTTL '$RecordTTL' is not supported") unless ($RecordTTL=~/^\d+[smhw]?$/); -die "$CE ExpireAfter '$ExpireAfter' is not supported\n" +fail($CE, "ExpireAfter '$ExpireAfter' is not supported") unless ($ExpireAfter=~/^\d+[smhw]?$/); -die "$CE UpdateTXT must be set when \$ExpireAfter is set\n" +fail($CE, "UpdateTXT must be set when \$ExpireAfter is set") if($ExpireAfter and not $UpdateTXT); foreach my $rrtype (@ReplaceRR) { - die "$CE ReplaceRR contains unsupported type '$rrtype'\n" + fail($CE, "ReplaceRR contains unsupported type '$rrtype'") unless exists $DNS_label{$rrtype}; } @@ -385,7 +399,6 @@ foreach my $rrtype (@ReplaceRR) { my $mode = $cgi->path_info || $cgi->param('mode') || 'view'; $mode=~s/^\/([^\/]+)(\/(.*))?/$1/; my $host = $cgi->param('host') || $3; -my $debug = ($AllowDebugKey eq 'off') ? 0 : ($AllowDebugKey eq ($cgi->param('debug') || '')); ############################## @@ -398,16 +411,13 @@ my %handlers = ( expire => \&handle_expire, ); if($host eq '' and $mode cmp 'list' and $mode cmp 'expire') { - print $cgi->header(-status=>400, -type=>'text/plain'), - "ERROR - No host name to act on specified\n"; + fail($PE, "No host name to act on specified", 400); } elsif(my $handler = $handlers{$mode}) { # Replace provided host with that of a CNAME it points to and determine domain my $dnshost = ($host) ? expand_CNAME($host) : undef; my $dnsdomain = $cgi->param('domain') || ($dnshost=~/\.(.*)$/)[0]; - $handler->($cgi, $mode, $host, $dnshost, $dnsdomain, $debug); + $handler->($mode, $host, $dnshost, $dnsdomain, $debug); } else { - print $cgi->header(-status=>($cgi->path_info) ? 404 : 400, - -type=>'text/plain'), - "ERROR - File Not Found / Invalid Mode '$mode' specified\n"; + fail("File Not Found", "Invalid Mode '$mode' specified", 404); } -- libgit2 0.22.2