#!/bin/bash

## CHECK

## Verifie que les programmes fonctionnent et les relance si ils sont en panne
## Doit etre lance par cron. On peut par exemple mettre un /etc/cron.d/check
## qui contient :
## * * * * * root /usr/sbin/check

PATH="/sbin:/bin:/usr/sbin:/usr/bin"

## Configuration dans un fichier exterieur : /etc/check.conf

if [ "`whoami`" != "root" ]; then
  echo "Check lance par un autre utilisateur que root : EXIT"
  exit
fi

RUNLEVEL=`runlevel | cut -f 2 -d ' '`
[ "x$RUNLEVEL" == "x" ] && RUNLEVEL=0
if [ "$RUNLEVEL" == "0" ] || [ "$RUNLEVEL" == "6" ]; then
  echo "Arret ou reboot en cours : on sort"
  exit
fi

if [ "$RUNLEVEL" == "1" ]; then
  echo "Démarrage en cours : on sort"
  exit
fi

if [ -f "/tmp/check-bloque" ]; then
  # Check est bloque par un programme externe : on sort
  exit
fi

touch /tmp/check.heartbeat

function checklog()
{
  # Loggue dans /var/log/check.log tous les arguments passes a la fonction
  # Ne doit pas se retrouver dans le fichier de configuration (sauf pour test
  # de demarrage de l'application check)
  echo "`date +"%b %d %T"` `hostname` check - $@" >> /var/log/check.log
  if [ "$alerteAudio" == "TRUE" ]; then
    rc=`/usr/bin/alerte.php "$@"`
    if [ "$rc" != "OK" ]; then
      echo $rc
    fi
  fi
  if [ "$alerteNotify" == "TRUE" ]; then
    rc=`/usr/bin/notify-send -u critical -t 2000 "$@"`
    if [ "$rc" != "OK" ]; then
      echo $rc
    fi
  fi
  if [ "$alerteAudio" != "TRUE" ] && [ "$alerteNotify" != "TRUE" ]; then
    # Si aucun moyen de communication n'est demandé, on écrit à l'écran
    echo "$@"
  fi
}

function chercheLanceur()
{
  # Cette fonction renvoie le lanceur du programme passé en paramètre
  # On peut définir un tableau supplémentaire dans le fichier de configuration
  # Si le lanceur n'est pas défini dans le tableau, on renvoie le nom du
  # processus
  # Le deuxieme parametre est "start", "stop", "test"
  startup=""
  if [ $BASH_VERSINFO -ge 4 ]; then
    processus=$1
    if [ -z ${lanceur["$processus"]} ]; then
      startup=$processus
    else
      startup=${lanceur["$processus"]}
    fi
  else
    # Le Bash 3 ne supporte pas les tableaux associatifs. On retourne aux liens
    # symboliques
    startup=$1
  fi
  if [ "$2" == "test" ]; then
    if [ -f "/lib/systemd/system/$startup.service" ]; then
      echo "Lanceur Systemd lib"
    elif [ -f "/etc/systemd/system/$startup.service" ]; then
      echo "Lanceur Systemd etc"
    elif [ -f "/etc/init.d/$startup" ]; then
      echo "Lanceur Init"
    else
      echo ""
    fi
  else
    if [ -f "/lib/systemd/system/$startup.service" ]; then
      /bin/systemctl "$2" "$startup.service"
    elif [ -f "/etc/systemd/system/$startup.service" ]; then
      /bin/systemctl "$2" "$startup.service"
    elif [ -f "/etc/init.d/$startup" ]; then
      "/etc/init.d/$startup" "$2"
    else
      checklog "ERREUR : Lanceur $startup introuvable"
    fi
  fi
}

function testps()
{
  lanceur=`chercheLanceur "$1" "test"`
  if [ "$lanceur" != "" ]; then
    if [ `ps -edf | grep -v grep | grep "$*" | wc -l` -eq 0 ]; then
      checklog "Relance de '$*' par $lanceur"
      chercheLanceur $1 stop
      chercheLanceur $1 start
    fi
  fi
} 

function testpsStrict()
{
  # On rend le test plus strict en mettant un slash devant le nom du demon
  lanceur=`chercheLanceur "$1" "test"`
  if [ "$lanceur" != "" ]; then
    if [ `ps -edf | grep -v grep | grep "/$*" | wc -l` -eq 0 ]; then
      checklog "Relance de '$*' par $lanceur"
      chercheLanceur $1 stop
      chercheLanceur $1 start
    fi
  fi
} 

function testiptables()
{
  # Verifie que iptables est bien ferme
  if [ ! -e "/etc/init.d/ipt4" ] && [ ! -e "/etc/init.d/ipt" ]; then
    checklog "ERREUR : /etc/init.d/ipt4 ou /etc/init.d/ipt introuvable (testiptables) "
  else
    if [ `/sbin/iptables -L -n | wc -l` -lt 10 ]; then
      checklog "Relance de IPTables IPv4"
      if [ -e "/etc/init.d/ipt4" ]; then
        /etc/init.d/ipt4 start 
      else
        /etc/init.d/ipt start 
      fi
    fi
  fi
}

function testip6tables()
{
  # Verifie que ip6tables est bien ferme
  if [ ! -e "/etc/init.d/ipt6" ]; then
    checklog "ERREUR : /etc/init.d/ipt6 introuvable (testip6tables) "
  else
    if [ `/sbin/ip6tables -L -n | wc -l` -lt 10 ]; then
      checklog "Relance de IPTables IPv6"
      /etc/init.d/ipt6 start 
    fi
  fi
}

function testDNS()
{
  # Verifie si le DNS a change pour l'adresse passe en argument
  NEW="`host -t A $1 2>/dev/null`"
  if [ "$?" != "0" ]; then return; fi
  if [ "$NEW" != "`cat /var/lib/check 2>&1 /dev/null`" ]; then
    checklog "Changement d'adresse pour $1 : `host -t A $1`"
    echo "ALERTE : Nouvelle adresse DNS pour $1 :"
    echo " Ancienne : `cat /var/lib/check 2>&1 /dev/null`"
    echo " Nouvelle : $NEW"
    host -t A $1 > /var/lib/check
  fi
}

function testdf()
{
  # Teste tous les disques et renvoie si le disque est plein a plus de 84%
  # On peut aussi tester les disques unitairement : 
  # testdf /mnt 
  # On peut aussi tester unitairement avec une limite differente de 84%
  # testdf /mnt 90
  if [ -z "$2" ]; then
    limite=84
  else
    limite=$2
  fi
  if [ ! -z "$1" ]; then
    fs="$1"
  fi
  IFS="
"
  for ligne in `LANGUAGE=C df -P -l -x tmpfs -x devtmpfs -x squashfs -x efivarfs $fs | sed '/Filesystem/d'`; do
    fs_space=`echo $ligne | awk '{print $5}'| sed 's/\%//'`
    filesys=`echo $ligne | awk '{print $6}'`
    if [ $fs_space -gt $limite ]; then
      checklog "Systeme de fichier $filesys plein a $fs_space %"
    fi
  done
}

function testPort()
{
  # Verifie si un port est actif
  # Attend 3 parametres : l'adresse IP a tester, le port a tester et le
  # fichier lanceur à utiliser
  lanceur=`chercheLanceur "$3" "test"`
  if [ "$lanceur" != "" ]; then
    RESULTAT=`echo "QUIT" | nc -w1 $1 $2 2>&1`
    if [ "$?" != "0" ] || [ "$RESULTAT" == "" ]; then
      checklog "Relance de $3 car injoignable par reseau"
      echo "ALERTE : La machine `hostname` ne peut plus se connecter a "
      echo "$1 port $2 : $RESULTAT"
      echo "Relance de $3"
      chercheLanceur $3 stop
      chercheLanceur $3 start
    fi
  fi
}

function testPortNoBann()
{
  # Verifie si un port est actif
  # Attend 3 parametres : l'adresse IP a tester, le port a tester et le 
  # fichier lanceur à utiliser
  # Le port sera actif même si il ne renvoie aucun caractère
  lanceur=`chercheLanceur "$3" "test"`
  if [ "$lanceur" != "" ]; then
    RESULTAT=`echo "QUIT" | nc -w1 $1 $2 2>&1`
    if [ "$?" != "0" ]; then
      checklog "Relance de $3 car injoignable par reseau"
      echo "ALERTE : La machine `hostname` ne peut plus se connecter a "
      echo "$1 port $2 : $RESULTAT"
      echo "Relance de $3"
      chercheLanceur $3 stop
      chercheLanceur $3 start
    fi
  fi
}

function testDDClient()
{
  # Verifie si ddclient est toujours actif. Pour cela, le processus doit avoir 
  # une ligne 'ddclient - sleeping for'. Si ce n'est pas le cas (test en cours)
  # on attend 15 s et on refait le test. Si le test est toujours negatif on 
  # relance.
  lanceur=`chercheLanceur "ddclient" "test"`
  if [ "$lanceur" != "" ]; then
    LIGNE1=`ps -edf | grep -v grep | egrep "ddclient "`
    LIGNE2=`echo $LIGNE1 | egrep 'ddclient - sleeping for|ddclient - (connecting to|reading from) checkip.dyndns.com port 80'`
    if [ $? -ne 0 ]; then
  checklog " DDCLIENT ERREUR 1 (LIGNE1=$LIGNE1///LIGNE2=$LIGNE2)"
  ps -edf >> /var/log/check.log
      checklog "ddclient non lance ou pas en mode sleep : relance"
      echo "Relance de ddclient"
      chercheLanceur ddclient stop
      chercheLanceur ddclient start
    fi
  fi
}

function testMount()
{
  # Verifie si le point de montage passe en argument est bien monte et en RW
  # Si pas monte, on le monte. Si en RO, on le passe en RW
  PNT=$1
  MNT=`egrep -v "^{none|rootfs|binfmt_misc|gvfs-fuse-daemon} " /proc/mounts | cut -d " " -f 2 | egrep "^$PNT$"`
  if [ "$MNT" == "" ]; then
    checklog "Disque $PNT non monte : remonte"
    mount $PNT
  fi
  ST=`egrep -v "^{none|rootfs|binfmt_misc|gvfs-fuse-daemon} " /proc/mounts | cut -d " " -f 2,4| egrep "^$PNT " | cut -d " " -f 2 | cut -d "," -f 1`

  if [ "$ST" == "ro" ]; then
    checklog "Disque $PNT en RO : Passage en RW"
    mount -o remount,rw $PNT
  fi
  # The drive is again in RO : umount, fsck, and remount
  ST=`egrep -v "^{none|rootfs|binfmt_misc|gvfs-fuse-daemon} " /proc/mounts | cut -d " " -f 2,4| egrep "^$PNT " | cut -d " " -f 2 | cut -d "," -f 1`
  TYPE=`egrep -v "^{none|rootfs|binfmt_misc|gvfs-fuse-daemon} " /proc/mounts | cut -d " " -f 2,3,4 | egrep "^$PNT " | cut -d " " -f 2`
  DISK=`egrep -v "^{none|rootfs|binfmt_misc|gvfs-fuse-daemon} " /proc/mounts | cut -d " " -f 1,2 | egrep " $PNT\$" | cut -d " " -f 1`
  if [ "$ST" == "ro" ]; then
    checklog "Disque $PNT toujours en RO : Démonte, FSCK et remonte"
    umount $DISK
    fsck.$TYPE $DISK
    mount $DISK
  fi
}

function testRepertoireVide()
{
  # Verifie que le repertoire passe en argument est bien vide
  REP=$1
  if [ ! -d "$REP" ]; then
    echo "ERREUR : '$REP' n'est pas un répertoire"
  else
    if [ "`ls -1 $REP`" != "" ]; then
      checklog "Repertoire $1 non vide"
      echo "Repertoire $1 non vide :"
      ls -1 $REP/*
    fi
  fi
}

function testBatterie()
{
  # Cette fonction teste la batterie de l'ordinateur si elle est dispo et envoie
  # une erreur si le niveau tombe sous le seuil defini pour la batterie
  # Recherche du seuil :
  SEUIL=`grep "design capacity warning:" /proc/acpi/battery/BAT1/info 2>/dev/null | cut -d " " -f 4`
  # Recherche de la capacite restante :
  CAPA=`grep "remaining capacity:" /proc/acpi/battery/BAT1/state 2>/dev/null | cut -d " " -f 8`
  if [ -n $SEUIL ] && [ -n $CAPA ] && [ $CAPA -lt $SEUIL ]; then
    checklog "Batterie faible ($CAPA/$SEUIL)"
  fi 
}

function testNTP()
{
  # Cette fonction teste si NTP est bien synchronisé avec un serveur de temps
  /usr/bin/ntpq -n -c peers  | egrep -q '^\*' || checklog "Serveur de temps désynchronisé ou injoignable"
}

function testZombie()
{
  # Cette fonction teste les zombies qui trainent sur le système. Elle les tue
  for ligne in `ps  -eo pid,ppid,state,cmd | grep -v grep | grep ' Z '`; do
    echo "Zombie : $ligne"
    IFS=" "
    for proc in `echo $ligne | awk '{print $1" "$2}'`; do
      echo "   PROCESSUS TUE :$proc"
      kill -9 $proc 2>/dev/null
    done
  done
}

function testFichierPresent
{
  # Cette fonction teste si le fichier passé en premier argument est disponible
  # Si ce n'est pas le cas, relance le programme passé en deuxième paramètre
  lanceur=`chercheLanceur "$2" "test"`
  if [ "$lanceur" != "" ]; then
    if [ ! -e "$1" ]; then
      checklog "ERREUR : Fichier $1 absent : relance $2"
      chercheLanceur $2 stop
      chercheLanceur $2 start
    fi
  fi
}

function testFail2BanActive
{
  ## Cette fonction verifie si des chaines f2b- existent bien dans iptables
  ## Si ce n'est pas le cas, essaie de redémarrer fail2ban
  lanceur=`chercheLanceur "fail2ban" "test"`
  if [ "$lanceur" != "" ]; then
    /sbin/iptables -L -nv |egrep -q "^Chain f(ail)?2b(an)?-" || (
      checklog "ERREUR : fail2ban absent dans iptables : redémarrage";
      chercheLanceur fail2ban stop ;
      chercheLanceur fail2ban start )
  fi
}

if [ ! -f "/etc/check.conf" ]; then
  echo "Le fichier /etc/check.conf n'existe pas : EXIT"
  exit
fi

# Lanceur est le tableau associatif des lanceurs 'processus'=>'lanceur' dans
# /etc/init.d ou systemd
# On peut définir des lanceurs dans la fonction chercheLanceur, ou dans le
# fichier de configuration /etc/check.conf. Si on redéfinit un lanceur dans
# /etc/check.conf, c'est lui qui est prioritaire par rapport à ceux codés en dur
if [ $BASH_VERSINFO -ge 4 ]; then
  declare -A lanceur
  lanceur[bind]="bind9"
  lanceur[dhcpd]="isc-dhcp-server"
  lanceur[clamd]="clamav-daemon"
  lanceur[freshclam]="clamav-freshclam"
  lanceur[master]="postfix"
  lanceur[munin-nod]="munin-node"
  lanceur[mysqld]="mysql"
  lanceur[ntpd]="ntp"
  lanceur[rpc.statd]="nfs-common"
  lanceur[rpc.idmapd]="nfs-common"
  lanceur[rsyslogd]="rsyslog"
  lanceur[smartd]="smartmontools"
  lanceur[spamd]="spamassassin"
  lanceur[sshd]="ssh"
fi

. /etc/check.conf

if [ "$alerteNotify" == "TRUE" ] && [ ! -f "/usr/bin/notify-send" ]; then
  echo "Check : alerteNotify demande mais /usr/bin/notify-send non disponible"
  echo "        apt-get install libnotify-bin"
  exit
fi

if [ "$alerteAudio" == "TRUE" ] && [ ! -f "/usr/bin/alerte.php" ]; then
  echo "Check : alerte audio demandée mais /usr/bin/alerte.php non disponible"
  echo "        apt-get install cric-alerte"
  exit
fi
