#!/usr/bin/perl use strict; use warnings; use Martnet::DDNS; use File::Temp qw/tempfile/; use Getopt::Long qw(GetOptions); # Defaults my $server = 'mister-dns.martnet.com'; # Optional flags # --server Override control-plane master # force Legacy positional flag my $opt_server; GetOptions( 'server=s' => \$opt_server, ) or die "Usage: sync-slave [--server host] [force]\n"; $server = $opt_server if defined $opt_server; # Optional positional "force" flag (legacy behavior) my $force = shift; my $ddns = Martnet::DDNS->new(server => $server); # Pull all control-plane entries, excluding metadata (_config) my @vh_all = $ddns->get(); my @vh = grep { $_->{type} ne '_config' && defined($_->{zone}) && $_->{zone} ne '' && defined($_->{master}) && $_->{master} ne '' && $_->{master} !~ /^\s*[\{\[]/ } @vh_all; my %vhh = map { $_->{zone} => 1 } @vh; my @all = parse_slavefile("/etc/bind/martnet.slave.zones.9"); my %allh = all_zones_hash(@all); # Detect changes my $changecount = 0; foreach my $i (@vh) { unless (contains_zone($i, @all)) { print "don't have $i->{zone}\n"; $changecount++; } } foreach my $z (keys %allh) { $changecount++ unless ($vhh{$z}); } die "Cowardly refusing to make a big update automatically [$changecount]" if ($changecount > 10 && !$force); if ($changecount) { do_rewrite(@vh); } exit 0; sub parse_slavefile { my ($f) = @_; my @ret; open(my $fh, $f) || die "Can't open $f: $!"; while (<$fh>) { if (/^zone\s+"([^"]+)"\s+\{.*masters\s+\{\s*([^}]+);\s*\}/) { push @ret, { zone => $1, master => $2 }; } } close $fh; return @ret; } sub do_rewrite { my (@vh) = @_; my ($fh, $path) = tempfile(); print "Differences found; rewriting slave file.\n"; foreach my $i (sort { $a->{zone} cmp $b->{zone} } @vh) { die "No master(s) found for slave zone $i->{zone}" unless defined($i->{master}) && $i->{master} ne ''; print $fh "zone \"$i->{zone}\" { " . "type slave; " . "file \"/var/cache/bind/db.$i->{zone}\"; " . "masters { $i->{master}; }; " . "allow-notify { key \"notify-key\"; }; " . "};\n"; } close $fh; print "Installing new slave host list\n"; my $dest = "/etc/bind/martnet.slave.zones.9"; my ($uid, $gid) = (0, 0); if (-e $dest) { ($uid, $gid) = (lstat $dest)[4,5]; } system("install -o $uid -g $gid $path $dest") == 0 or die "install failed: $?"; print "Reloading DNS files\n"; system("/usr/sbin/rndc reload") == 0 or die "rndc reload failed: $?"; } sub all_zones_hash { my (@zl) = @_; my %ret; $ret{$_->{zone}}++ for @zl; return %ret; } sub contains_zone { my ($zone, @zl) = @_; foreach my $i (@zl) { if ($i->{zone} eq $zone->{zone}) { return 1 if ($i->{master} eq $zone->{master}); } } return 0; }