diff --git a/DDNS.pm b/DDNS.pm index 803deba..8581a99 100644 --- a/DDNS.pm +++ b/DDNS.pm @@ -2,7 +2,106 @@ package Martnet::DDNS; use strict; use warnings; +use Net::DNS; +use File::Temp qw/tempfile/; our $VERSION = '0.1'; +sub new { + my $me = shift; + my %opts = @_; + + return bless { + 'keyfile' => '/etc/bind/Kprivate.invalid.+157+14348.key', + %opts + }, $me; +} + +sub _fqdn { + my ($dom) = @_; + + return $dom . "_vhosts.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) = @_; + + _validateOrDie($dom); + my $fqdn = _fqdn($dom); + + 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 -d -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; +} + +# Add a new vhost domain by adding a DDNS record that the slaves will notice. +sub addvhost { + my ($this, $dom, $master) = @_; + + _validateOrDie($dom); + my $fqdn = _fqdn($dom); + + $this->__docmd("update add $fqdn 60 TXT $master"); + $this->cleanup(); +} + +sub delvhost { + my ($this, $dom) = @_; + + _lookupOrDie($dom); + my $fqdn = _fqdn($dom); + + $this->__docmd("update delete $fqdn TXT"); + $this->cleanup(); +} + +sub cleanup { + my ($this) = @_; + # Merge the .jnl file in with the domain file + system("rndc sync -clean private.invalid"); +} + 1; diff --git a/Makefile.PL b/Makefile.PL index d5a8c3a..56b8f3c 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -3,6 +3,9 @@ use ExtUtils::MakeMaker; WriteMakefile( 'NAME' => 'Martnet::DDNS', 'VERSION_FROM' => 'DDNS.pm', - 'PREREQ_PM' => { Net::DNS => 0.8 }, + 'PREREQ_PM' => { Net::DNS => 0.8, + File::Temp => 0.234, + Regexp::Common => 2013031301, + }, 'AUTHOR' => 'Jorj Bauer ', ); diff --git a/bin/addvhost.pl b/bin/addvhost.pl new file mode 100755 index 0000000..e0016dd --- /dev/null +++ b/bin/addvhost.pl @@ -0,0 +1,19 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use Martnet::DDNS; +use Regexp::Common qw/net/; + +my $host = shift || die "No vhost provided"; +my $master = shift || die "No master DNS IP provided"; + +die "Hostname must end in a dot" + unless ($host =~ /^[a-zA-Z0-9\.]+\.$/); + +my $regex = $RE{net}{IPv4} . '|' . $RE{net}{IPv6}; +die "Master must be an IPv4 or IPv6 address" + unless ($master =~ /^$regex$/); + +my $ddns = Martnet::DDNS->new(); +$ddns->addvhost($host, $master); diff --git a/bin/delvhost.pl b/bin/delvhost.pl new file mode 100755 index 0000000..ee804ef --- /dev/null +++ b/bin/delvhost.pl @@ -0,0 +1,14 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use Martnet::DDNS; +use Regexp::Common qw/net/; + +my $host = shift || die "No vhost provided"; + +die "Hostname must end in a dot" + unless ($host =~ /^[a-zA-Z0-9\.]+\.$/); + +my $ddns = Martnet::DDNS->new(); +$ddns->delvhost($host);