#! /usr/bin/perl -w
# watch MX dns entries for a specified domain
# julien vehent - december 2010 - GPLv2

use strict;
use Net::DNS;
use Email::Sender::Simple qw(sendmail);
use Email::Simple;
use Email::Simple::Creator;


if (($ARGV[0] !~ /(?=^.{1,254}$)(^(?:(?!\d+\.)[a-zA-Z0-9_\-]{1,63}\.?)+(?:[a-zA-Z]{2,})$)/)){
    print   "usage: ./dnswatch.pl <domain> <watch timer> <notification email>\n",
            "example: ./dnswatch.pl linuxwall.info 600 julien\@linuxwall.info (verify domain linuxwall.info every 10 minutes and notify julien\@linuxwall.info if needed)\n",
            "\nif no watch timer is specified, the script will loop every 10 seconds undefinitely\n";
    exit 1;
}
my $watch_timer = 10;
if(defined($ARGV[1])){
    if ($ARGV[1] !~ /^\d+$/){
        print "invalid timer value !\n";
        exit 1;
    }
    $watch_timer = $ARGV[1];
    print "Starting DNS Watch with $watch_timer seconds interval\n";
}

my $notification_email = "";
if(defined($ARGV[2])){
    $notification_email = $ARGV[2];
    print "Alarms and notifications will be send to $notification_email\n";
}

my $domain_name = $ARGV[0];
my %domain_ns;
my %domain_mx;

# notification function
sub dns_alarm{
    my @args = @_;
    my $notification_message = "";
    if($args[2] eq "added"){
        $notification_message = "ALARM: entry $args[0] with ip $args[1] has been added to the domain\n";
    }
    elsif($args[2] eq "changed"){
        $notification_message = "ALARM: entry $args[0] with ip $args[1] has been changed to $args[2]\n";
    }
    else{
        print "unknown alarm\n";
        return 1;
    }
    print $notification_message;
    if($notification_email ne ""){
        my $email = Email::Simple->create(
                    header => [
                      To      => $notification_email,
                      From    => $notification_email,
                      Subject => "DNSWATCH alarm for $domain_name",
                    ],
                    body => $notification_message,
        );
        sendmail($email);
    }
    return 0;
}


# first, get the list of nameserver for the domain from local resolver
my $res   = Net::DNS::Resolver->new;
my $query = $res->query($domain_name, "NS");
if ($query) {
    foreach my $fqdn_rr (grep { $_->type eq 'NS' } $query->answer) {
        $query = $res->search($fqdn_rr->nsdname);
        if ($query) {
            foreach my $host_rr ($query->answer) {
                next unless $host_rr->type eq "A";
                print "NS server\t-> fqdn=",$fqdn_rr->nsdname, " ip=",$host_rr->address,"\n";
                $domain_ns{$fqdn_rr->nsdname} = $host_rr->address;
            }
        } else {
            warn "query failed: ", $res->errorstring, "\n";
        }
    }
}
else {
    warn "query failed: ", $res->errorstring, "\n";
}

# first round populates values, do not raised alarm during the first round
my $first_round = 1;

# loop indefinitely and check MX entry of each nameserver every time
while(1){
    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime time;
    print "\n----interation date: $mday/",$mon+1,"/",$year+1900," - $hour h $min m $sec s -----------\n";
    foreach my $domain_ns (keys %domain_ns)
    {
        print "using $domain_ns as nameserver\n";
        $res->nameservers($domain_ns);

        my @mx   = mx($res, $domain_name);
        if (@mx) {
            foreach my $fqdn_rr (@mx) {
                $query = $res->search($fqdn_rr->exchange);
                if ($query) {
                    foreach my $host_rr ($query->answer) {
                        next unless $host_rr->type eq "A";
                        print "MX server\t-> priority=",$fqdn_rr->preference, " fqdn=", $fqdn_rr->exchange, " ip=",$host_rr->address,"\n";
                        if(exists($domain_mx{$fqdn_rr->exchange})){
                            if($domain_mx{$fqdn_rr->exchange} ne $host_rr->address){
                                #dns entry has been changed ! raised alarm
                                dns_alarm($fqdn_rr->exchange,$domain_mx{$fqdn_rr->exchange},$host_rr->address,"changed");
                            }
                        }
                        else{
                            unless($first_round == 1){
                                dns_alarm($fqdn_rr->exchange,$host_rr->address,"added");
                            }
                            else{
                                $domain_mx{$fqdn_rr->exchange} = $host_rr->address;
                            }
                        }
                    }
                } else {
                    warn "query failed: ", $res->errorstring, "\n";
                }
            }
        } else {
            warn "Can't find MX records for $domain_name: ", $res->errorstring, "\n";
        }
        #once first round is finished, changed the value
        $first_round = 0;
    }

    sleep $watch_timer;
}