#!/usr/bin/perl
use 5.004;

eval 'require "./preferences.pl"';

sub DEBUG {
  return unless $ADEBUG;
  my $caller = (caller(1))[3];
  open(BUG, ">>$Vardir/debug.log") or return;
  print BUG 'autorecheck.pl:',$caller,': ',join("",@_),"\n";
  close(BUG);
}

&DEBUG("*** Main Start (pid=",$$,") ***\n",scalar(localtime(time)));

$email = $Email;
if (eval "use Mail::Mailer") {
    $email = "";
}

if ($DEMO) { print "Auto Recheck not available in DEMO mode.\n"; exit; }

($PIDFILE, $INTERVAL, $IP, $PORTMASK, $DISCOVER) = @ARGV;
$INTERVAL = $Autointerval if $INTERVAL > 0;
$restartcmd = "$0 '$PIDFILE' '$INTERVAL' '$IP' '$PORTMASK' '$DISCOVER'";
$IP =~ s/\*$/1-254/ if $DISCOVER;
$INTERVAL *= 60;
$scan_delay = (int($ScanDelay)>0)?' --scan_delay '.int($ScanDelay):'';

unless ($PIDFILE) {
  &DEBUG("Invalid (blank) pidfile; exiting.");
  exit;
}

$lockfile = $Vardir."/autorecheck.lock";
if (-f $lockfile) {
  open(F, "<$lockfile");
  $lockpid = <F>;
  close(F);
  chomp $lockpid;
  if ($lockpid != $$) {
    &DEBUG("Lockfile exists, killing the lockfile process $lockpid");
    kill 'TERM', $lockpid;
    unlink($lockfile);
    &DEBUG("$lockfile deleted");
  }
}

unless ($DISCOVER) {
  unless (-f $Configfile) {
    &DEBUG("Unable to find configfile: $Configfile");
    unlink($PIDFILE);
    unlink($lockfile);
    &DEBUG("$PIDFILE and $lockfile deleted; exiting.");
    exit;
  }
}
&ReadServices();

if ($INTERVAL > 0) {
  fork && exit;  
  close(STDIN); close(STDERR); close(STDOUT);
  use POSIX;
  POSIX::setsid();
  &DEBUG("*** Forked process: ",$$);
}

sub CreateDir {
    my $filename = shift || die "Unable to create a null directory.";
    my @dirs = split(/\//, $filename);
    pop @dirs;     #Filename part
    unshift @dirs; #Root part
    my $dir = "";
    foreach (@dirs) {
      $dir .= "/" . $_;
      mkdir($dir,0775) unless -d $dir;
      die("Unable to create directory: $dir") unless -d $dir;
    }
    return 1;
}

sub ip2file {
    return $Basedir . '/' . join('/', split(/\./, shift));
}

sub SendMail {
    my($status, $ip, $email) = @_;
    return unless ($ip and $email);
    my $mail = new Mail::Mailer;
    eval "use Sys::Hostname; $hostname = hostname();";
    my $from = $myName.' <'.($< || 'root').'@'.($hostname || $ENV{'HOSTNAME'} || 'localhost').'>'; 
    my $type = ($status == $Stat_fail) ? "Failure" : "Recovery";
    $mail->open( { From => $from,
                   To   => $email,
                   Subject => "nPULSE Status Message (Site $type) from $ip" } ) || die $!;
    print $mail "Status message from $from\nPULSE $type reported for $ip\nAt: ",scalar(localtime(time)),"\n";
    close($mail);
}

sub ReadConfigfile {
    local(*C);
    open(C, "<$Configfile") || return;
    my @out = <C>;
    close(C);
    chomp @out;
    return wantarray ? @out : join("\n",@out);
}

sub ReadServices {
    open(S, "<$Servicesfile") || &Error("Unable to open the services file: $Servicesfile");
    while (<S>) {
      $Services{$2} = $1 if m/\s*([^:\s]+)\s*:\s*(.*)$/;
    }
    $Services{'0'} = "alive";
    close(S);
}

sub CheckPortStatus {
    my($ip,$ports) = @_;
    &DEBUG($ip,$ports);
    return unless $ports =~ /^Ports/;
    $ports =~ s/^Ports: //;
    my @currentports;
    foreach (split(/,\s*/, $ports)) { push(@currentports, (split(/\//,$_))[0]); }
    return unless $#currentports >= 0;

    &DEBUG("Reading configfile");
# Configfile: (ipmask portmask)
    my $done = 0;
    my ($ipmask,$portmask,@portlist);
    foreach (&ReadConfigfile()) {
      ($ipmask, $portmask) = split / /;
      foreach (&GenerateSiteList($ipmask)) {
        next unless $_ eq $ip;
        @portlist = &GeneratePortList($portmask);
        $done = 1;
        last;
      }
      last if $done;
    }
    return unless $done;

    my $ev = "";
    my $p;
    &DEBUG("Checking portlist");
    foreach $p (@portlist) {
      $ev .= $p.($Services{$p}?'('.$Services{$p}.')':'').',' unless grep(/^$p$/,@currentports);
    }
    chop $ev;
    $ev = 'Port'.(($ev =~ /,/)?'s':'').' '.$ev.' not responding<br>' if $ev;
    return $ev;
}

sub WriteEvent {
    my($time,$ip,$type,$event) = @_;
    return unless ($time and $ip and $event and ($type >= $EventSeverity));
    return unless ($Eventlogfile and ($MaxEventLogSize > 0));
    my @events;
    if (open(E, "<$Eventlogfile")) {
      @events = <E>;
      close(E);
      chomp @events;
      &DEBUG("Found ",($#events+1)," events");
      @events = splice(@events,$#events-$MaxEventLogSize) if $#events >= $MaxEventLogSize;
    }
    push(@events,join("\t",$time,$ip,$type,$event));
    open(E, ">$Eventlogfile.tmp") or return;
    print E join("\n",@events),"\n";
    close(E);
    rename "$Eventlogfile.tmp", $Eventlogfile;
    &DEBUG("Event logged");
}


sub GenerateSiteList {
    my(@sites) = ();
    my($ip1, $ip2, $ip3, $ip4, @ips);

    my($ipmask) = shift || return;
    @ips = split(/\./, $ipmask);
    return unless $#ips == 3;
    foreach $i (0..3) {
      $ips[$i] =~ s/\-/\.\./g;
      $ips[$i] =~ s/\*/0\.\.255/g;
      $ips[$i] =~ s/^00?(\d)/$1/g;
      $ips[$i] =~ s/(\D)00?(\d)/$1$2/g;
    }
    foreach $ip1 (eval($ips[0])) {
      next unless $ip1 =~ /^\d+$/;
      foreach $ip2 (eval($ips[1])) {
        next unless $ip2 =~ /^\d+$/;
        foreach $ip3 (eval($ips[2])) {
          next unless $ip3 =~ /^\d+$/;
          foreach $ip4 (eval($ips[3])) {
            next unless $ip4 =~ /^\d+$/;
            push(@sites, $ip1.'.'.$ip2.'.'.$ip3.'.'.$ip4);
          }
        }
      }
    }
            
    return wantarray ? @sites : join("\n",@sites);
}

sub GeneratePortList {
    my($portmask) = shift;
    my(@ports);
    if ($portmask =~ /\*/) {
      $portmask = "1..65535";
    } else {
      $portmask =~ s/^[^0-9\., -]//g;
      $portmask =~ s/-/../g;
    }
    foreach (eval($portmask)) {
      push(@ports, $_);
    }
    return sort {$a <=> $b} @ports;
}

sub signal_handler {
    $time_to_die = 1;
    $time_to_restart = 0;
    &DEBUG("TERM or INT signal received.");
}

sub signal_hup {
    $time_to_die = 1;
    $time_to_restart = 1;
    &DEBUG("HUP signal received.");
}

$SIG{INT} = $SIG{TERM} = \&signal_handler;
$SIG{HUP} = \&signal_hup;

open(F, ">$PIDFILE") || exit;
print F $$; close(F);
&DEBUG("$PIDFILE created");

until ($time_to_die) {

  open(F, "<$PIDFILE") || exit;
  $pid = <F>;
  close(F);
  chomp $pid;
  exit unless $pid == $$;

  open(LOCK, ">$lockfile") || exit;
  print LOCK $$; close(LOCK);
  &DEBUG("$lockfile created");

  
  &DEBUG("**************************************\n   ",scalar(localtime(time)),"\n   DISCOVER    = $DISCOVER\n   PORTMASK    = $PORTMASK\n   IP          = $IP\n   email       = $email\n   INTERVAL    = $INTERVAL\n");
  &WriteEvent(scalar(localtime(time)),'Auto',$Stat_ok,'AutoCheck Started');

  undef @cmds; undef @hosts;
  if ($DISCOVER)    { push(@cmds, $Nmapcmd." --min_rtt_timeout $MinRttTimeout -v -oM - -sT '".$IP."' 2>/dev/null | grep \"Host: \""); }
  elsif ($PORTMASK) { push(@cmds, $Nmapcmd."$scan_delay --min_rtt_timeout $MinRttTimeout -v -oM - -sT -p '".$PORTMASK."' '".$IP."' 2>/dev/null | grep \"Host: \""); }
  elsif (!$IP)      { foreach (&ReadConfigfile()) { ($ipmask, $portmask) = split / /; push(@cmds, $Nmapcmd."$scan_delay --min_rtt_timeout $MinRttTimeout -v -oM - ".($portmask?"-sT -p '".$portmask."' ":"-sP".($TruePing?" -PI":""))." '".$ipmask."' 2>/dev/null | grep \"Host: \""); } }
  else              { push(@cmds, $Nmapcmd."$scan_delay --min_rtt_timeout $MinRttTimeout -v -oM - -sT '".$IP."' 2>/dev/null | grep \"Host: \""); }
  
  undef @output;
  foreach $cmd (@cmds) {
    &DEBUG("Running cmd: $cmd");
    @out = `$cmd`;
    chomp @out;
    push(@output, @out);
  }
  &DEBUG("Finished commands");

  if ($DISCOVER) {
    open(F, ">$Configfile") || exit;
    $INTERVAL = 0;
    $email = "";
  } else {
    $tempCurrentfile = $Currentfile.'.tmp';
    open(F, ">$tempCurrentfile") || exit;
  }

  &DEBUG("Found ",($#output+1)," output items");

  foreach (@output) {
    ($host, @rest) = split /\t/;
    $host =~ s/^Host: //;
    ($ip, $name) = split(/ /, $host);
    $name =~ s/\(|\)//g;
    unless ($ip) { $ip = $name; $name = ""; }

    if ($DISCOVER) {
      $ports = $rest[0];
      next unless $ports =~ /^Ports/;
      $ports =~ s/^Ports: //;
      $portmask = "";
      foreach (split(/,\s*/, $ports)) { $portmask .= (split(/\//,$_))[0].","; }
      chop $portmask;
      print F $ip," ",$portmask,"\n";
      &DEBUG("Discovered ",$ip," ",$portmask);

    } else {
      $filename = $Basedir . '/' . join('/', split(/\./, $ip)) . ".hist";
      &CreateDir($filename) unless -f $filename;
      open(G, ">>$filename") || exit;
      print G time,"\tHost: ",$ip," (",$name,")\t",join("\t",@rest),"\n";
      close(G);
      my $ev = &CheckPortStatus($ip,join("\t",@rest));
      &WriteEvent(scalar(localtime(time)),$ip,$Stat_warn,$ev) if $ev;
      print F "Host: ",$ip," (",$name,")\t",join("\t",@rest),"\n";
      if ($email) {
        $efilename = $Basedir . '/' . join('/', split(/\./, $ip)) . ".email";
        if ($rest[0] =~ /^Status: Down/) {
          &DEBUG("Down device: ",$host," -> ",$rest[0]);
          &WriteEvent(scalar(localtime(time)),$ip,$Stat_fail,"Device not responding");
          unless (-f $efilename) {
            &SendMail($Stat_fail, $ip, $email);
            open(E, ">$efilename"); print E time," Down"; close(E);
            &DEBUG("Mail sent to '$email' status = Down");
          }
        } elsif (-f $efilename) {
          &SendMail($Stat_ok, $ip, $email);
          &DEBUG("Mail sent to '$email' status = Up");
          &WriteEvent(scalar(localtime(time)),$ip,$Stat_ok,"Device Recovery");
          unlink($efilename);
        }
      }
    }
  }
  close(F);
  rename $tempCurrentfile, $Currentfile;
  unlink($lockfile);
  &DEBUG("$lockfile deleted");
  &WriteEvent(scalar(localtime(time)),'Auto',$Stat_ok,'AutoCheck Completed');
  
  if ($INTERVAL > 0) { &DEBUG("Sleeping for $INTERVAL seconds"); sleep($INTERVAL); }
  else               { $time_to_die = 1; }
}

unlink($PIDFILE);
unlink($lockfile);
&DEBUG("$PIDFILE and $lockfile deleted");
if ($time_to_restart) {
  &DEBUG("Restarting: ",$restartcmd);
  exec($restartcmd);
}
&DEBUG("Exiting");
exit;
