#!/usr/bin/env perl

# NAME:
#	validate_soa - validate that all NS agree on SOA
#
# SYNOPSIS:
#	validate_soa [-dv][-m "primary"] "zone" ["ns"] ...
#
# DESCRIPTION:
#	Starting with "primary" if specified we get the SOA and NS list
#	for "zone" and proceed to get the SOA and NS list from each
#	listed nameserver to check that they match and that each "ns"
#	is included in the list.
#
#	Options:
#
#	-d	Debug. Show the output from each NS.
#
#	-v	Verbose.  Show the SOA record from each NS.
#
#	-m "primary"
#		Query "primary" to get the canonical NS list, otherwise
#		we simply query our local nameserver to find the
#		origin of the "zone" and assume that that is the "primary".
#
# AUTHOR:
#	Simon J. Gerraty <sjg@crufty.net>

$RCSid='$Id: validate_soa,v 1.12 2022/10/10 01:03:11 sjg Exp $'; #'emacs

# RCSid:
#	$Id: validate_soa,v 1.12 2022/10/10 01:03:11 sjg Exp $
#
#	@(#) Copyright (c) 1997-2022 Simon J. Gerraty.
#
#	This file is provided in the hope that it will
#	be of use.  There is absolutely NO WARRANTY.
#	Permission to copy, redistribute or otherwise
#	use this file is hereby granted provided that 
#	the above copyright notice and this notice are
#	left intact. 
#      
#	Please send copies of changes and bug-fixes to:
#	sjg@crufty.net
#

if ($0 =~ m,^(.*)/([^/]+)$,) {
  $Mydir = $1;
  $Myname = $2;
} else {
  $Mydir = '.';
  $Myname = $0;
}

require "$Mydir/funcs.pl";
require "$Mydir/getIP.pl";
require 'getopts.pl';

&Getopts('dvm:');
&main;
exit 0;

sub main {
  ($zone,@secondarys) = @ARGV;

  $ok = 1;

  %Checked = ();
  
  %SOA = &getSOA($zone,$opt_m);
  $Checked{$SOA{'Address'}} = 1;
  
  if ($opt_m eq '') {
    $primary = $SOA{'origin'};
    
    if (($primaryIP = $SOA{'NS'}{$primary}) eq '') {
      &err(1, "$primary: not in NS list\n");
    }
    &show($opt_d, "Querying $primary/$primaryIP\n");
    %SOA = &getSOA($zone,$primaryIP);
  }
  &show($opt_v, "NS=%s ZONE=$zone SOA=%s\n", $SOA{'ID'}, $SOA{'SOA'});
  %Master = %SOA;
  foreach $secondary (@secondarys) {
    if ($Master{'NSA'}{$secondary} eq '' && $Master{'NS'}{$secondary} eq '') {
      &warn("$Master{'ID'}: missing NS: $secondary\n");
      $ok = 0;
    }
  }
  $MasterNS = &nsList($Master{'NSA'});
  if ($ok || $opt_d ne '') {
    foreach $server (split(/,/, $MasterNS)) {
      next if ($Checked{$server} ne '');
      %SOA = &getSOA($zone,$server);
      $Checked{$SOA{'Address'}} = 1;
      if ($SOA{'SOA'} ne $Master{'SOA'}) {
	&warn("$SOA{'ID'}: SOA: $SOA{'SOA'} != $Master{'SOA'}\n");
	$ok = 0;
	next if ($opt_d eq '');
      }
      $nsl = &nsList($SOA{'NSA'});
      if ($nsl ne $MasterNS) {
	&warn("$SOA{'ID'}: NS list: $nsl != $MasterNS\n");
	$ok = 0;
	next if ($opt_d eq '');
      }
      &show($opt_v, "NS=%s ZONE=$zone SOA=%s\n", $SOA{'ID'}, $SOA{'SOA'});
    }
  }
  exit 1 if (!$ok);
}

sub nsList {
  local(*a) = @_;

  join(',', (sort keys %a));
}


sub getList {
  local(*a) = @_;

  (keys %a);
}

sub getSOA {
  local($zone,$server) = @_;
  local(%SOA) = ();
  local($x);
  
  $zone =~ s/\.+$//;
  
  if (open(I, "nslookup -type=soa $zone. $server |")) {
    while (<I>) {
      &show($opt_d, "%s", $_);
      next unless(m/^\s*(.*)(:|\s=)\s+(.*)/o);
      $key = $1;
      $val = $3;
      $key =~ s/\s+/ /g;
      $val =~ s/\s\(.*//;
      
      if ($key =~ m/\snameserver/) {
	$SOA{'NS'}{$val} = 1;
	next;
      } elsif ($key =~ m/^(\S+)\sinternet\saddress/) {
	$key = $1;
	if (($x = $SOA{'NS'}{$key}) eq '1') {
	  $SOA{'NS'}{$key} = $val;
	  $SOA{'NSA'}{$val} = $key;
	} elsif ($x ne '') {
	  $SOA{'NS'}{$key} .= ",$val";
	  $SOA{'NSA'}{$val} = $key;
	}
      } elsif ($key =~ m/(Server|Address|origin|mail|serial|refresh|retry|expire|ttl)/o) {
	$SOA{$1} = $val;
      }
    }
    close I;
    foreach $val (&getList($SOA{'NS'})) {
      next if ($SOA{'NSA'}{$val} ne '');
      &show($opt_d, "Looking up NS $val\n");
      %X = &getIP($val);
      $SOA{'NSA'}{$val} = $X{'Address'};
    }
    $SOA{'ID'} = "$SOA{'Server'}/$SOA{'Address'}";
    $SOA{'SOA'} = "$SOA{'origin'},$SOA{'mail'},$SOA{'serial'},$SOA{'refresh'},$SOA{'retry'},$SOA{'expire'},$SOA{'ttl'}";
  }
  %SOA;
}

			 
