package Martnet::DDNS; use strict; use warnings; use Net::DNS; use File::Temp qw/tempfile/; our $VERSION = '0.2'; sub new { my $me = shift; my %opts = @_; return bless { 'keyfile' => '/etc/bind/Kprivate.invalid.+157+14348.key', %opts }, $me; } sub _validateTypeOrDie { my ($t) = @_; die "Invalid type" unless ($t =~ /^_(vhosts|hostedservers|pureslave|custom)$/); } sub _fqdn { my ($dom, $type) = @_; $type ||= '_vhosts'; _validateTypeOrDie($type); return $dom . "$type.private.invalid."; } sub _validateOrDie { my ($dom) = @_; die "No domain provided" unless $dom; die "Invalid domain name (must end in a dot)" unless ($dom =~ /^[a-zA-Z0-9\.\-\_]+\.$/); } sub _lookupOrDie { my ($dom, $type) = @_; _validateOrDie($dom); my $fqdn = _fqdn($dom, $type); my $res = Net::DNS::Resolver->new; my $query = $res->query($fqdn, "TXT"); if ($query) { foreach my $rr (grep { $_->type eq 'TXT' } $query->answer) { # We found a record; that's all that I care about. #print $rr->nsdname, "\n"; return 1; } } else { die "query failed: ", $res->errorstring, "\n"; } die "Failed to find existing DNS record for $fqdn"; } sub __docmd { my ($this, $cmd) = @_; my ($tmpfh, $filename) = tempfile(); close $tmpfh; my $fh; open($fh, "|nsupdate -k $this->{keyfile} > $filename") || 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>) { print; } close $fh; unlink $filename; } sub _gethosts { my ($this, $type) = @_; unless (!defined($type)) { _validateTypeOrDie($type); } my $fh; open($fh, "dig -t AXFR \@127.0.0.1 private.invalid. |") || die "Can't open dig: $!"; my @vh; while (<$fh>) { if ($type) { if (/^(\S+)\.$type\.private\.invalid\.\s+\d+\s+IN\s+TXT\s+\"(.+)\"$/) { push (@vh, { zone => $1, type => $type, master => $2 }); } } else { # Querying everything if (/^(\S+)\.(\S+)\.private\.invalid\.\s+\d+\s+IN\s+TXT\s+\"(.+)\"$/) { push (@vh, { zone => $1, type => $2, master => $3 }); } } } return @vh; } sub add { my ($this, $dom, $master, $type) = @_; _validateOrDie($dom); my $fqdn = _fqdn($dom, $type); $this->__docmd("update add $fqdn 60 TXT $master"); $this->cleanup(); } sub del { my ($this, $dom, $type) = @_; _lookupOrDie($dom, $type); my $fqdn = _fqdn($dom, $type); $this->__docmd("update delete $fqdn TXT"); $this->cleanup(); } sub get { my ($this, $type) = @_; return $this->_gethosts($type); } sub cleanup { my ($this) = @_; # Merge the .jnl file in with the domain file system("rndc sync -clean private.invalid"); system("rndc reload"); } 1;