#!/usr/bin/perl -w
# vim:et:ts=2:sw=2:

#*********************************************************************
#
# fai-monitor -- monitor daemon which collects client status info
#
# This script is part of FAI (Fully Automatic Installation)
# (c) 2003-2012 by Thomas Lange, lange@informatik.uni-koeln.de
# Universitaet zu Koeln
#
#*********************************************************************

use strict;
use Socket;
use Getopt::Std;

$| = 1;
my ($port, $timeout, $daemon, $timestamp);
my $pidfile = '/var/run/fai-monitor.pid';
my $logfile = '-';
my $daemonlogfile = '/var/log/fai-monitor.log';
my $useip;

our ($opt_b,$opt_h,$opt_p,$opt_l,$opt_t,$opt_d,$opt_P,$opt_T,$opt_i);
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
sub logline(@) {
  open(LOGFILE, $logfile) or return 0;
  print LOGFILE (scalar localtime(), ' - ') if ($timestamp);
  print LOGFILE @_ or return 0;
  close(LOGFILE);
  return 1;
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
sub signal_die(@) {
  logline(@_);
  unlink($pidfile);
  exit(1);
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
sub signal_warn(@) {
  logline(@_) or die "log: $!";
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
sub signal_deadly(@) {
  # Use the die-handler
  signal_die('Caught deadly signal ' . shift() . "\n");
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
sub server_init() {
  logline("FAI monitoring daemon starting..\n") or die "log: $!";

  # Init signals
  $SIG{INT} = \&signal_deadly;
  $SIG{QUIT} = \&signal_deadly;
  $SIG{TERM} = \&signal_deadly;
  $SIG{__DIE__} = \&signal_die;
  $SIG{__WARN__} = \&signal_warn;
  # HUP is usually used to reopen log files. This is not a problem
  # in this design.
  $SIG{HUP} = 'IGNORE';

  if ($daemon) {
    if (-e $pidfile) {
    # Pid file already exists. Check if it's a valid pid.
      open(PIDFILE, '<', "$pidfile") or die "open $pidfile: $!";
      my $pid = <PIDFILE>;
      chomp($pid);
      if ($pid ne '') {
      # Kill -0 exits with value 0 if pid is alive
        system("kill -0 $pid 2> /dev/null");
        if ($? == 0) {
          logline("Pidfile $pidfile exists and contains an existing pid. Exiting.\n");
          exit(1);
        }
      }
      close(PIDFILE);
    }
    eval "Proc::Daemon::Init";
    umask 022;

    open(PIDFILE, '>', "$pidfile") or die "open $pidfile: $!";
    print PIDFILE $$ or die "print $pidfile: $!";
    close(PIDFILE);
  }

  # Listen
  my $proto = getprotobyname('tcp');
  socket(SERVER, PF_INET, SOCK_STREAM, $proto) or die "socket: $!";
  setsockopt(SERVER, SOL_SOCKET, SO_REUSEADDR, 1) or die "setsockopt: $!";

  my $paddr = sockaddr_in($port, INADDR_ANY);

  bind(SERVER, $paddr) or die "bind: $!";
  listen(SERVER, SOMAXCONN) or die "listen: $!";
  logline("FAI monitoring daemon started on port $port with pid $$\n") or die "log: $!";
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
sub big_loop() {

  # accept a connection, print message received and close
  my ($client_addr);
  while ($client_addr = accept(CLIENT, SERVER)) {
    my ($port, $iaddr) = sockaddr_in($client_addr);
    my $ip = inet_ntoa($iaddr);

    my $inp = '';

    eval {
      local $SIG{__DIE__};
      local $SIG{__WARN__};
      local $SIG{'ALRM'} = sub { die("Timeout"); };

      alarm($timeout);
      $inp = <CLIENT>;
      alarm(0);
    };

    close CLIENT;

    if (!defined($inp) || $inp eq '') {
      # Client did not send anything, or alarm went off
      logline("$ip:$port: No data or timeout.\n") or die "log: $!";
      next;
    }

    if ($inp =~ /^([^\s;]+)\s+TASKEND install 0/ && $opt_b) {
      my $cname = $1;
      if ($useip) {
        $cname = $ip;
      }
      system('fai-chboot', '-d', $cname);
      logline("$ip:$port: Disabling pxelinux configuration for $cname\n") or die "log: $!";
    }
    logline("$inp") or die "log: $!";
  }
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
sub usage {

  print << "EOF";
fai-monitor, FAI monitor daemon.

    Copyright (C) 2003-2012 by Thomas Lange

Usage: fai-monitor [OPTIONS]

    -b                  Call fai-chboot to change boot parameter.
    -p PORT             Set port to listen to. Default is 4711.
    -l FILE             Logfile. Default is standard out and
                        '$daemonlogfile' in daemon mode.
    -t TIMEOUT          Timeout for bad clients. 0 to disable.
    -d                  Daemon mode.
    -P FILE             PID-file. Default is '$pidfile'.
                        Used only if starting in daemon mode.
    -T                  Print timestamps in the log.
    -i                  When using -b: send IP of client to fai-choot
                        instead of the hostname the host reports.

EOF
  exit 0;
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# check for old script name
my $callname;
$callname = $0;
$callname =~ s#.+/##;
if ($callname eq 'faimond') {
  warn "WARNING: faimond is now called fai-monitor.\n";
  warn "In the future only fai-monitor will be available.\n";
}

getopts('bhTp:l:t:dP:i') || usage;
$opt_h && usage;
$port = $opt_p || 4711;
$timeout = $opt_t || 5;
$daemon = $opt_d || 0;
$timestamp = $opt_T || 0;
$useip = $opt_i || 0;

if (defined($opt_P)) {
  $pidfile = $opt_P;
}

if (defined($opt_d)) {
  (eval "require Proc::Daemon") or
    die "Daemon mode not available, Proc::Daemon not found. Please install libproc-daemon-perl\n";
  # If in daemon mode, use standard daemon log file
  $logfile = $daemonlogfile;
}

if (defined($opt_l)) {
  $logfile = $opt_l;
}

# Constuct a $logfile that open can take as an argument
if ($logfile eq '-') {
  $logfile = ">&STDOUT";
}
else {
  $logfile = ">>$logfile";
}


server_init;
big_loop;
