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