#!/usr/local/bin/perl

use strict;

use LWP::UserAgent;
use HTTP::Request;
use Mail::Send;
use File::Compare qw(compare_text);
use File::Copy;

my $ua = new LWP::UserAgent ( timeout => 30 );

my $EOF_Match_String = "## EOF ##";
my @Masters = (
               {
                name => "hostingname",
                url => 'http://host.name/path.to/zones.txt',
                notify => [ 'contact1@email.address',
                            'contact2@email.address'
                          ]
               }
              );

my $BaseDir = "/var/named/slave-zones";
my $updated = 0;

foreach my $master (@Masters){
  my $request = new HTTP::Request('GET', $master->{url});
  $request->push_header("Pragma" => "no-cache");
  my $response = $ua->request($request);
  if($response->is_success){
    my $content = $response->content;

    # did we get a complete file?
    unless($content =~ m/$EOF_Match_String/o ) {
      print "$0: Filed to find EOF Match String ($EOF_Match_String) in content!, Incomplete transfer?\n";
      next;
    }

    my $zonefile = "$BaseDir/$master->{name}-zones.txt";
    my $zonefile_old = "$BaseDir/$master->{name}-zones.txt.old";

    if( -w $zonefile and ((not -e $zonefile_old) or -w $zonefile_old) ) {
      unless(move($zonefile, $zonefile_old) ){
        warn "$0: unable to backup $zonefile to $zonefile_old: $!\n"; next;
      }
    }

    if(open(ZONEFILE, ">$zonefile")){
      print ZONEFILE $content;
      close ZONEFILE;

      if( (! -r $zonefile_old) or compare_text($zonefile, $zonefile_old) != 0){
        # they are different
        generate_zoneconf($master, $zonefile);
        send_zonefile_update_mail($master, $zonefile, $zonefile_old);
        $updated++;
      }
    } else {
      warn "$0: unable to open $zonefile for writing: $!\n"; next;
    }

    chown 25,25,$zonefile;
    chown 25,25,$zonefile_old;
  }
}


&restart_named if $updated;

## SUBROUTINES

sub send_zonefile_update_mail {
  my ($master, $zonefile, $zonefile_old) = @_;

  my $diff = `diff -Bu $zonefile_old $zonefile`;
  my $msg_text = "Zonefile Update\nChanges:\n--------\n$diff\n";
  my $msg = new Mail::Send( To => join(",", @{$master->{notify}}),
                            Subject => "Zonefile Update for " . uc($master->{name}) . " zones"
                          );
  my $msg_fh = $msg->open;
  print $msg_fh $msg_text;
  close $msg_fh;
}


sub generate_zoneconf {

  #
  # TODO: Integrate zoneconf generator script
  #

  my ($master, $zonefile) = @_;
  my $dest_dir = "$BaseDir/$master->{name}";
  my $zone_conf = "$BaseDir/$master->{name}.conf";

  my %zones;

  if(open(ZONEFILE, "<$zonefile")){
    while(<ZONEFILE>){
      chomp;
      next if(/^\s+$/);
      next if(/^\s*\#/);

      my $line = trim_whitespace($_);
      next if(length($line) == 0);

      my @parts = split(/\s+/, $line);
      my $domain = lc($parts[0]);
      my @masters = @parts[1..$#parts];

      if(exists $zones{$domain}){
        warn "$0: trying to redefine zone $domain in $zonefile!"; next;
      }
      if(@masters == 0){
        warn "$0: zone $domain has no masters!"; next;
      }

      $zones{$domain} = \@masters;
    }
    close(ZONEFILE);
  } else {
    warn "$0: count not open zonefile $zonefile: $!";
  }

  if(open(ZONECONF, ">$zone_conf")){
    foreach my $domain (sort keys %zones){
      if(domain_ok($domain)){
        print ZONECONF "zone \"$domain\" {\n\ttype slave;\n\tfile \"$dest_dir/$domain\";\n\tmasters {\n ";
        foreach my $master (@{$zones{$domain}}){
          print ZONECONF "\t\t$master;\n";
        }
        print ZONECONF "\t};\n\tallow-update { none; };\n};\n";
      }
    }
    close(ZONECONF);
  } else {
    warn "$0: couldn't open $zone_conf for writing: $!";
  }
  chown 25,25,$zone_conf
}

# simple domain check
sub domain_ok {
  my ($domain) = @_;
  return 1 if($domain =~ /^[a-z0-9\.\-]+$/);
  return undef;
}

# simple ip check
sub masters_ok {
  foreach my $ip (@_){
    return undef unless ($ip =~ /^[0-9]{1,3}\.[0-9]{1.3}\.[0-9]{1.3}\.[0-9]{1.3}$/);
  }
}

sub trim_whitespace {
  foreach (@_){
    s/^\s+//;
    s/\s+$//;
  }
  if(wantarray) {
    return @_;
  } else {
    return join('',@_);
  }
}

sub restart_named {
  `/usr/sbin/rndc reload`;
}
