scripts/watchd

153 lines
5.5 KiB
Bash
Executable File

#!/bin/bash
# watchd - Bash script to check the integrity/state of a given directory. Meant to be run continuously in cron.
# Written 9th January 2013 by Matthew Connelly <maff@maff.me.uk>
#Internal variables
HOSTNAME="$(hostname -f)"
HOSTNAME_SHORT="$(hostname -s)"
EGREP_BIN="/bin/egrep"
FIND_BIN="/bin/find"
LS_BIN="/bin/ls"
LS_ARGS="-lAd"
SENDMAIL_BIN="/usr/sbin/sendmail"
STAT_BIN="/usr/bin/stat"
STAT_ARGS="-c %a"
#This should be the full path to your config file
CONF_LOCATION="/etc/watchd.conf"
#Main script
source $CONF_LOCATION
#First we check that we're supposed to be running checks. If not, simply exit as everything that's done after here is check processing.
if [ "$ENABLE_CHECKING" != "YES" ]; then
exit 0
fi
#Output variables. OUTPUT is the internal variable which is sent to the state file. HR_OUTPUT is the data that's sent in emails.
OUTPUT=""
HR_OUTPUT=""
#Counters
CHANGE_COUNT=0
DESTROY_COUNT=0
CREATION_COUNT=0
F_SKIPPED_COUNT=0
PERMCHANGE_COUNT=0
OWNERCHANGE_COUNT=0
#Delimiters
IFSB="$IFS"
IFSN="
"
#First we get a listing of all files.
LS_OUT="$($FIND_BIN $WATCH_DIR -type f -exec $LS_BIN $LS_ARGS {} +)"
PREVOUT="$(cat $WATCH_STATE_FILE)"
IFS="$IFSN"
#Format of the state file is as follows: $MD5SUM $OCTAL_PERMISSIONS $OWNER $GROUP $FILENAME
for file in $LS_OUT; do
IFS="$IFSB"
#| is used as a delimiter throughout this script due to the fact that it generally isn't used in filenames.
#Get the filename, octal permissions and owner/group
FILENAME="$(echo $file|sed "s|.* $WATCH_DIR|$WATCH_DIR|g")"
GREP_FNAME="$(echo $FILENAME|sed "s|\^|\\\^|g")"
PREV_STATE="$(echo "$PREVOUT"|$EGREP_BIN " $GREP_FNAME$")"
#Variable initialisation
RUN_CHECKS="YES"
TOOUT=""
CHECKOUT=""
FPERMS="000"
FOWNER="nobody nobody"
FINTEGRITY="d41d8cd98f00b204e9800998ecf8427e"
FILESTATE=""
SKIPFILE_OUT="$(echo "$GREP_FNAME"|$EGREP_BIN "$FILES_TO_SKIP")"
if [ $? -eq 0 -a ! -z "$SKIPFILE_OUT" ]; then
#We're supposed to skip this file, so we do.
RUN_CHECKS="NO"
F_SKIPPED_COUNT=$(($F_SKIPPED_COUNT+1))
else
if [ "$CHECK_FILE_PERMS_OWNER_CHANGED" == "YES" ]; then
FPERMS="$($STAT_BIN $STAT_ARGS "$FILENAME")"
FOWNER="$(echo $file|awk '{print $3 " " $4}')"
fi
#Check integrity with md5sum
if [ "$CHECK_FILES_CHANGED" == "YES" ]; then
FINTEGRITY="$(md5sum "$FILENAME"|awk '{print $1}')"
fi
CHECKOUT="$FILENAME (Perm: $FPERMS, Owner/Group: $FOWNER) -- "
fi
TOOUT="$FINTEGRITY $FPERMS $FOWNER $FILENAME"
if [ $? -ne 0 -o -z "$PREV_STATE" ] && [ "$RUN_CHECKS" == "YES" ]; then
#File didn't exist previously
CHECKOUT="$CHECKOUT$STR_FILECREATED"
FILESTATE="EFILENEW"
CREATION_COUNT=$(($CREATION_COUNT+1))
else
#File existed previously. First we check the md5sum
PREV_INTEGRITY="$(echo $PREV_STATE|awk '{print $1}')"
if [ "$PREV_INTEGRITY" != "$FINTEGRITY" ] && [ "$RUN_CHECKS" == "YES" ] && [ "$CHECK_FILES_CHANGED" == "YES" ]; then
#Integrity check failed, file contents were modified
CHECKOUT="$CHECKOUT$STR_FILECHANGED "
FILESTATE="EFILECHANGED"
CHANGE_COUNT=$(($CHANGE_COUNT+1))
fi
#Then we check permissions
PREV_PERMS="$(echo $PREV_STATE|awk '{print $2}')"
if [ "$PREV_PERMS" != "$FPERMS" ] && [ "$RUN_CHECKS" == "YES" ] && [ "$CHECK_FILE_PERMS_OWNER_CHANGED" == "YES" ]; then
#Permissions check failed, permissions were modified
CHECKOUT="$CHECKOUT$STR_PERMSCHANGED "
FILESTATE="$FILESTATE EPERMSCHANGED"
PERMCHANGE_COUNT=$(($PERMCHANGE_COUNT+1))
fi
#Then we check ownership
PREV_OWNERGROUP="$(echo $PREV_STATE|awk '{print $3 " " $4}')"
if [ "$PREV_OWNERGROUP" != "$FOWNER" ] && [ "$RUN_CHECKS" == "YES" ] && [ "$CHECK_FILE_PERMS_OWNER_CHANGED" == "YES" ]; then
#Ownership check failed, owner or group has changed
CHECKOUT="$CHECKOUT$STR_OWNCHANGED "
FILESTATE="$FILESTATE EOWNERCHANGED"
OWNERCHANGE_COUNT=$(($OWNERCHANGE_COUNT+1))
fi
fi
if [ ! -z "$FILESTATE" ] && [ "$RUN_CHECKS" == "YES" ]; then
HR_OUTPUT="$HR_OUTPUT$CHECKOUT$IFSN"
fi
OUTPUT="$OUTPUT$TOOUT$IFSN"
IFS="$IFSN"
done
IFS="$IFSB"
#At this point we've checked all files that currently exist. Let's now get a full list of all previous files and check if any are now deleted
#The list of files to skip does not apply here.
if [ "$CHECK_FILES_DESTROYED" == "YES" ]; then
IFS="$IFSN"
for file in $PREVOUT; do
IFS="$IFSB"
CHECKOUT=""
FILENAME="$(echo $file|sed "s|.* $WATCH_DIR|$WATCH_DIR|g")"
if [ ! -f "$FILENAME" ]; then
CHECKOUT="File deleted: $FILENAME"
DESTROY_COUNT=$(($DESTROY_COUNT+1))
fi
if [ ! -z "$CHECKOUT" ]; then
HR_OUTPUT="$HR_OUTPUT$CHECKOUT$IFSN"
fi
IFS="$IFSN"
done
IFS="$IFSB"
fi
#We've now completed all checks. Check if there's anything to send out, and email.
COUNT_OUT="$CREATION_COUNT new files, $CHANGE_COUNT modified files, $DESTROY_COUNT files were deleted, $PERMCHANGE_COUNT files with different permissions, $OWNERCHANGE_COUNT files with different ownership data. $F_SKIPPED_COUNT files were found, but skipped, and will not be included in the detailed log of events."
if [ ! -z "$HR_OUTPUT" ]; then
#We construct the email
EMAILOUT="Subject: $EMAIL_SUBJ
Date: $(date -u +"%a, %d %h %Y %T +0000")
From: $HOSTNAME <$EMAIL_FROM>
To: $EMAIL_TO <$EMAIL_ADDR>
$EMAIL_BODY_HEAD$IFSN$COUNT_OUT$IFSN$IFSN$EMAIL_BODY_DETAIL$IFSN$IFSN$HR_OUTPUT$IFSN$EMAIL_BODY_TAIL"
#Send the email
if [ "$NOTIFY_EMAIL" == "YES" ]; then
echo "$EMAILOUT"|$SENDMAIL_BIN $EMAIL_FROM $EMAIL_ADDR
fi
#Make a backup of the old statefile and write a new one
cp $WATCH_STATE_FILE $WATCH_STATE_FILE.previous
echo "$OUTPUT" > $WATCH_STATE_FILE
fi
#And we're done.