#!/bin/bash
# nmap-scanner.sh V1.0
#  My friend N8 had the great idea of nmaping a netblock and then running a diff to see if any new ports (HACKERS!!)
#  appeared on his network or any old ports had vanished (daemon died!!). He posted about it on his blog page at
#  http://www.mybrainhurts.com/blog/2009/03/automated-nmap-scans.html
#  I really liked his idea but struggled a little with the implementation since i wanted the script to be noisy
#  at first (for testing) and quiet later. I started hacking in commandline switches and changing the generic
#  Bourne shell code into BASH code, and here is the no longer terse result.
#
#REQUIREMENTS
#  *Nmap
#  *Cron (for automation)
#
#INSTALL
# The first time you run the script you need to start it with the -i option to initilize the data files. You will get an error otherwise.
# This script is designed to be placed in your root crontab file (via crontab -e), here is an example entry (the -q makes it quiet).
#     32 15 * * * /usr/local/bin/nmap-scanner.sh -q
# The following 4 options are overwriteable via commandline switches, run script with -h for options.

EMAIL=""                                # Optional email address to notify, defaults to STDOUT if none listed
BASE_DIR="/var/log/nmap/scans"          # Directory to store the nmap logfiles
NETWORKS=(127.0.0.1 192.168.0.1-254)    # Networks to scan, space delimited for multiple networks
INIT=0                                  # Set to 1 via -i option upon first run of script to seed the logfile

#function to handle error exiting
die() {
   echo "ERROR: $@" 1>&2
   exit 1;
}

#processes commandline options
while getopts ":qid:n:e:h" OPT; do
   case "$OPT" in
      q) QUIET=1;;
      i) INIT=1;;
      d) DIR="$OPTARG";;
      n) NETWORKS="$OPTARG";;
      e) EMAIL="$OPTARG";;
      h) HELP=1;;
     \?) HELP=1;;
   esac
done

#print help message and exit
if [[ -n ${HELP} ]];then
   echo "$0 usage:"
   cat <<EOF
         -q    Do not print progress messages to stdout
         -i    Seed logfiles, for first run of script
         -d    Base directory for logfiles
         -n    Networks to scan (space delimited) ex. -n "127.0.0.1 192.168.0.1-254"
         -e    Optional email address to mail to, defaults to STDOUT if none listed
EOF
      die "Unrecognized option(s): $@"
fi 
  

#Check that nmap is installed and in their current path
if ! command -v nmap >/dev/null 2>&1; then
   die "Sorry $0 requires NMAP to be installed and in your path of ($PATH)"
fi

mkdir -p "${BASE_DIR}" >/dev/null 2>&1

TODAY=$(date +%Y%m%d-%s)
for NETWORK in ${NETWORKS[@]}; do
   DIR="$BASE_DIR/${NETWORK}"
   mkdir -p "$DIR/state/"  #create a directory for each network to be scanned, and a state directory for tracking last scan symlinks
   if ! mv -f ${DIR}/state/new ${DIR}/state/old; then   #Rename the symlink for the newest scan to old (overwriting the old symlink in the process)
      if [[ ${INIT} -eq 0 ]]; then 
         die "An error occured while trying to roll off the log of the last scan. If this is first time running $0 use the --init option to override"
      fi
   fi
   if [[ -z $QUIET ]];then
      echo -ne "\nScanning network:${NETWORK}:\n"
   fi
   if nmap -R -sS ${NETWORK} -oN $DIR/${TODAY}.nmap >/dev/null 2>&1; then    #Actually perform the nmap scan
      ln -fs ${DIR}/${TODAY}.nmap ${DIR}/state/new     #create the .new symlink for this network
      if [[ ${INIT} -eq 1 ]]; then
         ln -fs ${DIR}/${TODAY}.nmap ${DIR}/state/old    #if this is the first run (--init) then seed old with new
      fi
   else
      die "An errror occured while trying to nmap the network:${NETWORK}"
   fi

   #check for diffrences between this run and the last run
   DIFF=$(diff -C0 --show-function-line='Interesting' --ignore-matching-lines='^#' ${DIR}/state/new ${DIR}/state/old |grep -v '\*\*\*' |grep -v '\-\-\-')
   if [[ $? -eq 2 ]];then
      die "the diff of ( ${DIR}/state/new ${DIR}/state/old) failed to execute properly"
   fi

   if [[ -n ${DIFF} ]]; then  #if there are diffrences alert the user
      if [[ -n ${EMAIL} ]]; then 
         echo ${DIFF} | mail -s "Change Detected for ${NETWORK}" ${EMAIL}
      else
         echo "The following changes were detected for network: ${NETWORK}"
         echo "${DIFF}"
      fi
   fi
done