remove fallback hard-coded config
This commit is contained in:
64
DDNS.pm
64
DDNS.pm
@@ -8,7 +8,7 @@ use JSON::PP qw/decode_json encode_json/;
|
||||
|
||||
memoize('_gethosts');
|
||||
|
||||
our $VERSION = '0.7';
|
||||
our $VERSION = '0.8';
|
||||
|
||||
# Control plane update keyfile (zone-management-key)
|
||||
our $keyfile = '/etc/bind/zone-management.key';
|
||||
@@ -17,9 +17,6 @@ sub new {
|
||||
my $me = shift;
|
||||
my %opts = @_;
|
||||
|
||||
# 'server' is where dig/axfr queries go for reading the control plane.
|
||||
# On the hidden master, localhost is correct. On other hosts, you may
|
||||
# pass 'server' => '<master-ip>'.
|
||||
my $server = $opts{server} // 'localhost';
|
||||
|
||||
return bless {
|
||||
@@ -58,22 +55,22 @@ sub _lookupOrDie {
|
||||
_validateOrDie($dom);
|
||||
my $fqdn = _fqdn($dom, $type);
|
||||
|
||||
my $answer;
|
||||
my $all = '';
|
||||
my $fh;
|
||||
open($fh, "dig +short -t txt \@$this->{server} $fqdn |")
|
||||
|| die "Can't open dig: $!";
|
||||
|
||||
# dig +short returns one line per TXT RR, each possibly containing multiple chunks.
|
||||
# We accept "exists" if we saw any quoted chunk at all.
|
||||
while (<$fh>) {
|
||||
$all .= $_;
|
||||
if ($_ =~ /^\"(.+)\"$/) {
|
||||
$answer = $1;
|
||||
}
|
||||
}
|
||||
if ($answer) {
|
||||
if ($_ =~ /\"/) { # any TXT output
|
||||
close $fh;
|
||||
return 1;
|
||||
} else {
|
||||
die "query failed: $all\n";
|
||||
}
|
||||
}
|
||||
close $fh;
|
||||
die "query failed: $all\n";
|
||||
}
|
||||
|
||||
sub __docmd {
|
||||
@@ -83,14 +80,11 @@ sub __docmd {
|
||||
close $tmpfh;
|
||||
|
||||
my $fh;
|
||||
# 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";
|
||||
|
||||
# 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> };
|
||||
@@ -99,7 +93,6 @@ sub __docmd {
|
||||
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;
|
||||
@@ -112,15 +105,12 @@ sub __docmd {
|
||||
sub _txt_from_line {
|
||||
my ($line) = @_;
|
||||
|
||||
# TXT in dig AXFR output may be one or more quoted segments.
|
||||
# Collect all segments and concatenate.
|
||||
my @parts;
|
||||
while ($line =~ /\"((?:\\.|[^\"])*)\"/g) {
|
||||
push @parts, $1;
|
||||
}
|
||||
my $txt = join('', @parts);
|
||||
|
||||
# Unescape common presentation escapes (at minimum \" and \\).
|
||||
$txt =~ s/\\\"/\"/g;
|
||||
$txt =~ s/\\\\/\\/g;
|
||||
|
||||
@@ -142,7 +132,6 @@ sub _parse_txt_payload {
|
||||
if (defined $obj) {
|
||||
$payload = $obj if ref($obj) eq 'HASH';
|
||||
|
||||
# Normalize masters string from supported JSON shapes.
|
||||
if (ref($obj) eq 'HASH') {
|
||||
if (defined $obj->{masters}) {
|
||||
if (ref($obj->{masters}) eq 'ARRAY') {
|
||||
@@ -169,20 +158,17 @@ sub _parse_txt_payload {
|
||||
}
|
||||
|
||||
# Produce BIND nsupdate TXT RDATA as one or more quoted strings.
|
||||
# BIND limits each TXT "character-string" to 255 bytes, so we chunk.
|
||||
# Each TXT character-string is limited to 255 bytes.
|
||||
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;
|
||||
|
||||
# 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);
|
||||
}
|
||||
|
||||
@@ -213,6 +199,7 @@ sub _gethosts {
|
||||
}
|
||||
}
|
||||
}
|
||||
close $fh;
|
||||
|
||||
return @vh;
|
||||
}
|
||||
@@ -248,7 +235,6 @@ sub set {
|
||||
my ($this, $dom, $txt_payload, $type) = @_;
|
||||
_validateOrDie($dom);
|
||||
|
||||
# Ensure it exists (for friendly errors)
|
||||
_lookupOrDie($this, $dom, $type);
|
||||
my $fqdn = _fqdn($dom, $type);
|
||||
|
||||
@@ -296,54 +282,30 @@ sub is_dnssec {
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub _default_config {
|
||||
return {
|
||||
default_master => '5.78.68.64',
|
||||
vhost_default_web_ip => '190.92.153.173',
|
||||
soa_mname => 'mister-dns.martnet.com.',
|
||||
soa_rname => 'root.martnet.com.',
|
||||
ns => [ 'ns1.martnet.com.', 'ns2.martnet.com.' ],
|
||||
vhost_mx => [
|
||||
{ priority => 5, host => '@' },
|
||||
{ priority => 10, host => 'mx2.martnet.com.' },
|
||||
{ priority => 15, host => 'mx3.martnet.com.' },
|
||||
],
|
||||
master_ddns_keyname => 'master-ddns-tsig',
|
||||
master_ddns_keyfile => '/etc/bind/master-ddns-tsig.key',
|
||||
};
|
||||
}
|
||||
|
||||
sub get_config {
|
||||
my ($this) = @_;
|
||||
|
||||
# Stored at: global._config.private.invalid.
|
||||
my @cfg = $this->get('_config');
|
||||
foreach my $r (@cfg) {
|
||||
next unless lc($r->{zone}) eq 'global';
|
||||
my $p = $r->{payload};
|
||||
if (defined $p && ref($p) eq 'HASH') {
|
||||
# Merge defaults for any missing keys
|
||||
my $d = _default_config();
|
||||
for my $k (keys %$d) {
|
||||
$p->{$k} = $d->{$k} unless exists $p->{$k};
|
||||
}
|
||||
return $p;
|
||||
}
|
||||
}
|
||||
return _default_config();
|
||||
|
||||
die "_config is not present in the control plane (expected global._config.private.invalid TXT). Bootstrap it before running tools.\n";
|
||||
}
|
||||
|
||||
sub set_config {
|
||||
my ($this, $cfg_hashref) = @_;
|
||||
die "Config must be a hashref" unless (defined $cfg_hashref && ref($cfg_hashref) eq 'HASH');
|
||||
|
||||
# Always store compact canonical JSON (encode_json is compact)
|
||||
my $txt = encode_json($cfg_hashref);
|
||||
|
||||
my $dom = "global.";
|
||||
my $type = "_config";
|
||||
|
||||
# Upsert behavior: if record exists, set, else add.
|
||||
my $existing;
|
||||
eval { $existing = $this->type($dom); 1 };
|
||||
if ($existing) {
|
||||
|
||||
Reference in New Issue
Block a user