#!/bin/bash
#
# Copyright (C) 2000-2005 Mike Markley
# Copyright (C) 2006-2023 Marc Haber
# Copyright (C) 2010-2021 Hannes von Haugwitz
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Code shared with aideupdate lives in aide-lib.sh.


set -e
set -C

shopt -u checkwinsize

CALLER=""
if [ -n "$1" ]; then
    #shellcheck disable=SC2034
    CALLER="$1"
fi

# trap handler

FQDN="$(hostname --fqdn 2>/dev/null || true)"
if [ -z "$FQDN" ]; then
    FQDN="$(hostname 2>/dev/null || true)"
fi
if [ -z "$FQDN" ]; then
    echo >&2 "error determining FQDN: neither hostname nor hostname --fqdn does give output"
    hostname >&2
    hostname --fqdn >&2
    exit 1
fi

traphandler() {
    trap - INT ERR
    if [ -n "${LOCKED:-}" ]; then
        # we have the lock, 
        pidof aide | xargs --no-run-if-empty kill -9
    fi
    if type -t onexit >/dev/null; then
        onexit signal "$1"
    else
        echo "${MAILHEAD:-}" | fold --bytes --width="${MAILWIDTH:-990}" | send_mail "${MAILTO}" "early termination - ${MAILSUBJ}"
    fi
    return 0
}
trap ' traphandler INT; trap - INT ERR' INT
trap ' traphandler ERR; trap - INT ERR' ERR

# bail if no aide binary found

if ! [ -f "/usr/bin/aide" ] && ! [ -f "/usr/sbin/aide" ]; then
    exit 0
fi

# shared code. The database staging file gets a name that aide.conf can
# exclude.

AIDE_TMP_SUFFIX=".dailyaidecheck.tmp"
AIDE_LIB="${AIDE_LIB:-/usr/share/aide/lib/aide-lib.sh}"
if ! [ -r "$AIDE_LIB" ]; then
    echo >&2 "ERR: $AIDE_LIB is not readable, cannot run the daily aide check"
    exit 1
fi
# shellcheck source=aide-lib.sh
. "$AIDE_LIB"
if [ "${AIDE_LIB_API:-0}" != "1" ]; then
    echo >&2 "ERR: $AIDE_LIB has API version ${AIDE_LIB_API:-0}, expected 1"
    exit 1
fi

# Configuration
#
# This script does not read any configuration file. All settings are taken
# from the environment, which is expected to be populated by the calling
# systemd unit (EnvironmentFile=/etc/default/aide). Every setting has a
# default that is used if the variable is unset or empty. To run the script by
# hand with the same configuration, use
#   (set -a; . /etc/default/aide; exec /path/to/dailyaidecheck)

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

LOGDIR="${LOGDIR:-/var/log/aide}"
LOCKBASE="${LOCKBASE:-/run/aide}"
TMPBASE="${TMPBASE:-/var/lib/aide}"
if ! [ -d "${TMPBASE}" ]; then
    TMPBASE="/run/aide"
fi

# LOGFILE: /var/log/aide/aide.log - all logs untruncated (not temp)
LOGFILE="${LOGFILE:-$LOGDIR/aide.log}"
LOCKFILE="${LOCKFILE:-$LOCKBASE/dailyaidecheck.lock}"
TMPDIRIN="${TMPDIRIN:-$TMPBASE/dailyaidecheck}"

# USE_SAVELOG: 1 or yes to rotate $LOGFILE with savelog. Anything else (e.g.
# no) selects time stamped log files. Default: use savelog if it is installed.
USE_SAVELOG="${USE_SAVELOG:-}"
if [ -z "$USE_SAVELOG" ] && command -v savelog > /dev/null; then
    USE_SAVELOG="1"
fi

AIDEARGS="${AIDEARGS:-}"
MAILSUBJ="${MAILSUBJ:-Daily AIDE report for $FQDN}"
# systemd's EnvironmentFile= does not expand variables, so a configured
# MAILSUBJ may contain a literal $FQDN or ${FQDN}: replace it with the real FQDN
MAILSUBJ="${MAILSUBJ//\$\{FQDN\}/$FQDN}"
MAILSUBJ="${MAILSUBJ//\$FQDN/$FQDN}"

BEGINSTAMP="$(date +"%Y-%m-%d %H:%M:%S")"

# from here on, we're going to bail on unbound variables

set -u

# umask

umask 027

MAILTO="${MAILTO:-root}"
eval MAILTO="$MAILTO"

send_mail() {
    local MAILTO
    local MAILSUBJ
    local MAILNAME
    local S_NAIL_MTA
    local RET
    MAILTO="$1"
    shift
    MAILSUBJ="$*"
    RET=""
    if [ -n "${MAILCMD:-}" ]; then
        eval "${MAILCMD} -s \"${MAILSUBJ}\" \"${MAILTO}\"" || RET=$?
    elif command -v s-nail >/dev/null; then
        S_NAIL_MTA="smtp://_aide@localhost"
        if [ "${CALLER}" == "--autopkgtest" ]; then
            S_NAIL_MTA="test:///tmp/dailyaidecheck"
        fi
        if echo "${MAILTO}" | grep -qv @; then
            # unqualified MAILTO, add /etc/mailname
            MAILNAME="$(cat /etc/mailname 2>/dev/null || true)"
            MAILTO="${MAILTO}@${MAILNAME:-localhost}"
        fi
        s-nail -Smta=${S_NAIL_MTA} -Ssmtp-auth=none -Sv15-compat -Sfrom="AIDE on ${FQDN} <root@${FQDN}>" -s "${MAILSUBJ}" "${MAILTO}" || RET=$?
    elif command -v mail >/dev/null; then
        if [ "${CALLER}" = "--crondaily" ] || [ "$(id -u)" -eq 0 ]; then
            # We are called from cron via capsh or have root and thus
            # mail(1) is useable, even if sendmail is set-uid-root.
            mail -s "${MAILSUBJ}" "${MAILTO}" || RET=$?
        else
            printf >&2 "WARN: it is not possible to use mail(1) unless aide is run as root or as non-root with added capabilities (either via systemd or via capsh). See /usr/share/doc/aide-common/README.Debian.gz for more information.\\n"
        fi
    else
        printf >&2 "WARN: mail or s-nail not installed, cannot send mail. See /usr/share/doc/aide-common/README.Debian.gz for more information.\\n"
    fi
    if [ -n "${RET}" ]; then
        printf >&2 "ERR: dailyaidejob was not able to send mail. The invoked mail command returned a non-zero exit code.\\n"
    fi
}

CONFIG="${CONFIG:-/etc/aide/aide.conf}"

# grep aide configuration data from aide config

aide_conf_db_paths "${CONFIG}"
DATABASE="$AIDE_DB_IN"
DATABASE_OUT="$AIDE_DB_OUT"
GROUPED="$(aide_conf_grouped "${CONFIG}")"

# default values

CRON_DAILY_RUN="${CRON_DAILY_RUN:-yes}"
MAILWIDTH="${MAILWIDTH:-990}"
FIGLET="${FIGLET:-yes}"
DATABASE="${DATABASE:-/var/lib/aide/aide.db}"
DATABASE_OUT="${DATABASE_OUT:-/var/lib/aide/aide.db.new}"
LINES="${LINES:-1000}"
COMMAND="${COMMAND:-check}"
COPYNEWDB="${COPYNEWDB:-no}"
QUIETREPORTS="${QUIETREPORTS:-no}"
SILENTREPORTS="${SILENTREPORTS:-no}"
TRUNCATEDETAILS="${TRUNCATEDETAILS:-no}"
FILTERUPDATES="${FILTERUPDATES:-no}"
FILTERINSTALLATIONS="${FILTERINSTALLATIONS:-no}"
CRONEXITHOOK="${CRONEXITHOOK:-}"
ONEXIT=""
# DAILYAIDECHECK_DEBUG=n: save every newly generated database with a time
# stamp next to $DATABASE_OUT, keeping at most n copies. 0 (default) disables.
DEBUGKEEP="${DAILYAIDECHECK_DEBUG:-0}"
AIDEUSER="${AIDEUSER:-$(id --user --name)}"
AIDEUSERUID="$(id --user "${AIDEUSER}")"

# insist that $LOCKBASE exists (only needed if we are going to use dotlockfile)
if command -v dotlockfile >/dev/null 2>&1 && ! [ -d "$LOCKBASE" ]; then
    printf >&2 "ERR: %s does not exist. It must exist and be only writable for %s\\n" "${LOCKBASE}" "${AIDEUSER}"
fi

# silent implies quiet
if [ "$SILENTREPORTS" = "yes" ]; then
    QUIETREPORTS="yes"
fi

# Get the database's date
DATABASEDATE="$(aide_db_date "$DATABASE")"

# Force TRUNCATEDETAILS when filter updates/installations
if [ "$FILTERUPDATES" = "yes" ] || [ "$FILTERINSTALLATIONS" = "yes" ] ; then
    TRUNCATEDETAILS="yes"
fi

# functions

mytempfile() {
    local name="$1"
    echo "$TMPDIR/$name"
    touch "$TMPDIR/$name"
}

save_debug_db() {
    # $1: number of copies to keep
    local keep="$1"
    local LC_COLLATE=C
    local dest i
    local -a copies

    case "$keep" in
        ''|*[!0-9]*)
            printf >> "$ERRORLOG" "DAILYAIDECHECK_DEBUG must be a non-negative integer, got \"%s\". Not saving the new database.\\n" "$keep"
            return 0
            ;;
    esac
    keep=$((10#$keep))
    if [ "$keep" -eq 0 ]; then
        return 0
    fi

    case "$COMMAND" in
        update|init)
            ;;
        *)
            printf >> "$POSTRUNLOG" "debug: COMMAND=%s does not generate a new database, nothing saved\\n" "$COMMAND"
            return 0
            ;;
    esac

    # do not save a stale database left over from an earlier run
    if ! [ -f "$DATABASE_OUT" ] || [ "$(stat -c %Y "$DATABASE_OUT")" -lt "$BEGINTIME" ]; then
        printf >> "$POSTRUNLOG" "debug: %s was not generated by this run, nothing saved\\n" "$DATABASE_OUT"
        return 0
    fi

    dest="${DATABASE_OUT}.debug-$(date +%Y%m%d-%H%M%S)"
    if ! cp -p -- "$DATABASE_OUT" "$dest"; then
        printf >> "$ERRORLOG" "debug: could not copy %s to %s\\n" "$DATABASE_OUT" "$dest"
        return 0
    fi
    printf >> "$POSTRUNLOG" "debug: saved new database as %s\\n" "$dest"

    # The glob expands sorted and the time stamp sorts chronologically, so the
    # oldest copies come first. The copy we just made matches the glob.
    copies=( "${DATABASE_OUT}.debug-"????????-?????? )
    for ((i = 0; i < ${#copies[@]} - keep; i++)); do
        if rm -f -- "${copies[i]}"; then
            printf >> "$POSTRUNLOG" "debug: removed old copy %s\\n" "${copies[i]}"
        else
            printf >> "$ERRORLOG" "debug: could not remove old copy %s\\n" "${copies[i]}"
        fi
    done
    return 0
}

onexit() {
    if [ "$ONEXIT" = "running" ]; then
        return 1
    fi

    ONEXIT="running"

    local LOGHEAD
    local MAILHEAD
    local CRONJOBERR

    CRONEXITHOOKPARM="$1"
    case "$1" in
        signal)
            LOGHEAD="$(printf "terminated with signal %s" "$2")"
            MAILHEAD="$(printf "The daily aide check was terminated with signal %s" "$2")"
            CRONJOBERR="$2"
            ;;
        nodb)
            LOGHEAD="$(printf "database not present.")"
            MAILHEAD="$(printf "The daily aide check was terminated because no AIDE database is present")"
            CRONJOBERR="no DB"
            ;;
        fatal)
            LOGHEAD="$(printf "terminated by fatal error.")"
            MAILHEAD="$(printf "The daily aide check was terminated by a fatal error.")"
            CRONJOBERR="fatal"
            ;;
        nolock)
            LOGHEAD="$(printf "terminated because lock %s could not be obtained." "$LOCKFILE")"
            MAILHEAD="$(printf "The daily aide check was terminated because lock %s could not be obtained." "$LOCKFILE")"
            CRONJOBERR="no lock"
            ;;
        cantmovetmp)
            LOGHEAD="$(printf "terminated: Cannot move away %s." "$TMPDIRIN")"
            MAILHEAD="$(printf "The daily aide check was terminated: Cannot move away %s." "$TMPDIRIN")"
            CRONJOBERR="temp dir"
            ;;
        nohook)
            LOGHEAD="$(printf "terminated: CRONEXITHOOK set to %s which is not executeable." "$CRONEXITHOOK")"
            MAILHEAD="$(printf "The daily aide check was terminated: CRONEXITHOOK set to %s which is not executeable." "$CRONEXITHOOK")"
            CRONJOBERR="no hook"
            ;;
        cantcreatetmp)
            LOGHEAD="$(printf "terminated: Cannot create temporary directory %s." "$TMPDIRIN")"
            MAILHEAD="$(printf "The daily aide check was terminated: Cannot create temporary directory %s." "$TMPDIRIN")"
            CRONJOBERR="temp dir"
            ;;
        cantchmodtmp)
            LOGHEAD="$(printf "terminated: Cannot chmod temporary directory %s." "$TMPDIRIN")"
            MAILHEAD="$(printf "The daily aide check was terminated: Cannot chmod temporary directory %s." "$TMPDIRIN")"
            CRONJOBERR="temp dir"
            ;;
        tmpnotours)
            LOGHEAD="$(printf "terminated: Temporary directory %s has wrong owner/mode." "$TMPDIRIN")"
            MAILHEAD="$(printf "The daily aide check was terminated: Temporary directory %s has wrong owner/mode." "$TMPDIRIN")"
            CRONJOBERR="temp dir"
            ;;
        success)
            ;;
        *)
            LOGHEAD="$(printf "wrong parameter (\"%s\") to onexit." "$1")"
            MAILHEAD="$(printf "The daily aide check was terminated for unknown reasons, and a wrong parameter (\"%s\")was given to onexit." "$1")"
            CRONJOBERR="unknown"
            CRONEXITHOOKPARM="unknown"
            ;;
    esac

    if [ -z "${TMPDIR:-}" ] || [ -z "${MAILFILE:-}" ]; then
        # we are being called so early that we are not yet fully initialized
        # LOGHEAD goes to syslog instead of LOGFILE since we do not know
        # what's up with LOGFILE
        logger -t dailyaidecheck "$LOGHEAD"
        if [ "$SILENTREPORTS" != "yes" ]; then
            echo "$MAILHEAD" | fold --bytes --width="${MAILWIDTH:-990}" | send_mail "${MAILTO}" "premature termination - ${MAILSUBJ}"
        fi
        CRONEXITHOOKPARM="early-$CRONEXITHOOKPARM"
        SCRIPTRETVAL="1"
    else
        # we are being called after the daily aide check was properly set up.
        # Do the full works.

        if [ "$USE_SAVELOG" = "1" ] || [ "$USE_SAVELOG" = "yes" ]; then
            savelog -t -m 640 -c 7 "$LOGFILE" > /dev/null
        else
            printf >> "$ERRORLOG" "ERR: savelog is not available (it is part of the debianutils package) or disabled by USE_SAVELOG. The log file %s is not rotated, a time stamped log file is written instead. Old log files have to be removed manually.\\n" "$LOGFILE"
            LOGFILEWDATE="${LOGFILE}-$(date +%Y%m%d-%H%M%S)"
            # do not destroy an existing log file that is not a symlink
            if [ -f "$LOGFILE" ] && ! [ -L "$LOGFILE" ]; then
                mv -f -- "$LOGFILE" "${LOGFILEWDATE}.old"
            fi
            ln -sf "$LOGFILEWDATE" "$LOGFILE"
            LOGFILE="${LOGFILEWDATE}"
        fi

        printf >> "$MAILFILE" \
            "This is an automated report generated by the Advanced Intrusion Detection environment on %s started at %s.\\n\\n" "$FQDN" "$BEGINSTAMP"

        printf >> "$LOGFILE" \
            "aide run on %s started at %s.\\n" "$FQDN" "$BEGINSTAMP"

        if [ -n "${LOGHEAD:-}" ]; then
            printf "%s\\n" "$LOGHEAD" | aide_frame >> "$LOGFILE"
            printf "\\n" >> "$LOGFILE"
        fi
        if [ -n "${MAILHEAD:-}" ]; then
            printf "%s\\n" "$MAILHEAD" | aide_frame >> "$MAILFILE"
            printf "\\n\\n" >> "$MAILFILE"
        fi

        # report about AIDE's return value

        PRINTED=""
        FIGLETTEXT=""
        SCRIPTRETVAL="0"
        if [ -n "${ARETVAL:-}" ]; then
            ARETEXPL=""
            ARETERR=""
            PREFIX="$(printf "AIDE returned with exit code %d." "$ARETVAL")"
            case "$ARETVAL" in
                -1)
                    PREFIX=""
                    ARETERR="the daily aide check was interrupted before AIDE could return an exit code."
                    FIGLETTEXT="${CRONJOBERR:-interrupt}"
                    SCRIPTRETVAL="1"
                    ;;
                0)
                    PREFIX="AIDE returned with a zero exit code."
                    ARETEXPL="No changes detected!"
                    FIGLETTEXT="unchanged"
                    ;;
                1)
                    ARETEXPL="Added entries detected!"
                    FIGLETTEXT="add"
                    ;;
                2)
                    ARETEXPL="Removed entries detected!"
                    FIGLETTEXT="rem"
                    ;;
                3)
                    ARETEXPL="Added and removed entries detected!"
                    FIGLETTEXT="add rem"
                    ;;
                4)
                    ARETEXPL="Changed entries detected!"
                    FIGLETTEXT="chg"
                    ;;
                5)
                    ARETEXPL="Added and changed entries detected!"
                    FIGLETTEXT="add chg"
                    ;;
                6)
                    ARETEXPL="Removed and changed entries detected!"
                    FIGLETTEXT="rem chg"
                    ;;
                7)
                    ARETEXPL="Added, removed and changed entries detected!"
                    FIGLETTEXT="add rem chg"
                    ;;
                14)
                    ARETERR="Error writing!"
                    FIGLETTEXT="$ARETERR"
                    SCRIPTRETVAL="1"
                    ;;
                15)
                    ARETERR="Invalid Argument!"
                    FIGLETTEXT="EINVAL"
                    SCRIPTRETVAL="1"
                    ;;
                16)
                    ARETERR="Unimplemented function!"
                    FIGLETTEXT="unimplemented"
                    SCRIPTRETVAL="1"
                    ;;
                17|255)
                    ARETERR="Invalid configuration!"
                    FIGLETTEXT="invalid config"
                    SCRIPTRETVAL="1"
                    ;;
                18)
                    ARETERR="Input/Output error!"
                    FIGLETTEXT="EIO"
                    SCRIPTRETVAL="1"
                    ;;
                21)
                    ARETERR="cannot obtain lock"
                    FIGLETTEXT="no lock"
                    SCRIPTRETVAL="1"
                    ;;
                250)
                    ARETERR="executable aide not found"
                    FIGLETTEXT="no executable"
                    SCRIPTRETVAL="1"
                    ;;
                *)
                    ARETERR="$(printf "unknown non-zero exit value %d\\n\\n" "$ARETVAL")"
                    FIGLETTEXT="unknown error"
                    SCRIPTRETVAL="1"
                    ;;
            esac
            if [ -n "$ARETEXPL" ]; then
                printf "%s %s\\n" "$PREFIX" "$ARETEXPL" >> "$MAILFILE"
                printf "%s %s\\n" "$PREFIX" "$ARETEXPL" >> "$LOGFILE"
                PRINTED=1
            fi
            if [ -n "$ARETERR" ]; then
                printf "%s %s\\n" "$PREFIX" "$ARETERR" | aide_frame >> "$MAILFILE"
                printf "%s %s\\n" "$PREFIX" "$ARETERR" | aide_frame >> "$LOGFILE"
                PRINTED=1
            fi
            unset ARETEXPL
            unset ARETERR
            unset PREFIX
        else
            ARETEXPL="ARETVAL not initialized. daily aide check was aborted prematurely."
            ARETVAL=255
            FIGLETTEXT="abort"
            printf "%s\\n" "$ARETEXPL" | aide_frame >> "$MAILFILE"
            printf "%s\\n" "$ARETEXPL" | aide_frame >> "$LOGFILE"
            PRINTED=1
            unset ARETEXPL
        fi
        if [ "${FIGLET}" = "yes" ] && [ -x "$(command -v figlet)" ] && [ -n "$FIGLETTEXT" ]; then
            #shellcheck disable=SC2086
            printf "\\n%s\\n\\n" "$(figlet $FIGLETTEXT)" >> "$MAILFILE"
            PRINTED=1
        fi
        if [ -n "$PRINTED" ]; then
            printf "\\n" >> "$LOGFILE"
            printf "\\n\\n" >> "$MAILFILE"
        fi
        unset PRINTED

        # script errors

        if [ -n "${ERRORLOG:-}" ] && [ -s "$ERRORLOG" ]; then
            {
                printf "script errors\\n" | aide_frame
                cat "$ERRORLOG"
                printf "End of script errors\\n\\n"
            } >> "$MAILFILE"

            {
                printf "script errors\\n" | aide_frame
                cat "$ERRORLOG"
                printf "End of script errors\\n"
            } >> "$LOGFILE"
        fi

        # aide post run information

       if [ -n "${POSTRUNLOG:-}" ] && [ -s "$POSTRUNLOG" ]; then
           {
               printf "AIDE post run information\\n"
               cat "$POSTRUNLOG"
               printf "End of AIDE post run information\\n\\n"
           } >> "$MAILFILE"

           {
               printf "AIDE post run information\\n"
               cat "$POSTRUNLOG"
               printf "End of AIDE post run information\\n"
           } >> "$LOGFILE"
        fi

        # include error log in daily report e-mail

        if [ -n "${AERRLOG:-}" ] && [ -s "$AERRLOG" ]; then
            errorlines="$(wc -l "$AERRLOG" | awk '{ print $1 }')"
            {
                if [ "$LINES" -gt "0" ] && [ "${errorlines:=0}" -gt "$LINES" ]; then
                    printf "AIDE has returned many errors.\\nthe error log output has been truncated in this mail\\n" | \
                        aide_frame
                    printf "Error output is %d lines, truncated to %d.\\n" "$errorlines" "$LINES"
                    head -n "$LINES" "$AERRLOG"
                    printf "\\nEnd of truncated AIDE error output. The full output can be found in %s.\\n\\n" "$LOGFILE"
                else
                    printf "Errors produced  (%d lines):\\n" "$errorlines"
                    cat "$AERRLOG"
                    printf "\\nEnd of AIDE error output.\\n\\n"
                fi
            } >> "$MAILFILE"
            {
                printf "AIDE error output (%d lines):\\n" "$errorlines"
                cat "$AERRLOG"
                printf "End of AIDE error output\\n"
            } >> "$LOGFILE"
        else
            printf >> "$MAILFILE" "AIDE produced no errors.\\n\\n"
            printf >> "$LOGFILE" "AIDE produced no errors.\\n"
        fi

        # finish log file
        {
            if [ -n "${ARUNLOG:-}" ] && [ -s "$ARUNLOG" ]; then
                printf "AIDE output (%d lines):\\n" "$(wc -l "$ARUNLOG" | awk '{ print $1 }')"
                cat "$ARUNLOG"
                printf "End of AIDE output.\\n\\n"
            else
                printf "AIDE detected no changes.\\n\\n"
            fi

            if [ -n "${DBCHECKLOG:-}" ] && [ -s "$DBCHECKLOG" ]; then
                cat "$DBCHECKLOG"
            fi

            ENDTIME="$(date +%s)"

            printf "End of AIDE daily aide check at %s, run time %d seconds\\n"  "$(date +"%Y-%m-%d %H:%M" -d@"$ENDTIME")" "$(( ENDTIME - BEGINTIME ))"
        } >> "$LOGFILE"

        LOGFILE_CHECKSUM="$(sha256sum "$LOGFILE")"

        # include de-noised log into mail

        if [ -n "${ARUNLOG:-}" ] && [ -s "$ARUNLOG" ]; then

            MAIL_MODE=0

            # truncate details
            if [ "$TRUNCATEDETAILS" = "yes" ] ; then
                case "$ARETVAL" in
                    4|5|6|7)
                        MAILTMP="$(mytempfile aidemail)"
                        < "$ARUNLOG" sed '/^Detailed information about changes:$/,/^The attributes of the (uncompressed) database(s):$/{/^The attributes of the (uncompressed) database(s):$/!d}' >> "$MAILTMP"
                        MAIL_MODE=1
                        ;;
                    *)
                        MAILTMP="$ARUNLOG"
                        ;;
                esac

                # Filter package upgrades/installations

                if [ "$FILTERUPDATES" = "yes" ] || [ "$FILTERINSTALLATIONS" = "yes" ]; then
                    DPKGLOG="$(aide_dpkg_log)"
                    if [ -s "$DPKGLOG" ]; then
                        # Create a list of files modified by system updates
                        if [ "$FILTERUPDATES" = "yes" ] && [ "$FILTERINSTALLATIONS" = "yes" ] ; then
                            FILTER="install|upgrade"
                        elif [ "$FILTERUPDATES" = "yes" ]; then
                            FILTER="upgrade"
                        else
                            FILTER="install"
                        fi
                        PKG_FILE_LIST="$(mytempfile pkg_file_list)"
                        PKGS_LIST="$(mytempfile pkgs_list)"
                        aide_pkg_files "$DATABASEDATE" "$FILTER" "$DPKGLOG" "$PKG_FILE_LIST" "$PKGS_LIST"

                        if [ -s "$PKGS_LIST" ]; then
                            FILTEREDMAIL="$(mytempfile filteredmail)"
                            if aide_filter_report "$MAILTMP" "$FILTEREDMAIL" "$GROUPED" "$PKG_FILE_LIST" "$PKGS_LIST"; then
                                MAIL_MODE=$(( MAIL_MODE + 2 ))
                                MAILTMP="$FILTEREDMAIL"
                            else
                                printf >> "$ERRORLOG" "ERR: could not filter package changes from the report, mail report is not filtered (full output can be found in %s)!\\n" "$LOGFILE"
                            fi
                        fi
                    fi
                fi
            else
                MAILTMP="$ARUNLOG"
            fi

            if [ -n "${NOISE:-}" ]; then
                NOISETMP="$(mytempfile aidenoise1)"
                NOISETMP2="$(mytempfile aidenoise2)"
                < "$MAILTMP" sed -n '1,/^Detailed information about changes:/p' | \
                grep '^\(changed\|removed\|added\|[fdLDBFs?!][ :l<>=bpugamcinHAXSECF.+-]\{16,\}\):' | \
                grep -v "^added: THERE WERE ALSO [0-9]\\+ FILES ADDED UNDER THIS DIRECTORY" >> "$NOISETMP2" || true

                {
                    if [ -n "$NOISE" ]; then
                        ##+# leaning toothpick syndrome, consider grep -E
                        < "$NOISETMP2" grep -v "^\\(changed\\|removed\\|added\\|[fdLDBFs?!][ :l<>=bpugamcinHAXSECF.+-]\\{16,\\}\\): $NOISE" >> "$NOISETMP" || true
                        printf "De-Noised output removes everything matching %s.\\n" "$NOISE"
                    fi

                    if [ -s "$NOISETMP" ]; then
                        loglines="$(< "$NOISETMP" wc -l | awk '{ print $1 }')"
                        if [ "$LINES" -gt "0" ] && [ "${loglines:=0}" -gt "$LINES" ]; then
                            printf "AIDE has returned long output which has been truncated in this mail\\n" | \
                                aide_frame
                            printf "De-Noised output is %d lines, truncated to %d.\\n" "$loglines" "$LINES"
                            head -n "$LINES" "$NOISETMP"
                            printf "\\nEnd of truncated De-Noised AIDE output. The full output can be found in %s.\\nsha256sum: %s\\n\\n" "$LOGFILE" "$LOGFILE_CHECKSUM"
                        else
                            printf "De-Noised output of the daily AIDE check (%d lines):\\n" "$loglines"
                            cat "$NOISETMP"
                            printf "\\nEnd of De-Noised AIDE output.\\n\\n"
                        fi
                    else
                        printf "AIDE detected no changes after removing noise.\\n\\n"
                    fi
                    printf "============================================================================\\n"
                } >> "$MAILFILE"
            fi

            # include non-de-noised log into mail

            {
                if [ -n "${MAILTMP:-}" ] && [ -s "$MAILTMP" ]; then
                    loglines="$(wc -l "$MAILTMP" | awk '{ print $1 }')"
                    if [ "$LINES" -gt "0" ] && [ "${loglines:=0}" -gt "$LINES" ]; then
                        printf "AIDE has returned long output which has been truncated in this mail\\n" | \
                            aide_frame
                        printf "Output is %d lines, truncated to %d.\\n" "$loglines" "$LINES"
                        head -n "$LINES" "$MAILTMP"
                        printf "\\nEnd of truncated AIDE output. The full output can be found in %s.\\nsha256sum: %s\\n\\n" "$LOGFILE" "$LOGFILE_CHECKSUM"
                    else
                        printf "Output of the daily AIDE check (%d lines):\\n" "$loglines"
                        cat "$MAILTMP"
                        if [ "$MAIL_MODE" -gt "0" ] ; then
                            case "$MAIL_MODE" in
                                1) AIDE_OUTPUT="truncated" ;;
                                2) AIDE_OUTPUT="filtered" ;;
                                3) AIDE_OUTPUT="truncated and filtered" ;;
                            esac
                            printf "\\nEnd of %s AIDE output.\\n\\nThe full output can be found in %s.\\nsha256sum: %s\\n\\n" "$AIDE_OUTPUT" "$LOGFILE" "$LOGFILE_CHECKSUM"
                        else
                            printf "\\nEnd of AIDE output.\\n\\n"
                        fi
                    fi
                else
                    printf "AIDE detected no changes.\\n\\n"
                fi
            } >> "$MAILFILE"
        else
            printf >> "$MAILFILE" "funny, AIDE did not leave a log.\\n\\n"
            printf >> "$LOGFILE" "funny, AIDE did not leave a log.\\n"
        fi

        if [ -n "${DBCHECKLOG:-}" ] && [ -s "$DBCHECKLOG" ]; then
            < "$DBCHECKLOG" cat >> "$MAILFILE"
            printf >> "$MAILFILE" "\\n"
        fi

        printf >> "$MAILFILE" "End of AIDE daily aide check at %s, run time %d seconds\\n"  "$(date +"%Y-%m-%d %H:%M" -d@"$ENDTIME")" "$(( ENDTIME - BEGINTIME ))"

        # send mail if changes or errors (ours or aide's) were detected or quiet reports not requested
        if [ "$QUIETREPORTS" != "yes" ] || [ "$ARETVAL" != "0" ] || [ "$(< "$ERRORLOG" wc -l)" -ne 0 ] || [ -s "${AERRLOG:-/dev/null}" ]; then
            # do not send anything (not even error messages) if silence is requested
            if [ "$SILENTREPORTS" != "yes" ]; then
                < "$MAILFILE" fold --bytes --width="${MAILWIDTH:-990}" | send_mail "${MAILTO}" "${MAILSUBJ}"
            fi
        fi

        # clean up tmpfiles
        rm -rf "$TMPDIRIN"
    fi

    if [ -n "$CRONEXITHOOK" ] && [ -x "$CRONEXITHOOK" ]; then
        "$CRONEXITHOOK" "$CRONEXITHOOKPARM" || printf >&2 "WARN: CRONEXITHOOK %s exited with status %d.\\n" "$CRONEXITHOOK" "$?"
    fi

    # clear lock
    if [ -n "${LOCKED:-}" ] && command -v dotlockfile >/dev/null 2>&1; then
        cd "${LOCKBASE}"
        dotlockfile -u "$LOCKFILE" || true
    fi
    unset LOCKED

    return "${SCRIPTRETVAL}"
}

BEGINTIME="$(date +%s)"

if [ "$CRON_DAILY_RUN" != "yes" ] && ! tty -s; then
    exit 0
fi

if [ -n "$CRONEXITHOOK" ]; then
    if ! [ -x "$CRONEXITHOOK" ]; then
        onexit nohook
        exit 1
    fi
fi

if command -v dotlockfile >/dev/null 2>&1; then
    cd "${LOCKBASE}"
    if ! dotlockfile -p -l "$LOCKFILE"; then
        onexit nolock
        exit 1
    fi
else
    PREERRORLOG="no dotlockfile binary in path, not checking for already running daily aide check\\n"
fi
LOCKED=yes

# prepare temp dir
# We use invariant file names here since our work files need to be
# excluded from aide.
if [ -e "$TMPDIRIN" ]; then
    # $TMPDIRIN already exists (from an aborted run, maybe?). Move
    # it away. This does not cause the script to abort, but aide should
    # have rules to report the new directrory appearing in $TMPBASE.
    if ! NEWNAME="$(mktemp -d "$TMPBASE/dailyaidecheck.old.XXXXXXXXXX")"; then
        onexit cantmovetmp
        exit 1
    fi
    mv "$TMPDIRIN" "$NEWNAME"
    unset NEWNAME
fi

if ! mkdir -p "$TMPDIRIN"; then
    onexit cantcreatetmp
    exit 1
fi

if [ "$(stat --format='%a %u' "${TMPDIRIN}")" != "750 ${AIDEUSERUID}" ]; then
    onexit tmpnotours
    exit 1
fi

if ! chmod 0700 "${TMPDIRIN}"; then
    onexit cantchmodtmp
    exit 1
fi

# we can now directly use file names inside $TMPDIR: It is only
# writeable for us (umask 027), so we're safe against symlink attacks.
TMPDIR="$TMPDIRIN"

# now, with $TMPDIR having been created, we can use the full power of onexit.

# ERRORLOG: Error messages from script. Gets written to $LOGFILE first
ERRORLOG="$(mytempfile errorlog)"

if [ -n "${PREERRORLOG:-}" ]; then
    printf >> "$ERRORLOG" "%b" "$PREERRORLOG"
fi
unset PREERRORLOG

# MAILFILE: Contents gets mailed. Built and handled from inside onexit()
MAILFILE="$(mytempfile mailfile)"

# aide return value
ARETVAL=-1

if [ ! -f "$DATABASE" ]; then
    printf >> "$ERRORLOG" "Fatal error: The AIDE database '%s' does not exist!\\n" "$DATABASE"
    printf >> "$ERRORLOG" "This may mean you haven't created it or that the initialization process is still running, or it may mean that someone has removed it.\\n"
    onexit nodb
    exit 1
fi

# code

# re-assign current time to be more accurate about aide's real start time
BEGINSTAMP="$(date +"%Y-%m-%d %H:%M:%S")"

# ARUNLOG: standard output of aide run
ARUNLOG="$(mytempfile arunlog)"

# AERRLOG: standard error of aide run
AERRLOG="$(mytempfile aerrlog)"

printf "begin timestamp %s\\n" "$BEGINSTAMP" >> "$ARUNLOG"

ARETVAL=0
# shellcheck disable=SC2086
aide --config="${CONFIG}" ${AIDEARGS} "--${COMMAND}" --after="report_url=file:${ARUNLOG}" 2>|"${AERRLOG}" || ARETVAL="$?"

# POSTRUNLOG: summary of aide execution and daily aide check log
POSTRUNLOG="$(mytempfile postrunlog)"

# DBCHECKLOG: Output of the database checksums
DBCHECKLOG="$(mytempfile dbchecklog)"

# NOISETMP: completely de-noised log
# NOISETMP2: pre-filtered ARUNLOG, containing only changed, removed and added lines
NOISETMP="$(mytempfile noisetmp)"
NOISETMP2="$(mytempfile noisetmp2)"

# keep a time stamped copy of the new database if DAILYAIDECHECK_DEBUG=n is set
save_debug_db "$DEBUGKEEP"

# find out whether we neeed to copy the new database over the old one

COPYDB="0"
if [ "$COPYNEWDB" = "ifnochange" ] && [ "$ARETVAL" = "0" ]; then
    COPYDB="1"
    printf >> "$POSTRUNLOG" "no significant changes detected.\\n"
fi

if [ "$COPYNEWDB" = "yes" ]; then
    COPYDB=1
fi

if [ "$COPYDB" = "1" ] && [ "$COMMAND" = "update" ]; then
    if [ "$ARETVAL" -le 7 ]; then
        # aide_copy_db_atomic copies to a temporary file next to $DATABASE and
        # renames it, so that a failing copy (disk full) cannot leave a
        # truncated baseline behind
        if aide_copy_db_atomic "$DATABASE_OUT" "$DATABASE"; then
            printf >> "$POSTRUNLOG" "output database %s was copied to %s as requested by daily aide check configuration\\n" "$DATABASE_OUT" "$DATABASE"
        else
            printf >> "$ERRORLOG" "ERR: could not copy output database %s to %s, the old database is unchanged.\\n" "$DATABASE_OUT" "$DATABASE"
        fi
    else
        # aide failed, its output database cannot be trusted
        printf >> "$POSTRUNLOG" "AIDE returned with exit code %d, output database %s was NOT copied to %s\\n" "$ARETVAL" "$DATABASE_OUT" "$DATABASE"
    fi
fi

onexit success
exit 0

# vim: tabstop=4 shiftwidth=4 expandtab
# end of file
