#!/bin/bash
# ==============================================================================
#
# detectChanges version 0.5 written by Abi Arroyo
# Visit for updates
#
# This is subject to the GNU GPL version 2 released June 1991.
#
# Introduction
# ==============================================================================
#
# This script is a file integrity checking and management tool. Several
# applications exist in this product space, but none of them are written
# in a scripting language and as a result require package installation or
# compilation. This can cause some problems on systems where a compiler is
# not available or where administrative privileges have not been granted.
# While detectChanges does not provide as much functionality, it is sufficient
# for most users; and provides a simple command line interface.
#
# Application Notes
# ==============================================================================
#
# (1)
# Sometimes when the file is transfered between a Windows and Unix systems
# errors are encountered when trying to run the script. Sample errors are
# shown below:
#
# detectChanges.sh: line 2: $'\r': command not found
# detectChanges.sh: line 24: $'\r': command not found
# detectChanges.sh: line 25: $'\r': command not found
# detectChanges.sh: line 27: $'\r': command not found
# detectChanges.sh: line 28: syntax error near unexpected token `$'{\r''
# detectChanges.sh: line 28: `createMapping () {
#
# To fix this error open the script in vi and to convert the file to a
# Unix format by typing the following exactly as shown ":set ff=unix"
#
# (2)
# The storage directory ($dcStorage) may contain temporary storage files
# (*_diff.int) if the debug switch is enabled or if the application
# is stopped prematurely. These files may be safely deleted.
#
# (3)
# It may also be necessary to configure some variables listed below in the
# "user configurable variables" section. The only other requirement for
# running the script is having the following software available:
#
# * Bourne Shell
# * OpenSSL
# * find
# * grep
# * diff
# * readlink
#
# (4)
# The script has been tested on the following operating systems but should
# be supported on any OS that has the software listed above in section (3).
#
# CYGWIN_NT-5.1 DTC1200F32D2F1E 1.5.24(0.156/4/2) 2007-01-31 10:57 i686 Cygwin
#
# I want to expand the list of supported operating systems. If you
# successfully run this script I would appreciate if you sent me an e-mail
# with the output of `uname -a` for your system. Please submit an e-mail
# via the following web interface .
#
# (5)
# Current the application is able to detect changes based on the md5 and sha1
# digest algorithms, in addition to file permissions and user/group ownership.
# More checks can be added with ease. The following comment has been added to
# aide modifications: # ADD HERE FOR MORE CHECKS
#
# Todo
# ==============================================================================
#
# * Add support for encrypting checksum files
# * checksum support for integrity file (intFile) and check file (chkFile)
#
# Change log
# ==============================================================================
#
# 2007 08 21
# - cleaned up code
# - clarified variable names
# - more restrictive persmissions on script storage
# - changed checksum utility from md5 to openssl
# - improved documentation (description and fixing file format error)
# 2007 08 28
# - removed createMapping() function and developed better method
# - added debug mode
# - fixed bug detecting file modifications (moved from grep to diff)
# 2007 09 01
# - fixed bug detecting numChanges
# 2007 09 17
# - updated modification detection mechanism
# - updated reporting mechanism
# - updated web site form
# 2007 09 22
# - removed extra printing for number of changes that have been detected
# - fixed permissions error (found by Mikhail Ru)
# - added todo list
#
# ==============================================================================
# user configurable variables -----------------------------------------------------
#
# retrieve the name of the shell script
#
cmd="detectChanges"
#
# set the directory where checksums and configuration files are stored
#
dcStorage="./dcStorage"
#
# location of openssl
#
openssl="/bin/openssl"
# functions ------------------------------------------------------------------------
#
# convert relative path (../asdf/jkl/) to absolute path. also removes
# trailing directory path delimeter with usage of pwd
#
absPath () {
tmp_file=`readlink -f $1`
if [ -d "$tmp_file" ]; then
tmp_file="${tmp_file}/"
fi
echo "$tmp_file"
}
usage () {
echo -e "\n\t$cmd [ -init | -diff ] directory ... directoryN -exclude dirA -exclude dirB"
echo -e "\n\t\t-help\t\tprint this screen"
echo -e "\t\t-init\t\tinitialize checksums"
echo -e "\t\t-diff\t\tdetect checksums changes (must run -init first)"
echo -e "\t\t-exclude\texclude these directories (must be included with init and diff)"
echo -e "\t\t-verbose\tshow differences in files instead of only listing filename changes"
echo -e "\t\t-debug\t\tenable debug mode (most verbose, does not delete storage files)"
}
error () {
usage;
echo -e "\n\terror: ${*}"
exit -1
}
# internal variables ----------------------------------------------------------------
#
# time
#
theTime=`date +%Y%m%d_%H%M%S`
#
# report
#
report="${dcStorage}/${theTime}_fsChangeReport.txt"
#
# delimeter
#
delim=":"
# process command line arguments -----------------------------------------------
if [ -z "$1" ]; then
error "command line parameters not specified"
fi
while [ "$1" != "" ]; do
case $1 in
-init ) shift; init=true ;;
-verbose ) shift; verbose=true ;;
-diff ) shift; diff=true ;;
-debug ) shift; debug=true ;;
-dir ) shift; dir="$1"; shift ;;
-exclude )
# determine if file is a symbolic link
# if so, exclude symbolic link and actual directory
shift
path="`readlink -f $1`"
if [ -z "$exclude" ]; then
exclude="-wholename \"${path}\" -prune";
else
exclude="${exclude} -o -wholename \"${path}\" -prune"
fi
shift ;;
-v ) shift; verbose=true; shift ;;
-h | -help | --help ) usage; exit 1;;
* )
if [ ! -e $1 ]; then
error "unknown parameter: $1"
fi
input_path="$1 $input_path"; shift ;;
esac
done
if [ ! -z "${exclude}" ]; then
exclude="${exclude} -o"
fi
# error checking ---------------------------------------------------------------
if [ -z "$input_path" ]; then
error "directory not specified"
fi
if [ ! -e $dcStorage ]; then
mkdir -p $dcStorage
if [ ! -e $dcStorage ]; then
error "unable to create script storage directory: $dcStorage"
fi
fi
chmod u+rwx,og-rwx "${dcStorage}"
if [ -z $init ]; then
if [ -z $diff ]; then
error "initialization or comparison have not selected"
fi
fi
if [ ! -z $init ]; then
if [ ! -z $diff ]; then
error "cannot initialize and compare at the same time (-init or -diff, not both)"
fi
fi
#
# check to see if openssl is installed
#
if [ -x $openssl ]; then
$openssl list-message-digest-commands | grep -E -v "md5|sha1" > /dev/null
if [ $? -ne 0 ]; then
error "unable to locate ssl: set openssl variable in script: $openssl"
fi
else
error "unable to locate ssl: set openssl variable in script: $openssl"
fi
# generate data and perform comparison if selected -----------------------------
for i in `echo $input_path`; do
# determine absolute path of file
entry="`absPath $i`"
intFile="$dcStorage/`echo "$entry" | tr -d /`.int" # file containing the initialized data
chkFile="${dcStorage}/${theTime}_diff.int" # temporary data storage
#echo "storage directory: $dcStorage"
#echo "file with integrity data: $intFile"
#echo "comparison check file: $chkFile"
#echo "exclude expr: $exclude"
#echo "processing: $entry"
if [ ! -e "$intFile" ]; then
if [ ! -z $diff ]; then
error "unable to compare checksums since they have not been created: $entry"
fi
fi
OFS="${IFS}"
IFS=$'\n'
rm -rf $chkFile
echo ""
for j in `eval find \"$entry\" $exclude -print`; do
echo -n "."
# ADD HERE FOR MORE CHECKS
if [ -d "$j" ]; then # directories
perms=`ls -ald "$j" | awk '{ print $1 $3 $4 }'`
echo "${perms}${delim}${j}" >> $chkFile
else # files
md5=`$openssl md5 "$j" | sed -e s/.*\=\ //g`
sha1=`$openssl sha1 "$j" | sed -e s/.*\=\ //g`
perms=`ls -al "$j" | awk '{ print $1 $3 $4 }'`
echo "${md5}${delim}${sha1}${delim}${perms}${delim}${j}" >> $chkFile
fi
done
IFS="${OFS}"
if [ ! -z $diff ]; then
if [ ! -z $verbose ] || [ ! -z $debug ]; then
echo -e "\n\ndiff ${intFile} ${chkFile}"
diff "$intFile" "$chkFile"
fi
numChanges=`diff "$intFile" "$chkFile" | grep ${delim} | sed -e s/.*\\${delim}//g | uniq | wc -l | awk '{ print $1 }'`
if [ $numChanges -gt 1 ]; then
echo -e "\n\nthe following file(s) have been modified\n"
diff "$intFile" "$chkFile" | grep ${delim} | sed -e s/.*\\${delim}/\ \ \ /g | uniq | grep -v "$intFile" > "$report"
#
# This note explains why the "grep -v" statement is listed above:
#
# The mechanism used for detecting changes is that you create a
# list of checksums and then put them into a file. However, it is not
# possible to validate the checksum file containing the list of all of
# the checksums.
#
# When you compute the checksum for the file and then attempt to add the
# file to itself, then the checksum changes.
#
# One solution is to place the checksum in another file and then perform
# a comparison on the file and the new checksum comparison file that is
# created.
#
cat "$report"
chmod u+r,u-wx,og-rwx "$report"
echo ""
else
echo -e "\n\nno changes have been found\n"
fi
else
mv "$chkFile" "$intFile"
echo -e "\n\nfile integrity information saved to: $intFile"
chmod u+r,u-wx,og-rwx "$intFile"
fi
if [ -z $debug ]; then
rm -rf "$chkFile"
else
chmod u+r,u-wx,og-rwx "$chkFile"
fi
done