From b6af2cefe28dbfd37615261247aa0ad77402314b Mon Sep 17 00:00:00 2001 From: Jorj Bauer Date: Sat, 24 Jan 2026 15:17:13 -0500 Subject: [PATCH] fix for large data --- DDNS.pm | 44 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/DDNS.pm b/DDNS.pm index 46d1f64..5e6ce27 100644 --- a/DDNS.pm +++ b/DDNS.pm @@ -8,7 +8,7 @@ use JSON::PP qw/decode_json encode_json/; memoize('_gethosts'); -our $VERSION = '0.6'; +our $VERSION = '0.7'; # Control plane update keyfile (zone-management-key) our $keyfile = '/etc/bind/zone-management.key'; @@ -83,17 +83,28 @@ sub __docmd { close $tmpfh; my $fh; - open($fh, "|nsupdate -k $this->{keyfile} > $filename") + # Capture BOTH stdout and stderr into the temp file so errors are visible, + # and so we can fail the caller when nsupdate fails. + open($fh, "|nsupdate -k $this->{keyfile} > $filename 2>&1") || die "Can't open nsupdate: $!"; print $fh "server localhost\nzone private.invalid.\n$cmd\nshow\nsend\n"; - close $fh; - open($fh, $filename) || die "Can't re-open tmpfile $filename: $!"; - while (<$fh>) { + # If nsupdate fails, close() will return false and $? will be non-zero. + close($fh) or do { + open(my $rfh, $filename) || die "Can't re-open tmpfile $filename: $!"; + my $out = do { local $/; <$rfh> }; + close $rfh; + unlink $filename; + die "nsupdate failed (exit=$?):\n$out\n"; + }; + + # Echo nsupdate output (useful for operators/logs) + open(my $rfh, $filename) || die "Can't re-open tmpfile $filename: $!"; + while (<$rfh>) { print; } - close $fh; + close $rfh; unlink $filename; } @@ -157,12 +168,22 @@ sub _parse_txt_payload { return ($v, $payload); } +# Produce BIND nsupdate TXT RDATA as one or more quoted strings. +# BIND limits each TXT "character-string" to 255 bytes, so we chunk. sub _quote_txt_rdata { my ($s) = @_; $s = '' unless defined $s; + + # Escape for inclusion inside a quoted TXT character-string $s =~ s/\\/\\\\/g; $s =~ s/\"/\\\"/g; - return "\"$s\""; + + # Conservative chunk size to stay well under 255 bytes after escaping + my $chunk_len = 200; + my @chunks = ($s =~ /.{1,$chunk_len}/gs); + + # Emit as: "chunk1" "chunk2" "chunk3" + return join(' ', map { "\"$_\"" } @chunks); } sub _gethosts { @@ -231,7 +252,10 @@ sub set { _lookupOrDie($this, $dom, $type); my $fqdn = _fqdn($dom, $type); - $this->__docmd("update delete $fqdn TXT\nupdate add $fqdn 60 TXT " . _quote_txt_rdata($txt_payload)); + $this->__docmd( + "update delete $fqdn TXT\n" . + "update add $fqdn 60 TXT " . _quote_txt_rdata($txt_payload) + ); $this->cleanup(); } @@ -313,10 +337,10 @@ sub set_config { my ($this, $cfg_hashref) = @_; die "Config must be a hashref" unless (defined $cfg_hashref && ref($cfg_hashref) eq 'HASH'); - # Always store canonical JSON + # Always store compact canonical JSON (encode_json is compact) my $txt = encode_json($cfg_hashref); - my $dom = "global."; + my $dom = "global."; my $type = "_config"; # Upsert behavior: if record exists, set, else add.