Addressing this issue was a little harder than actually expected and
resulted in a major refactoring of the daily cron job code. Can you
please try with the attached new cron script which I have commited to
svn?

Greetings
Marc

-- 
-----------------------------------------------------------------------------
Marc Haber         | "I don't trust Computers. They | Mailadresse im Header
Mannheim, Germany  |  lose things."    Winona Ryder | Fon: *49 621 72739834
Nordisch by Nature |  How to make an American Quilt | Fax: *49 621 72739835
#!/bin/bash

set -e
set -C

# trap handler

traphandler() {
        trap - INT ERR
        if [ -n "${LOCKED:-}" ]; then
          # we have the lock, 
          pidof aide | xargs --no-run-if-empty kill -9
        fi
        onexit signal $1
        return 0
}
trap ' traphandler INT; trap - INT ERR' INT
trap ' traphandler ERR; trap - INT ERR' ERR

# bail if no aide binary found

[ -f "/usr/bin/aide" ] || exit 0

# default variables

PATH="/sbin:/usr/sbin:/bin:/usr/bin"
LOGDIR="/var/log/aide"
LOGFILE="$LOGDIR/aide.log"
CONFFILE="/var/lib/aide/aide.conf.autogenerated"
PREFIX="aide"
TMPBASE="/var/run/aide"
LOCKFILE="$TMPBASE/cron.daily.lock"
TMPDIRIN="$TMPBASE/cron.daily"

AIDEARGS="-V4"
FQDN="$(hostname -f)"
MAILSUBJ="Daily AIDE report for $FQDN"

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

# have /etc/default/aide override variables

if [ -f "/etc/default/aide" ]; then
        . "/etc/default/aide"
fi

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

set -u

# umask

umask 077

# grep aide configuration data from aide config

DATABASE="$(< "$CONFFILE" grep "^database=file:/" | head -n 1 | cut 
--delimiter=: --fields=2)"
DATABASE_OUT="$(< "$CONFFILE" grep "^database_out=file:/" | head -n 1 | cut 
--delimiter=: --fields=2)"

# default values

MAILTO="${MAILTO:-root}"
eval MAILTO="$MAILTO"
DATABASE="${DATABASE:-/var/lib/aide/aide.db}"
LINES="${LINES:-1000}"
COMMAND="${COMMAND:-check}"
COPYNEWDB="${COPYNEWDB:-no}"
QUIETREPORTS="${QUIETREPORTS:-no}"
ONEXIT=""

# functions

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

frame() {
  WIDTH=78
  
STARS="*******************************************************************************"
  SPACES="                                                                      
         "
  printf "%s\n" "${STARS:1:$WIDTH}"
  while read line ; do
    HALF="${SPACES:1:$((($WIDTH-${#line})/2))}"
    LINE="$HALF$line$SPACES"
    printf "*%s*\n" "${LINE:1:$(($WIDTH-2))}"
  done
  printf "%s\n" "${STARS:1:$WIDTH}"
}

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

  ONEXIT="running"

  local LOGHEAD
  local MAILHEAD

  case "$1" in
        signal)
                LOGHEAD="$(printf "terminated with signal %s" "$2")"
                MAILHEAD="$(printf "The cron job was terminated with signal %s" 
"$2")"
                ;;
        fatal)
                LOGHEAD="$(printf "terminated by fatal error.")"
                MAILHEAD="$(printf "The cron job was terminated by a fatal 
error.")"
                ;;
        nolock)
                LOGHEAD="$(printf "terminated because lock %s could not be 
obtaiend." "$LOCKFILE")"
                MAILHEAD="$(printf "The cron job was terminated because lock %s 
could not be obtained." "$LOCKFILE")"
                ;;
        cantmovetmp)
                LOGHEAD="$(printf "terminated: Cannot move away %s." 
"$TMPDIRIN")"
                MAILHEAD="$(printf "The cron job was terminated: Cannot move 
away %s." "$TMPDIRIN")"
                ;;
        cantcreatetmp)
                LOGHEAD="$(printf "terminated: Cannot create temporary 
directory %s." "$TMPDIRIN")"
                MAILHEAD="$(printf "The cron job was terminated: Cannot create 
temporary directory %s." "$TMPDIRIN")"
                ;;
        success)
                ;;
        *)
                LOGHEAD="$(printf "wrong parameter (\"%s\") to onexit." "$1")"
                MAILHEAD="$(printf "The cron job was terminated for unknown 
reasons, and a wrong parameter (\"%s\")was given to onexit." "$1")"
                ;;
  esac
  
  if [ -z "${TMPDIR:-}" ]; then
    # we are being called so early that no TMPDIR exists yet
    # LOGHEAD goes to syslog instead of LOGFILE since we do not know
    # what's up with LOGFILE
    logger -t aide-cron-daily "$LOGHEAD"
    echo "$MAILHEAD" | /usr/bin/mail -s "premature termination - $MAILSUBJ" 
"$MAILTO"
  else
    # we are being called after the cron job was properly set up.
    # To the full works.

    [ -f "$LOGFILE" ] && savelog -t -g adm -m 640 -u root -c 7 "$LOGFILE" > 
/dev/null

    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 "$LOGHEAD\n" | frame >> "$LOGFILE"
      printf "\n" >> "$LOGFILE"
    fi
    if [ -n "$MAILHEAD" ]; then
      printf "$MAILHEAD\n" | frame >> "$MAILFILE"
      printf "\n\n" >> "$MAILFILE"
    fi

    # script errors

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

      printf "script errors\n" | frame >> "$LOGFILE"
      < "$ERRORLOG" cat >> "$LOGFILE"
      printf "End of script errors\n" >> "$LOGFILE"
    fi

    # aide post run information

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

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

    # include error log in daily report e-mail

    if [ -n "${ARETVAL:-}" ] && [ "$ARETVAL" != "0" ]; then
      printf "AIDE returned a non-zero exit value\nexit value is %d\n\n" 
"$ARETVAL" | frame >> "$MAILFILE"
      printf "AIDE returned a non-zero exit value\nexit value is %d\n" 
"$ARETVAL" | frame >> "$LOGFILE"
    fi

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

    # include de-noised log into mail

    if [ -n "${NOISE:-}" ]; then
        NOISETMP="$(tempfile --directory "/tmp" --prefix "aidenoise")"
        NOISETMP2="$(tempfile --directory "/tmp" --prefix "aidenoise")"
        < "$ARUNLOG" sed -n '1,/^Detailed information about changes:/p' | \
        grep '^\(changed\|removed\|added\):' | \
        grep -v "^added: THERE WERE ALSO [0-9]\+ FILES ADDED UNDER THIS 
DIRECTORY" > "$NOISETMP2"
        
        if [ -n "$NOISE" ]; then
                < "$NOISETMP2" grep -v "^\(changed\|removed\|added\):$NOISE" > 
"$NOISETMP"
                printf >> "$MAILFILE" "De-Noised output removes everything 
matching %s.\n" "$NOISE"
        fi
        
        if [ -s "$NOISETMP" ]; then
                loglines="$(< $NOISETMP wc -l | awk '{ print $1 }')"
                if [ "${loglines:=0}" -gt "$LINES" ]; then
                        printf "AIDE has returned long output which has been 
truncated in this mail\n" | \
                          frame >> "$MAILFILE"
                        printf >> "$MAILFILE" \
                          "De-Noised output is %d lines, truncated to %d.\n" 
"$loglines" "$LINES"
                        < "$NOISETMP" head -n "$LINES" >> "$MAILFILE"
                        printf >> "$MAILFILE" "\nEnd of truncated De-Noised 
AIDE output. The full output can be found in %s.\n\n" "$LOGFILE"
                else
                        printf >> "$MAILFILE" "De-Noised output of the daily 
AIDE run (%d lines):\n" "$loglines"
                        < "$NOISETMP" cat >> "$MAILFILE"
                        printf >> "$MAILFILE" "\nEnd of De-Noised AIDE 
output.\n\n"
                fi
        else
                printf >> "$MAILFILE" "AIDE detected no changes after removing 
noise.\n\n"
        fi
        printf >> "$MAILFILE" 
"============================================================================\n"
    fi

    # include non-de-noised log into mail

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

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

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

    # send mail if changes or errors were detected or quiet reports not 
requested
    if [ "$QUIETREPORTS" = "no" ] || [ "$CHANGES" != "0" ] || [ $(< "$ERRORLOG" 
wc -l) -ne 0 ]; then
      < "$MAILFILE" /usr/bin/mail -s "$MAILSUBJ" "$MAILTO"
    fi

    # clean up temp files
    rm -rf $TMPDIR
  fi

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

  return 0
}

BEGINTIME="$(date +%s)"

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

# prepare temp dir
if [ -e "$TMPDIRIN" ]; then
        if ! NEWNAME="$(mktemp -d $TMPBASE/cron.daily.old.XXXXXXXXXX)"; then
                onexit cantmovetmp
                exit 1
        fi
        mv "$TMPDIRIN" "$NEWNAME"
        OLDTMPDIRFOUND="yes"
fi

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

# we can now directly use file names inside $TMPDIR: It is only
# writeable for us (umask 077), so we're safe against symlink attacks.
# We use invariant file names here since our work files need to be
# excluded from aide.
TMPDIR="$TMPDIRIN"

# now, with $TMPDIR having been created, we can use onexit.

# LOGFILE: /var/log/aide/aide.log - all logs untruncated (not temp)
# ERRORLOG: Error messages from script. Gets written to $LOGFILE first
ERRORLOG="$(mytempfile errorlog)"

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

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

ARETVAL=0

if [ ! -f "$DATABASE" ]; then
        printf >> "$ERRORLOG" "Fatal error: The AIDE database does not exist!\n"
        printf >> "$ERRORLOG" "This may mean you haven't created it, or it may 
mean that someone has removed it.\n"
        onexit fatal
        exit 0
fi

# code

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"

update-aide.conf
aide.wrapper $AIDEARGS "--$COMMAND" >|"$ARUNLOG" 2>|"$AERRLOG"
ARETVAL="$?"

# POSTRUNLOG: summary of aide execution and cron job 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)"

# find out checksums and other data for the database we were comparing against.

DBCHECKDB="$(mytempfile dbcheckdb)"

printf >> "$DBCHECKLOG" "The check was done against %s with the following 
characteristics:\n" "$DATABASE"

DBCHECKFILE="$DBCHECKDB"
DBCHECKOLD="$DATABASE"
DBCHECKNEW="$DBCHECKDB"
printf "database=file:%s\ndatabase_out=file:%s\n%s\$ 
p+u+g+n+i+s+b+m+c+md5+sha1+rmd160+haval+gost+crc32+tiger" \
  "${DBCHECKOLD}" "${DBCHECKNEW}" "${DBCHECKFILE}" | \
  aide --config=- --init > /dev/null
sed -i "s|^${DBCHECKFILE}[[:space:]]|${DATABASE} |" "$DBCHECKDB"
DBCHECKFILE="$DATABASE"
DBCHECKOLD="$DBCHECKDB"
DBCHECKNEW="$DBCHECKDB"

printf "database=file:%s\ndatabase_out=file:%s\n%s\$ 
p+u+g+n+i+s+b+m+c+md5+sha1+rmd160+haval+gost+crc32+tiger" \
  "${DBCHECKOLD}" "${DBCHECKNEW}" "${DBCHECKFILE}" | \
  aide --config=- --check 2>/dev/null | \
  sed -n '/^  
\(Size\|Bcount\|Mtime\|Ctime\|Inode\|MD5\|SHA1\|RMD160\|TIGER\|CRC32\|HAVAL\|GOST\)/{s/\([^:]*\)[^,]*,[[:space:]]*\(.*\)/\1:
 \2/;p;}' \
  >> "$DBCHECKLOG"

if [ "$COMMAND" = "update" ]; then
        printf >> "$DBCHECKLOG" "\nThe AIDE run created a new database %s with 
the following characteristics:\n" "$DATABASE_OUT"

        sed -i "s|^${DATABASE}[[:space:]]|${DATABASE_OUT} |" "$DBCHECKDB"
        DBCHECKFILE="$DATABASE_OUT"
        DBCHECKOLD="$DBCHECKDB"
        DBCHECKNEW="$DBCHECKDB"
        printf "database=file:%s\ndatabase_out=file:%s\n%s\$ 
p+u+g+n+i+s+b+m+c+md5+sha1+rmd160+haval+gost+crc32+tiger" \
          "${DBCHECKOLD}" "${DBCHECKNEW}" "${DBCHECKFILE}" | \
        aide --config=- --check 2>/dev/null | \
          sed -n '/^  
\(Size\|Bcount\|Mtime\|Ctime\|Inode\|MD5\|SHA1\|RMD160\|TIGER\|CRC32\|HAVAL\|GOST\)/{s/\([^:]*\)[^,]*,[[:space:]]*\(.*\)/\1:
 \2/;p;}' \
        >> "$DBCHECKLOG"
fi
rm -f "$DBCHECKDB"

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

COPYDB="0"
CHANGES="1"
if < "$ARUNLOG" grep '^###' | head -n 1 | grep -q '### All files match AIDE 
database. Looks okay!' && \
   [ "$(< $ARUNLOG wc -l)" -lt 20 ]; then
        CHANGES="0"
fi

if [ "$COPYNEWDB" = "ifnochange" ] && [ "$CHANGES" = "0" ]; then
        COPYDB="1"
fi

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

if [ "$COPYDB" = "1" ] && [ "$COMMAND" = "update" ]; then
        cp -f "$DATABASE_OUT" "$DATABASE"
        printf >> "$POSTRUNLOG" "no significant changes detected.\n"
        printf >> "$POSTRUNLOG" "output database %s was copied to %s as 
requested by cron job configuration\n" "$DATABASE_OUT" "$DATABASE"
fi

onexit success

# end of file

Reply via email to