Commit f663788138ee017d728988252a0ace41cbdf5813

Authored by Frederik Lindenaar
1 parent e568ed64

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
Showing 1 changed file with 53 additions and 43 deletions
dyndns.pl
@@ -117,19 +117,37 @@ sub DNS_get($;$) { @@ -117,19 +117,37 @@ sub DNS_get($;$) {
117 } 117 }
118 } 118 }
119 119
  120 +##############################
  121 +# Initialize CGI, determine whether debugging is on and declaire fail method
  122 +my $cgi = CGI->new;
  123 +my $debug = ($AllowDebugKey cmp 'off') && (($AllowDebugKey eq '') || ($AllowDebugKey eq $cgi->param('debug')) );
  124 +my $SE = 'System Error';
  125 +my $CE = 'Configuration Error';
  126 +my $PE = 'Required parameter missing';
  127 +sub fail($$;$) {
  128 + my ($errormsg, $debugmsg, $exitcode) = @_;
  129 + print $debug . "\n";
  130 + print $cgi->header(-status=>$exitcode || 503, -type=>'text/plain'),
  131 + "ERROR - $errormsg" . ($debug ? ": $debugmsg\n" : "\n");
  132 + exit 0;
  133 +}
  134 +
  135 +
120 # Check whether the hostname provided is actually a CNAME, returns the host 136 # Check whether the hostname provided is actually a CNAME, returns the host
121 # it points to in case of a CNAME or the original hostname if it is not. 137 # it points to in case of a CNAME or the original hostname if it is not.
122 -sub expand_CNAME($;$) {  
123 - my ($host, @found) = @_;  
124 - if(my $cname=DNS_get($host, 'CNAME')) { 138 +sub expand_CNAME($) {
  139 + my ($host) = @_;
  140 + my @found;
  141 + while(my $cname = DNS_get($host, 'CNAME')) {
125 push(@found, $host); 142 push(@found, $host);
126 - die $found[0]."has > $ExpandCNAMEs level deep CNAME chain, aborting!\n" 143 + $host = $cname;
  144 + fail("DNS CNAME Not supported", "$found[0] points to a CNAME but this is not supported", 400)
  145 + unless($ExpandCNAMEs);
  146 + fail("DNS CNAME Chain Error", "$found[0] has > $ExpandCNAMEs level deep CNAME chain", 400)
127 unless($#found < $ExpandCNAMEs); 147 unless($#found < $ExpandCNAMEs);
128 - foreach my $r (@found) {  
129 - die "found CNAME loop for $host, aborting!\n" if($cname eq $r); 148 + foreach my $r (@found) {
  149 + fail("DNS CNAME Loop", "found CNAME loop for $found[0]", 400) if($cname eq $r);
130 } 150 }
131 -  
132 - return &expand_CNAME($cname, @found);  
133 } 151 }
134 return $host; 152 return $host;
135 } 153 }
@@ -145,11 +163,8 @@ sub get_authinfo($$) { @@ -145,11 +163,8 @@ sub get_authinfo($$) {
145 || (($AuthMode eq 'both') ? $StaticSigner : undef)); 163 || (($AuthMode eq 'both') ? $StaticSigner : undef));
146 164
147 # Ensure we have a value for signer and key, otherwise abort the processing 165 # Ensure we have a value for signer and key, otherwise abort the processing
148 - if($signer eq '' or $key eq '') {  
149 - print $cgi->header(-status=>400, -type=>'text/plain'),  
150 - "ERROR - No/incomplete authentication information provided\n";  
151 - exit  
152 - } 166 + fail($PE, "No/incomplete authentication information provided", 400)
  167 + if($signer eq '' or $key eq '');
153 168
154 # and return the values 169 # and return the values
155 return($signer, $key); 170 return($signer, $key);
@@ -195,7 +210,7 @@ sub DNS_Update($$$$$$$) { @@ -195,7 +210,7 @@ sub DNS_Update($$$$$$$) {
195 $response->header->rcode . $debugmessage); 210 $response->header->rcode . $debugmessage);
196 } else { 211 } else {
197 # REFUSED, FORMERR 212 # REFUSED, FORMERR
198 - return (400, "ERROR - DNS update for $dnshost failed: " . 213 + return (403, "ERROR - DNS update for $dnshost failed: " .
199 $response->header->rcode . $debugmessage); 214 $response->header->rcode . $debugmessage);
200 } 215 }
201 } else { 216 } else {
@@ -207,8 +222,8 @@ sub DNS_Update($$$$$$$) { @@ -207,8 +222,8 @@ sub DNS_Update($$$$$$$) {
207 222
208 ############################## 223 ##############################
209 # Handlers for the different requests 224 # Handlers for the different requests
210 -sub handle_update($$$$$$) {  
211 - my ($cgi, $mode, $host, $dnshost, $dnsdomain, $debug) = @_; 225 +sub handle_update($$$$$) {
  226 + my ($mode, $host, $dnshost, $dnsdomain, $debug) = @_;
212 my ($signer, $key) = get_authinfo($cgi, $host); 227 my ($signer, $key) = get_authinfo($cgi, $host);
213 228
214 # perform the action 229 # perform the action
@@ -236,8 +251,8 @@ sub handle_update($$$$$$) { @@ -236,8 +251,8 @@ sub handle_update($$$$$$) {
236 251
237 } 252 }
238 253
239 -sub handle_expire($$$$$$) {  
240 - my ($cgi, $mode, $host, $dnshost, $dnsdomain, $debug) = @_; 254 +sub handle_expire($$$$$) {
  255 + my ($mode, $host, $dnshost, $dnsdomain, $debug) = @_;
241 my ($signer, $key) = get_authinfo($cgi, $host); 256 my ($signer, $key) = get_authinfo($cgi, $host);
242 257
243 my $debugmsg = ($debug) ? "\n" : ''; 258 my $debugmsg = ($debug) ? "\n" : '';
@@ -271,8 +286,8 @@ sub handle_expire($$$$$$) { @@ -271,8 +286,8 @@ sub handle_expire($$$$$$) {
271 } 286 }
272 } 287 }
273 288
274 -sub handle_view($$$$$$) {  
275 - my ($cgi, $mode, $host, $dnshost, $dnsdomain, $debug) = @_; 289 +sub handle_view($$$$$) {
  290 + my ($mode, $host, $dnshost, $dnsdomain, $debug) = @_;
276 my $title = "DynDNS Updater - $host"; 291 my $title = "DynDNS Updater - $host";
277 print $cgi->header(-status=>200), 292 print $cgi->header(-status=>200),
278 $cgi->start_html(-title => $title), 293 $cgi->start_html(-title => $title),
@@ -295,8 +310,8 @@ sub handle_view($$$$$$) { @@ -295,8 +310,8 @@ sub handle_view($$$$$$) {
295 print $cgi->end_html(); 310 print $cgi->end_html();
296 } 311 }
297 312
298 -sub handle_list($$$$$$) {  
299 - my ($cgi, $mode, $host, $dnshost, $dnsdomain, $debug) = @_; 313 +sub handle_list($$$$$) {
  314 + my ($mode, $host, $dnshost, $dnsdomain, $debug) = @_;
300 my $title = "DynDNS Updater - $dnsdomain"; 315 my $title = "DynDNS Updater - $dnsdomain";
301 316
302 print $cgi->header(-status=>200), 317 print $cgi->header(-status=>200),
@@ -322,7 +337,6 @@ sub handle_list($$$$$$) { @@ -322,7 +337,6 @@ sub handle_list($$$$$$) {
322 print $cgi->end_html(); 337 print $cgi->end_html();
323 } 338 }
324 339
325 -my $cgi = CGI->new;  
326 340
327 ############################## 341 ##############################
328 # Load configuration, if desired 342 # Load configuration, if desired
@@ -346,36 +360,36 @@ if ($ConfigFile cmp &#39;ignore&#39;) { @@ -346,36 +360,36 @@ if ($ConfigFile cmp &#39;ignore&#39;) {
346 my $dst = $CONFIG{$key}; # get destination for value 360 my $dst = $CONFIG{$key}; # get destination for value
347 if (ref $dst eq 'SCALAR') { $$dst = $value; } # store scalar value 361 if (ref $dst eq 'SCALAR') { $$dst = $value; } # store scalar value
348 elsif (ref $dst eq 'CODE') { &$dst($value); } # call setter function 362 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 363 + elsif (ref $dst cmp 'ARRAY') { fail($SE, "Invalid config: $key"); }
  364 + else { @$dst = split(/\s*,\s*/, $value); } # split and store array
351 } 365 }
352 close CONFIG; 366 close CONFIG;
353 } elsif ($ConfigFile eq 'required') { 367 } elsif ($ConfigFile eq 'required') {
354 - die "unable to load configuration from $CFGFile: $!, aborting\n" 368 + fail($SE, "unable to load configuration from $CFGFile: $!");
355 } 369 }
356 } 370 }
357 371
  372 +
358 ############################## 373 ##############################
359 # Validate Configuration 374 # Validate Configuration
360 -my $CE = 'Configuration Error:';  
361 -die "$CE ConfigFile must be optional, required or ignore, not '$ConfigFile'\n" 375 +fail($CE, "ConfigFile must be optional, required or ignore, not '$ConfigFile'")
362 unless $ConfigFile=~/optional|required|ignore/; 376 unless $ConfigFile=~/optional|required|ignore/;
363 -die "$CE AuthMode '$AuthMode' is unsupported must be remote, static or both\n" 377 +fail($CE, "AuthMode '$AuthMode' is unsupported must be remote, static or both")
364 unless $AuthMode=~/remote|static|both/; 378 unless $AuthMode=~/remote|static|both/;
365 -die "$CE StaticSigner must be set for \$AuthMode '$AuthMode'\n" 379 +fail($CE, "StaticSigner must be set for \$AuthMode '$AuthMode'")
366 unless ($StaticSigner or $AuthMode eq 'remote'); 380 unless ($StaticSigner or $AuthMode eq 'remote');
367 -die "$CE StaticKey must be set for \$AuthMode '$AuthMode'\n" 381 +fail($CE, "StaticKey must be set for \$AuthMode '$AuthMode'")
368 unless ($StaticKey or $AuthMode eq 'remote'); 382 unless ($StaticKey or $AuthMode eq 'remote');
369 -die "$CE RequireRR is set to unsupported type '$RequireRR'\n" 383 +fail($CE, "RequireRR is set to unsupported type '$RequireRR'")
370 if ($RequireRR and not $DNS_label{$RequireRR}); 384 if ($RequireRR and not $DNS_label{$RequireRR});
371 -die "$CE RecordTTL '$RecordTTL' is not supported\n" 385 +fail($CE, "RecordTTL '$RecordTTL' is not supported")
372 unless ($RecordTTL=~/^\d+[smhw]?$/); 386 unless ($RecordTTL=~/^\d+[smhw]?$/);
373 -die "$CE ExpireAfter '$ExpireAfter' is not supported\n" 387 +fail($CE, "ExpireAfter '$ExpireAfter' is not supported")
374 unless ($ExpireAfter=~/^\d+[smhw]?$/); 388 unless ($ExpireAfter=~/^\d+[smhw]?$/);
375 -die "$CE UpdateTXT must be set when \$ExpireAfter is set\n" 389 +fail($CE, "UpdateTXT must be set when \$ExpireAfter is set")
376 if($ExpireAfter and not $UpdateTXT); 390 if($ExpireAfter and not $UpdateTXT);
377 foreach my $rrtype (@ReplaceRR) { 391 foreach my $rrtype (@ReplaceRR) {
378 - die "$CE ReplaceRR contains unsupported type '$rrtype'\n" 392 + fail($CE, "ReplaceRR contains unsupported type '$rrtype'")
379 unless exists $DNS_label{$rrtype}; 393 unless exists $DNS_label{$rrtype};
380 } 394 }
381 395
@@ -385,7 +399,6 @@ foreach my $rrtype (@ReplaceRR) { @@ -385,7 +399,6 @@ foreach my $rrtype (@ReplaceRR) {
385 my $mode = $cgi->path_info || $cgi->param('mode') || 'view'; 399 my $mode = $cgi->path_info || $cgi->param('mode') || 'view';
386 $mode=~s/^\/([^\/]+)(\/(.*))?/$1/; 400 $mode=~s/^\/([^\/]+)(\/(.*))?/$1/;
387 my $host = $cgi->param('host') || $3; 401 my $host = $cgi->param('host') || $3;
388 -my $debug = ($AllowDebugKey eq 'off') ? 0 : ($AllowDebugKey eq ($cgi->param('debug') || ''));  
389 402
390 403
391 ############################## 404 ##############################
@@ -398,16 +411,13 @@ my %handlers = ( @@ -398,16 +411,13 @@ my %handlers = (
398 expire => \&handle_expire, 411 expire => \&handle_expire,
399 ); 412 );
400 if($host eq '' and $mode cmp 'list' and $mode cmp 'expire') { 413 if($host eq '' and $mode cmp 'list' and $mode cmp 'expire') {
401 - print $cgi->header(-status=>400, -type=>'text/plain'),  
402 - "ERROR - No host name to act on specified\n"; 414 + fail($PE, "No host name to act on specified", 400);
403 } elsif(my $handler = $handlers{$mode}) { 415 } elsif(my $handler = $handlers{$mode}) {
404 # Replace provided host with that of a CNAME it points to and determine domain 416 # Replace provided host with that of a CNAME it points to and determine domain
405 my $dnshost = ($host) ? expand_CNAME($host) : undef; 417 my $dnshost = ($host) ? expand_CNAME($host) : undef;
406 my $dnsdomain = $cgi->param('domain') || ($dnshost=~/\.(.*)$/)[0]; 418 my $dnsdomain = $cgi->param('domain') || ($dnshost=~/\.(.*)$/)[0];
407 - $handler->($cgi, $mode, $host, $dnshost, $dnsdomain, $debug); 419 + $handler->($mode, $host, $dnshost, $dnsdomain, $debug);
408 } else { 420 } else {
409 - print $cgi->header(-status=>($cgi->path_info) ? 404 : 400,  
410 - -type=>'text/plain'),  
411 - "ERROR - File Not Found / Invalid Mode '$mode' specified\n"; 421 + fail("File Not Found", "Invalid Mode '$mode' specified", 404);
412 } 422 }
413 423