Sanitized HTTP Error codes

Implemented consistent output (normally provide an HTTP response when 
usual things go wrong)
Hid detailed error information in debug info so that end users cannot 
see this
This commit is contained in:
2019-08-05 15:49:55 +02:00
parent e568ed64cd
commit f663788138

100
dyndns.pl
View File

@@ -117,19 +117,37 @@ sub DNS_get($;$) {
} }
} }
# 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. # Initialize CGI, determine whether debugging is on and declaire fail method
sub expand_CNAME($;$) { my $cgi = CGI->new;
my ($host, @found) = @_; my $debug = ($AllowDebugKey cmp 'off') && (($AllowDebugKey eq '') || ($AllowDebugKey eq $cgi->param('debug')) );
if(my $cname=DNS_get($host, 'CNAME')) { my $SE = 'System Error';
push(@found, $host); my $CE = 'Configuration Error';
die $found[0]."has > $ExpandCNAMEs level deep CNAME chain, aborting!\n" my $PE = 'Required parameter missing';
unless($#found < $ExpandCNAMEs); sub fail($$;$) {
foreach my $r (@found) { my ($errormsg, $debugmsg, $exitcode) = @_;
die "found CNAME loop for $host, aborting!\n" if($cname eq $r); print $debug . "\n";
print $cgi->header(-status=>$exitcode || 503, -type=>'text/plain'),
"ERROR - $errormsg" . ($debug ? ": $debugmsg\n" : "\n");
exit 0;
} }
return &expand_CNAME($cname, @found);
# 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) = @_;
my @found;
while(my $cname = DNS_get($host, 'CNAME')) {
push(@found, $host);
$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) {
fail("DNS CNAME Loop", "found CNAME loop for $found[0]", 400) if($cname eq $r);
}
} }
return $host; return $host;
} }
@@ -145,11 +163,8 @@ sub get_authinfo($$) {
|| (($AuthMode eq 'both') ? $StaticSigner : undef)); || (($AuthMode eq 'both') ? $StaticSigner : undef));
# Ensure we have a value for signer and key, otherwise abort the processing # Ensure we have a value for signer and key, otherwise abort the processing
if($signer eq '' or $key eq '') { fail($PE, "No/incomplete authentication information provided", 400)
print $cgi->header(-status=>400, -type=>'text/plain'), if($signer eq '' or $key eq '');
"ERROR - No/incomplete authentication information provided\n";
exit
}
# and return the values # and return the values
return($signer, $key); return($signer, $key);
@@ -195,7 +210,7 @@ sub DNS_Update($$$$$$$) {
$response->header->rcode . $debugmessage); $response->header->rcode . $debugmessage);
} else { } else {
# REFUSED, FORMERR # REFUSED, FORMERR
return (400, "ERROR - DNS update for $dnshost failed: " . return (403, "ERROR - DNS update for $dnshost failed: " .
$response->header->rcode . $debugmessage); $response->header->rcode . $debugmessage);
} }
} else { } else {
@@ -207,8 +222,8 @@ sub DNS_Update($$$$$$$) {
############################## ##############################
# Handlers for the different requests # Handlers for the different requests
sub handle_update($$$$$$) { sub handle_update($$$$$) {
my ($cgi, $mode, $host, $dnshost, $dnsdomain, $debug) = @_; my ($mode, $host, $dnshost, $dnsdomain, $debug) = @_;
my ($signer, $key) = get_authinfo($cgi, $host); my ($signer, $key) = get_authinfo($cgi, $host);
# perform the action # perform the action
@@ -236,8 +251,8 @@ sub handle_update($$$$$$) {
} }
sub handle_expire($$$$$$) { sub handle_expire($$$$$) {
my ($cgi, $mode, $host, $dnshost, $dnsdomain, $debug) = @_; my ($mode, $host, $dnshost, $dnsdomain, $debug) = @_;
my ($signer, $key) = get_authinfo($cgi, $host); my ($signer, $key) = get_authinfo($cgi, $host);
my $debugmsg = ($debug) ? "\n" : ''; my $debugmsg = ($debug) ? "\n" : '';
@@ -271,8 +286,8 @@ sub handle_expire($$$$$$) {
} }
} }
sub handle_view($$$$$$) { sub handle_view($$$$$) {
my ($cgi, $mode, $host, $dnshost, $dnsdomain, $debug) = @_; my ($mode, $host, $dnshost, $dnsdomain, $debug) = @_;
my $title = "DynDNS Updater - $host"; my $title = "DynDNS Updater - $host";
print $cgi->header(-status=>200), print $cgi->header(-status=>200),
$cgi->start_html(-title => $title), $cgi->start_html(-title => $title),
@@ -295,8 +310,8 @@ sub handle_view($$$$$$) {
print $cgi->end_html(); print $cgi->end_html();
} }
sub handle_list($$$$$$) { sub handle_list($$$$$) {
my ($cgi, $mode, $host, $dnshost, $dnsdomain, $debug) = @_; my ($mode, $host, $dnshost, $dnsdomain, $debug) = @_;
my $title = "DynDNS Updater - $dnsdomain"; my $title = "DynDNS Updater - $dnsdomain";
print $cgi->header(-status=>200), print $cgi->header(-status=>200),
@@ -322,7 +337,6 @@ sub handle_list($$$$$$) {
print $cgi->end_html(); print $cgi->end_html();
} }
my $cgi = CGI->new;
############################## ##############################
# Load configuration, if desired # Load configuration, if desired
@@ -346,36 +360,36 @@ if ($ConfigFile cmp 'ignore') {
my $dst = $CONFIG{$key}; # get destination for value my $dst = $CONFIG{$key}; # get destination for value
if (ref $dst eq 'SCALAR') { $$dst = $value; } # store scalar value if (ref $dst eq 'SCALAR') { $$dst = $value; } # store scalar value
elsif (ref $dst eq 'CODE') { &$dst($value); } # call setter function elsif (ref $dst eq 'CODE') { &$dst($value); } # call setter function
elsif (ref $dst cmp 'ARRAY') { die "Invalid $key in $CFGFile\n"; } elsif (ref $dst cmp 'ARRAY') { fail($SE, "Invalid config: $key"); }
else { @$dst = split(/\s*,\s*/, $value); } # split and store array else { @$dst = split(/\s*,\s*/, $value); } # split and store array
} }
close CONFIG; close CONFIG;
} elsif ($ConfigFile eq 'required') { } elsif ($ConfigFile eq 'required') {
die "unable to load configuration from $CFGFile: $!, aborting\n" fail($SE, "unable to load configuration from $CFGFile: $!");
} }
} }
############################## ##############################
# Validate Configuration # Validate Configuration
my $CE = 'Configuration Error:'; fail($CE, "ConfigFile must be optional, required or ignore, not '$ConfigFile'")
die "$CE ConfigFile must be optional, required or ignore, not '$ConfigFile'\n"
unless $ConfigFile=~/optional|required|ignore/; 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/; 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'); 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'); 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}); 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]?$/); unless ($RecordTTL=~/^\d+[smhw]?$/);
die "$CE ExpireAfter '$ExpireAfter' is not supported\n" fail($CE, "ExpireAfter '$ExpireAfter' is not supported")
unless ($ExpireAfter=~/^\d+[smhw]?$/); 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); if($ExpireAfter and not $UpdateTXT);
foreach my $rrtype (@ReplaceRR) { foreach my $rrtype (@ReplaceRR) {
die "$CE ReplaceRR contains unsupported type '$rrtype'\n" fail($CE, "ReplaceRR contains unsupported type '$rrtype'")
unless exists $DNS_label{$rrtype}; unless exists $DNS_label{$rrtype};
} }
@@ -385,7 +399,6 @@ foreach my $rrtype (@ReplaceRR) {
my $mode = $cgi->path_info || $cgi->param('mode') || 'view'; my $mode = $cgi->path_info || $cgi->param('mode') || 'view';
$mode=~s/^\/([^\/]+)(\/(.*))?/$1/; $mode=~s/^\/([^\/]+)(\/(.*))?/$1/;
my $host = $cgi->param('host') || $3; 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, expire => \&handle_expire,
); );
if($host eq '' and $mode cmp 'list' and $mode cmp 'expire') { if($host eq '' and $mode cmp 'list' and $mode cmp 'expire') {
print $cgi->header(-status=>400, -type=>'text/plain'), fail($PE, "No host name to act on specified", 400);
"ERROR - No host name to act on specified\n";
} elsif(my $handler = $handlers{$mode}) { } elsif(my $handler = $handlers{$mode}) {
# Replace provided host with that of a CNAME it points to and determine domain # Replace provided host with that of a CNAME it points to and determine domain
my $dnshost = ($host) ? expand_CNAME($host) : undef; my $dnshost = ($host) ? expand_CNAME($host) : undef;
my $dnsdomain = $cgi->param('domain') || ($dnshost=~/\.(.*)$/)[0]; my $dnsdomain = $cgi->param('domain') || ($dnshost=~/\.(.*)$/)[0];
$handler->($cgi, $mode, $host, $dnshost, $dnsdomain, $debug); $handler->($mode, $host, $dnshost, $dnsdomain, $debug);
} else { } else {
print $cgi->header(-status=>($cgi->path_info) ? 404 : 400, fail("File Not Found", "Invalid Mode '$mode' specified", 404);
-type=>'text/plain'),
"ERROR - File Not Found / Invalid Mode '$mode' specified\n";
} }