#!/bin/bash
#
# bitcron was once written by Pim van Pelt <pim@ipng.nl>
# modifications by BIT employees <support@bit.nl>
#
# See 'man 1 bitcron' for documentation.
#
# You MUST NOT edit this file.
# You should be editing the cron definition file.
#


BITCRON_SEARCHPATH="/usr/share/bit-cron/cronscripts/ /etc/cronscripts/ ./"


fatal() 
{
    echo ""
    echo "===== FATAL ====="
    echo $1
    echo "===== FATAL ====="
    ERR=$(($ERR + 1))
    FATAL=1;
    func_end
}


error() 
{
    echo ""
    echo "===== ERROR ====="
    echo $1
    echo "===== ERROR ====="
    ERR=$(($ERR + 1))
}


warning() 
{
    echo ""
    echo "===== WARNING ====="
    echo $1
    echo "===== WARNING ====="
    WRN=$(($WRN + 1))
}


func_sendmail()
{
    if [ $WRN -eq 0 -a $ERR -eq 0 ]; then
        if [ ! -z "$MAILTO" ]; then
            (
                echo "From: $HOSTNAME $NAME <`whoami`@$FQDN>"
                echo "To: $MAILTO"
                echo "Subject: bitcron $NAME - succesful"
                echo "Date: `date '+%a, %d %b %Y %T %z'`"
                echo "Errors-To: $MAILTO"
                echo "Reply-To: $MAILTO"
                echo "X-Mailer: bitcron 128.59"
                echo "X-bitcron: $NAME"
                echo "X-Precedence: automatic"
                echo ""
                echo "Dear reader,"
                echo ""
                echo "This is the $0 program on $HOSTNAME informing"
                echo "you I have succesfully executed the bitcron $NAME"
                echo "with arguments '$BITCRONSCRIPTARGS'."
                echo ""
                echo "The log that I have for this cronjob follows:"
                cat $LOG
                echo ""
                echo "This mail is purely FYI, user intervention is not required."
                echo ""
                echo "-- "
                echo "Cheers,"
                echo "    $HOSTNAME"
            ) | /usr/sbin/sendmail -t
        fi
    else
        (
            echo "From: $HOSTNAME $NAME <`whoami`@$FQDN>"
            echo "To: $ESCALATE_MAILTO"
            if [ ! -z "$MAILTO" ]; then 
                echo "Cc: $MAILTO"
            fi
            if [ $FATAL -eq 0 ]; then
                echo "Subject: bitcron $NAME - $ERR error(s), $WRN warning(s)"
            else
                echo "Subject: bitcron $NAME - FATAL - $ERR error(s), $WRN warning(s)"
            fi
            echo "Date: `date '+%a, %d %b %Y %T %z'`"
            echo "Errors-To: $ESCALATE_MAILTO"
            echo "Reply-To: $ESCALATE_MAILTO"
            echo "X-Mailer: bitcron 128.59"
            echo "X-Precedence: automatic"
            echo "X-bitcron: $NAME"
            echo "X-Errors: $ERR"
            echo "X-Warnings: $WRN"
            echo ""
            echo "Dear reader,"
            echo ""
            echo "This is the $0 program on $HOSTNAME informing"
            echo "you that there was a problem running the bitcron $NAME"
            echo "with arguments '$BITCRONSCRIPTARGS'."
            echo ""
            echo "The logfile that lead up to this:"
            cat $LOG
            echo ""
            echo "I hope you can deal with this situation ASAP."
            echo ""
            echo "-- "
            echo "Cheers,"
            echo "    $HOSTNAME"
        ) | /usr/sbin/sendmail -t
    fi
}


func_begin()
{
    if [ -z "$MASTERLOG" ]; then
        LOG="/tmp/bitcron-$NAME.TMP.$$"
    else
        LOG="${MASTERLOG}.TMP.$$"
    fi

    exec 3>&1 4>&2 1>$LOG 2>&1

    WRN=0
    ERR=0
    FATAL=0
    LOCKED=0
    FQDN=$(hostname -f)
    HOSTNAME=$(hostname -s)

    if [ "A`echo $FQDN | grep '\.'`" = "A" ]; then
        FQDN="${FQDN}.mail.colo.bit.nl"
    fi
    
    echo "Beginning $NAME cronjob at `date`"

    # Optional concurrent process locking.
    if [ ! -z "$BITCRONLOCK" ]; then
        if [ -e "$BITCRONLOCK" ]; then
            PID=`cat "$BITCRONLOCK"`
            if kill -0 $PID >/dev/null 2>&1; then
                LOCKED=1
                
                dtnow=$(date +%s)
                dtthen=$(stat -c"%Y" "${BITCRONLOCK}")
                dtdiff=$(( $dtnow - $dtthen ))
                if [ $dtdiff -ge $BITCRONLOCKLIMIT ]; then
                    echo ""
                    echo "[!!]                                          [!!]"
                    echo "[!!]                                          [!!]"
                    echo "[!!] THIS JOB WAS LOCKED FOR $dtdiff SECONDS! [!!]"
                    echo "[!!]                                          [!!]"
                    echo "[!!]                                          [!!]"
                    echo ""
                fi

                if [ ! -z $BITCRONLOCKSILENT ]; then
                    ERR=0
                    WRN=0
                    FATAL=0
                    func_end
                else
                    fatal "$PID is still active. Stopping execution."
                fi
            else
                warning "Stale lockfile found? Process $PID seems inactive. Resuming."
            fi
        fi
        echo "Concurrency locking enabled: $BITCRONLOCK"
        echo $$ > "$BITCRONLOCK"
    fi

    echo ""
}


func_end()
{
    echo ""
    echo "Done with $NAME cronjob at `date`"
    echo ""
    echo "There were $ERR error(s) and $WRN warning(s)"
    if [ $FATAL -ne 0 ]; then
        echo "This script had a fatal error!"
    fi
    
    exec 1>&3 2>&4
    
    func_sendmail
    
    if [ ! -z "$MASTERLOG" ]; then
        # Rotate logs.
        if [ -e $MASTERLOG ]; then
            [ -e $MASTERLOG.9 ] && rm -f -- $MASTERLOG.9
            [ -e $MASTERLOG.8 ] && mv -f -- $MASTERLOG.8 $MASTERLOG.9
            [ -e $MASTERLOG.7 ] && mv -f -- $MASTERLOG.7 $MASTERLOG.8
            [ -e $MASTERLOG.6 ] && mv -f -- $MASTERLOG.6 $MASTERLOG.7
            [ -e $MASTERLOG.5 ] && mv -f -- $MASTERLOG.5 $MASTERLOG.6
            [ -e $MASTERLOG.4 ] && mv -f -- $MASTERLOG.4 $MASTERLOG.5
            [ -e $MASTERLOG.3 ] && mv -f -- $MASTERLOG.3 $MASTERLOG.4
            [ -e $MASTERLOG.2 ] && mv -f -- $MASTERLOG.2 $MASTERLOG.3
            [ -e $MASTERLOG.1 ] && mv -f -- $MASTERLOG.1 $MASTERLOG.2
            [ -e $MASTERLOG.0 ] && mv -f -- $MASTERLOG.0 $MASTERLOG.1
            [ -e $MASTERLOG ]   && mv -f -- $MASTERLOG   $MASTERLOG.0
        fi
        cp -f -- "$LOG" "$MASTERLOG"
    fi

    rm -f -- $LOG >/dev/null 2>&1

    # Do not remove the lockfile if this proces quits due to
    # it being locked by another process running. Stale lockfiles
    # are fixed by the locking-section in func_begin() above.
    if [ $LOCKED -eq 0 ]; then
        if [ ! -z "$BITCRONLOCK" ]; then
            rm -f -- "$BITCRONLOCK" >/dev/null 2>&1
        fi
    fi

    if [ ! -z $RETVAL ]; then
        exit $RETVAL
    elif [ $ERR -ne 0 ]; then
        exit 127
    elif [ $WRN -ne 0 ]; then
        exit 126
    fi

    exit 0
}


func_stdout_err()
{
    echo "bitcron: $1"
    echo ""
    echo "Usage:"
    echo " user@host:~\$ bitcron /path/to/my.cron"
    echo ""
    echo "Please read 'man 1 bitcron'."
    exit 128
}


# Here goes!
if [ $# -lt 1 ]; then
    func_stdout_err "Missing bitcron script"
fi

CRONDEFINITION=$1; shift;
if [ "$CRONDEFINITION" = "--insecure" ]; then
    CRONDEFINITION=$1; shift;
fi

BITCRONSCRIPTARGS=$*

#
# Search through paths if the cron definition was not an absolute path.
#
DIR=""
if [ ${CRONDEFINITION%%/*} ]; then
    for TDIR in $BITCRON_SEARCHPATH; do
        if [ -r ${TDIR}${CRONDEFINITION} ]; then
            DIR=$TDIR
            break;
        fi
    done
fi

if [ ! -r ${DIR}${CRONDEFINITION} ]; then
    func_stdout_err "Unreadable cron definition: ${DIR}${CRONDEFINITION}"
fi

# Ignore MAILTO env set from cron.d/crontab definition.
# Bit-cron scripts can define MAILTO when they want to.
MAILTO=

# Before loading the cron definition, check if we're privileged
# and then wether or not the cron definition is owned by root.
#
# This is a security hole, otherwise. The user could edit the
# cron definition and have it execute as root. If '--insecure'
# occurs somewhere in the cmdline, it forces this insecure behavior.
if ! grep -q -- "--insecure" /proc/$$/cmdline; then
    if [ "$(id -u)" = "0" -o "$(id -ru)" = "0" ]; then
        # Check cron definition ownership
        CDU=$(stat -c "%u" "${DIR}${CRONDEFINITION}")
        CDG=$(stat -c "%g" "${DIR}${CRONDEFINITION}")
        if [ $CDU -ne 0 -a $CDG -ne 0 ]; then
            # XXX 20200128 SS
            echo "Refusing to run as root: '${DIR}${CRONDEFINITION}' is owned by $CDU:$CDG - Security risk!" >>/var/log/bit-cron.log
            func_stdout_err "Refusing to run as root: '${DIR}${CRONDEFINITION}' is owned by $CDU:$CDG - Security risk!"
        fi

        # Check group/other writeability
        WCL=$(find "${DIR}${CRONDEFINITION}" -perm /022 | wc -l)
        if [ $WCL -ne 0 ]; then
            # XXX 20200128 SS
            echo "Refusing to run as root: '${DIR}${CRONDEFINITION}' is writable for group/others - Security risk!" >>/var/log/bit-cron.log
            func_stdout_err "Refusing to run as root: '${DIR}${CRONDEFINITION}' is writable for group/others - Security risk!"
        fi
    fi
fi

# Load the CRON definition file
. ${DIR}${CRONDEFINITION}

[ -z "$BITCRONLOCKLIMIT" ] && BITCRONLOCKLIMIT=86400
[ -z "$NAME" ] && func_stdout_err "bitcron: Missing mandatory variable NAME in ${DIR}${CRONDEFINITION}"
[ -z "$ESCALATE_MAILTO" ] && func_stdout_err "bitcron: Missing mandatory variable ESCALATE_MAILTO in ${DIR}${CRONDEFINITION}"

func_begin
func_cron $*
func_end
