chage-omfg

#!/bin/bash

## -------------------------------------------------------------------------------------------------------
## check_password_age.sh
## -------------------------------------------------------------------------------------------------------
##
## Programmbeschreibung:
## Skript um das Alter des Passworts von root zu ermitteln und eine E-Mail abzusetzen falls das Alter
## einen konfigurierten Wert überschreitet.

## -------------------------------------------------------------------------------------------------------
## Initialisierung
## -------------------------------------------------------------------------------------------------------

# Debugging Option
# set -x

# /etc/profile sourcen
source /etc/profile

# Systemsprache explizit setzen um zu erzwingen, dass die Ausgabe auf Englisch erfolgt.
export LANG="en_US.UTF-8"


## -------------------------------------------------------------------------------------------------------
## Konfiguration des Skripts
## -------------------------------------------------------------------------------------------------------

MAXPASSWORDAGE=279;                                     # Maximales Alter des root-Passworts in Tagen

# Pfad zur Userliste
USERLIST="";                                            # Nicht editieren
PATHTOUSERLIST="/root/scripts/chage/data/";             # Pfad zur Datei, die die User zur Ueberpruefung beinhaltet
USERLISTFILE="users.txt";                               # Dateiname der Datei, die die Userliste enthaelt

# Mailversand
MAILSENDER="email@domain.com";                          # Absenderadresse
MAILRECEIVER="email@localhost";                         # Empfaengeradresse
SENDMAILIFNOOP="yes";                                   # Soll eine Mail gesendet werden wenn kein Fehler auftrat (yes|no)
SENDMAILIFERROR="yes";                                  # Soll eine Mail gesendet werden wenn ein Fehler aufgetreten ist (yes|no)
SENDMAILIFUPDATE="yes";                                 # Soll eine Mail gesendet werden wenn Updates von Passwoertern ausstehen (yes|no)

# Logging
MYNAME=$(basename $0);                                  # Dateiname des Scripts (nicht editieren)
LOGFILEPATH="/root/scripts/chage/log/";                 # Pfad zum skripteigenen Logfile
LOGFILENAME=$(hostname -f)"-checkpasswordage.log";      # Dateiname des skripteigenen Logfile
LOGFILE=${LOGFILEPATH}${LOGFILENAME};                   # Nicht editieren
LOGVERBOSITY="3";                                       # Standard-Loglevel [0 - 4]
SILENT_LVL="0";                                         # Definiert Silent (nicht editieren)
ERR_LVL="1";                                            # Definiert Loglevel von Error (nicht editieren)
WRN_LVL="2";                                            # Definiert Loglevel von Warn (nicht editieren)
INF_LVL="3";                                            # Definiert Loglevel von Info (nicht editieren)
DBG_LVL="4";                                            # Definiert Loglevel von Debug (nicht editieren)


## -------------------------------------------------------------------------------------------------------
## Strings
## -------------------------------------------------------------------------------------------------------

WORKLIST="";                                            # Nicht editieren
UPDATELIST="";                                          # Nicht editieren
ERRORSTATUSSTRING="";                                   # Nicht editieren
MYHOSTNAME=$(hostname -f);                              # Der Hostname des Systems auf dem das Skript laeuft (nicht editieren)
SUBJECTSTATENOOP="[NOOP]";                              # Mail-Subject-Indikator fuer einen erfolgreichen Lauf ohne Handlungsbedarf.
SUBJECTSTATEERROR="[ERROR]";                            # Mail-Subject-Indikator fuer einen Lauf bei dem Fehler aufgetreten sind.
SUBJECTSTATEUPDATE="[PENDING-PASSWORD-UPDATE]";         # Mail-Subject-Indikator fuer Useraccounts die einen Passwortwechsel benötigen.
MAILBODY="";


## -------------------------------------------------------------------------------------------------------
## Funktionen
## -------------------------------------------------------------------------------------------------------

# Ein formatiertes Datum bauen
function makeadate ()
{
        local l_FORMATTEDDATE="";
        l_FORMATTEDDATE=$(date +"%Y-%m-%d %H:%M:%S");
        echo "$l_FORMATTEDDATE";
}

# Die Userliste aus einer Datei auslesen und den Inhalt in ein Array befuellen, womit dann die WORKLIST befuellt wird
function readUserList ()
{
        local l_FULLPATHTOUSERLIST="";
        l_FULLPATHTOUSERLIST="${PATHTOUSERLIST}${USERLISTFILE}";
        USERLIST=$(awk '{print $1}' $l_FULLPATHTOUSERLIST);
}


# Minimales Logging und Error-Handling
function notify () { log $SILENT_LVL "NOTE: $1"; }      # Wird immer ausgegeben
function error ()
{
        log $ERR_LVL "ERROR: $1";
}
function warn () { log $WRN_LVL "WARNING: $1"; }
function inf () { log $INF_LVL "INFO: $1"; }            # "info" ist ein Shell-Kommando
function debug () { log $DBG_LVL "DEBUG: $1"; }
function log ()
{
        local l_LOGDATE="";
        l_LOGDATE=$(makeadate);
        if [ $LOGVERBOSITY -ge $1 ]
        then
                # Ins Logfile schreiben
                echo -e "${l_LOGDATE} - $2" >> $LOGFILE;

                # An das Syslog uebergeben
                logger -t "${MYNAME}" "${2}";

                #Den Error-String erweitern
                if [ $1 = $ERR_LVL ]
                then
                        ERRORSTATUSSTRING+="  $2\n";
                fi
        fi

        # Die Meldung an STDOUT ausgeben
        echo "$2";
}

# Funktion um zu testen ob es den User gibt.
# Existierende User werden der WORKLIST hinzugefuegt.
# Nicht existierende User oder mehrfach vorkommende User loesen einen Fehler aus.
function initialtest ()
{
        # debug "initialtest (): Teste User: ${1}";

        local l_TESTCOUNT=$(grep -c "^${1}:" /etc/shadow);

        # debug "initialtest (): TESTCOUNT: ${l_TESTCOUNT}";

        if [ ${l_TESTCOUNT} -ne 1 ]
        then
                if [ ${l_TESTCOUNT} = 0 ]
                then
                        error "initialtest (): User ${1} existiert nicht.";
                else
                        error "initialtest (): User ${1} kommt ${l_TESTCOUNT} mal vor.";
                fi
        else
                # debug "initialtest (): User ${1} gefunden und ${l_TESTCOUNT} mal vorhanden.";
                inf "initialtest (): User ${1} existiert.";
                inf "Fuege User ${1} der WORKLIST hinzu.";
                WORKLIST+=" "${i};
        fi
}


# Funktion um das Alter der Userpasswoerter zu ueberpruefen
function checkPasswordAge ()
{
        # debug "checkPasswordAge (): Ermittle Passwortalter von User: ${1}";

        # init
        local l_ITEMARRAY="";
        local l_PARSESTRING="";
        local l_COUNTERVAR=0;
        local l_ITEMARRAY="";
        local l_LASTCHANGEOFPASSWORD="";
        local l_TODAY="";
        local l_DIFFERENCE="";

        # Den Eintrag fuer den uebergebenen USER aus der /etc/shadow auslesen
        l_PARSESTRING=$(grep "^$1:" /etc/shadow);
        # debug "checkPasswordAge (): ermittelter String: ${l_PARSESTRING}";

        # Den String von Asterisks saeubern
        l_PARSESTRING=$(echo "${l_PARSESTRING}" | sed 's/\*/ASTERISK/g');
        # debug "l_PARSESTRING nach SED: ${l_PARSESTRING}";

        # Die Textzeile aus /etc/shadow fuer das Array aufsplitten
        l_ARRAYPREPARE=$(echo "${l_PARSESTRING}" | tr ":" "\n");
        # debug "l_ARRAYPREPARE: ${l_ARRAYPREPARE}";

        # Das Array befuellen
                # 0 = Username
                # 1 = Passwort-hash
                # 2 = Alter des Passworts in Tagen seit 01.01.1970
        l_ITEMARRAY=($l_ARRAYPREPARE);
        # debug "[0]=${l_ITEMARRAY[0]}";
        # debug "[1]=${l_ITEMARRAY[1]}";
        # debug "[2]=${l_ITEMARRAY[2]}";

        # Timestamp der letzten Passwortaenderung
        l_LASTCHANGEOFPASSWORD=${l_ITEMARRAY[2]};
        # debug "checkPasswordAge (): Timestamp der letzen Passwortaenderung: ${l_LASTCHANGEOFPASSWORD}.";

        # Timestamp fuer heute erzeugen
        l_TODAY=$(($(date --utc --date "" +%s)/86400));
        # debug "checkPasswordAge (): Timestamp von heute: ${l_TODAY}.";

        # Berechnen der Differenz zwischen den Timestamps der letzten Passwortaenderung und heute
        l_DIFFERENCE=$((l_TODAY-l_LASTCHANGEOFPASSWORD));
        # inf "checkPasswordAge (): Die Differenz zwischen den Timestamps der letzen Passwortaenderung und heute betraegt ${l_DIFFERENCE} Tage.";

        echo "${l_DIFFERENCE}";
}

function sendAlertMail ()
{
# Test ob eine Mail gesendet werden soll
if [ "${SENDMAILIFNOOP}" == "yes" ] || [ "${SENDMAILIFERROR}" == "yes" ] ||  [ "${SENDMAILIFUPDATE}" == "yes" ]
then
        notify "Mailen des Status vom ${SCRIPTSTARTDATE} gestartet.";
        inf "Konfiguration:";
        inf " * Mail bei NOOP: ${SENDMAILIFNOOP}";
        inf " * Mail bei Fehler: ${SENDMAILIFERROR}";
        inf " * Mail bei Updates: ${SENDMAILIFUPDATE}";

        local l_SUBJECTDATE=$SCRIPTSTARTDATE;
        local l_MAILSUBJECT="";
        local l_MAILBODY="";


        # Den Anfang des Betreff mit Hostname und Datum generieren.
        l_MAILSUBJECT="[${MYHOSTNAME}]: ${l_SUBJECTDATE} - ";

        # Den Status des Programmlaufs an den Betreff anhaengen.
        if [ "$ERRORSTATUSSTRING" = "" ]
        then
                # Wenn es keinen Fehler gab
                if [ "$UPDATELIST" = "" ]
                then
                        # Wenn keine Updates durchzufuehren sind (NOOP)
                        debug "sendAlertMail (): Es sind keine Updates auszufuehren, Subject um NOOP erweitert.";
                        l_MAILSUBJECT+="${SUBJECTSTATENOOP}";
                else
                        # Es liegen Userpasswoerter vor, die geupdated werden muessen (UPDATE)
                        debug "sendAlertMail (): Es liegen Updates vor, Subject um Update erweitert.";
                        l_MAILSUBJECT+="${SUBJECTSTATEUPDATE}"
                fi

        else
                # Es sind Fehler aufgetreten (ERROR)
                debug "sendAlertmail (): Es liegen Fehler vor, Subject um ERROR erweitert.";
                l_MAILSUBJECT+="${SUBJECTSTATEERROR}"
        fi

        # Bauen des Mail-Bodies abhaengig vom Status
        if [ "$ERRORSTATUSSTRING" = "" ]
        then
                # Es sind keine Fehler aufgetreten
                if [ "$UPDATELIST" =  "" ]
                then
                        # Wenn keine Updates durchzufuehren sind (NOOP)
                        debug "sendAlertMail (): Es sind keine Updates auszufuehren, NOOP-Body generieren.";
                        l_MAILBODY+="Beim Lauf des Skripts ${MYNAME} auf ${MYHOSTNAME} wurde keine Operation ausgefuehrt.\n";
                        l_MAILBODY+="\n";
                        l_MAILBODY+="==========================================================================================\n"
                        l_MAILBODY+="Das Alter des Passworts aller ueberprueften User ist unterhalb des Ablauflimits\n";
                        l_MAILBODY+="von ${MAXPASSWORDAGE} Tagen.\n";
                        l_MAILBODY+="==========================================================================================\n"
                else
                        # Es liegen Userpasswoerter vor, die geupdated werden muessen (UPDATE)
                        debug "sendAlertMail (): Es liegen Updates vor, UPDATE-Body generieren.";
                        l_MAILBODY+="Beim Lauf des Skripts ${MYNAME} auf ${MYHOSTNAME} wurden folgende User gefunden,\n";
                        l_MAILBODY+="deren Passwort abgelaufen ist.\n";
                        l_MAILBODY+="\n";
                        l_MAILBODY+="==========================================================================================\n";
                        for i in $UPDATELIST; do
                                l_MAILBODY+="${i}\n";
                        done;
                        l_MAILBODY+="==========================================================================================\n";
                fi

        else
                # Es sind Fehler aufgetreten (ERROR)
                debug "sendAlertMail (): Es sind Fehler aufgetreten, ERROR-Body generieren.";
                l_MAILBODY+="Beim Lauf des Skripts ${MYNAME} auf ${MYHOSTNAME} traten folgende Fehler auf:\n";
                l_MAILBODY+="\n";
                l_MAILBODY+="==========================================================================================\n";
                l_MAILBODY+="${ERRORSTATUSSTRING}";
                l_MAILBODY+="==========================================================================================\n";
        fi


        # Senden der E-Mail

        echo -e "${l_MAILBODY}" | mailx -s "${l_MAILSUBJECT}" -r $MAILSENDER $MAILRECEIVER && inf "Die Email an ${MAILRECEIVER} wurde versendet." || error "Die Email an ${MAILRECEIVER} konnte nicht versendet werden."

        notify "Mailen des Status der Passwortalterueberpruefung vom ${SCRIPTSTARTDATE} beendet."

fi
}

## -------------------------------------------------------------------------------------------------------
## Main
## -------------------------------------------------------------------------------------------------------

# Startup Info
SCRIPTSTARTDATE=$(makeadate);
notify "**************************************************************************************************";
notify "${SCRIPTSTARTDATE} - ${MYHOSTNAME}: ${MYNAME} gestartet."
notify "**************************************************************************************************";

# Die Userliste in USERLIST einlesen
readUserList;

# Initialer Test ob mehr als eine Trefferzeile von grep fuer einen User zurueckgeliefert wurde oder ob ein
# User gar nicht existiert.
# Wenn ein User existiert, wird er der WORKLIST hinzugefuegt.
inf "Initiale Tests und Befuellung der WORKLIST.";
for i in $USERLIST; do
        initialtest $i;
done;

# Wenn keine Fehlerkondition vorliegt wird das Alter des Passworts, der in der WORKLIST gelisteten User, ueberprueft. Wenn das Alter ueber der erlaubten
# Grenze ($MAXPASSWORDAGE) liegt, wird der User der UPDATELIST hinzugefuegt.
if [ "${ERRORSTATUSSTRING}" == "" ]
then
        inf "Die WORKLIST ist: ${WORKLIST}";

        # Aufruf der Funktion um das Alter der Passwoerter der einzelnen User aus USERLIST zu pruefen.
        inf "Abarbeitung der WORKLIST und ermitteln des Passwortalters.";
        for a in $WORKLIST; do
                USERPASSWORDAGE=$(checkPasswordAge $a);
                if [ "$USERPASSWORDAGE" -gt $MAXPASSWORDAGE ]
                then
                        # Wenn das Passwort abgelaufen ist
                        warn "Das Passwort von User $a ist $USERPASSWORDAGE Tage alt und ueber dem maximalen Passwortalter von $MAXPASSWORDAGE Tagen.";
                        UPDATELIST+=" "${a};
                else
                        # Wenn das Passwort nicht abgelaufen ist
                        inf "Das Passwort von User $a ist $USERPASSWORDAGE Tage alt und unter dem maximalen Passwortalter von $MAXPASSWORDAGE Tagen.";
                fi
        done;
fi

# Versenden der Mail
sendAlertMail;

# Skriptende Info
notify "**************************************************************************************************";
notify "$(makeadate) - ${MYHOSTNAME}: ${MYNAME} beendet."
notify "**************************************************************************************************\n\n";