#!/bin/sh
#
# $Id$
#
# NAME
#       sshaide.sh - SSH/AIDE remote integrity monitoring script
#
# SYNOPSIS
#       sshaide.sh  -check|-init  ALL|<machine-list>
#
# DESCRIPTION
#       sshaide.sh uses AIDE and SSH to remotely run integrity checks
#       on ALL configured client systems or those specifically listed on
#       the command line from a centralized manager station.  sshaide.sh
#       stores all binaries, databases and reports on a secure, centralized
#       manager station.  Database initialization or periodic checks are
#       run on demand or via cron jobs from the manager stations based on
#       local policy requirements.
#
#       sshaide.sh requires a valid account on the remote system and uses
#       SSH RSA authentication with public/private password-less key pairs
#       to obtain automated, scripted access to a remote system.  Naturally
#       the account(s), sshaide.sh keys and manager system must be heavily
#       protected from compromise.  To minimize potential problems, it is
#       recommended that sshaide.sh use non-privileged accounts.  While
#       this limits access to verify some files and diretories on remote
#       systems, we believe it is an acceptable trade-off.  Most critical
#       files and directories can be effectively monitored without having
#       privileged access.  It is recommended that an unprivileged, but
#       dedicated account on the manager station also be setup to manage
#       AIDE databases, AIDE reports, remote logins and other sshaide.sh
#       requirements.
#
#       Remote clients must have the public SSH RSA key that will be used
#       by the sshaide.sh manager.  The sshaide.sh manager must have the
#       managed client's SSH server RSA public key in known_hosts or
#       hostkeys file.  Refer to your SSH documentation for instructions
#       on setting up public SSH RSA keys.
#
# OPTIONS
#       The option must be given in the proper order and with proper
#       syntax.
#
#       -init   Initialize or re-initialize the AIDE database for the
#               listed host or hosts.
#
#       -check  Run an integrity check on the specified system or systems.
#               The database for any host being checked must have already
#               been intialized.
#
# DIRECTORIES and FILES
#       ~/
#               This is the home directory of the user running sshaide.sh.
#               By default, this is retrieved from the $HOME environment
#               variable.
#
#       ~/bin
#               The directory where the sshaide.sh script and AIDE
#               binaries are stored.  Required.
#
#       ~/bin/sshaide.sh
#               The sshaide.sh program.  This file.  Required.
#
#       ~/bin/aide.[platform]
#               The AIDE binary for a [platform].  For example, a Linux
#               2.4 binary may be named aide.linux-2.4.  These binaries
#               will be linked to from the independent client directories
#               based on their platform requirements.  Required.
#
#       ~/configs
#               The directory where the AIDE configuration files are
#               stored.  Common AIDE configurations are stored here and
#               can be linked to from the independent client directories
#               based on policy requirements.  Required.
#
#       ~/reports
#               This directory will store the initialization logs and
#               integrity check reports.  Integrity reports will be
#               tar.gz'd by year-month-day-hour.  Required, but created
#               automatically by sshaide.sh.
#
#       ~/clients
#               This is the parent directory for all client hosts being
#               managed.  Required.
#
#       ~/clients/[client-host]
#               This directory is a specific client host to be managed by
#               sshaide.sh. [client-host] is a host name.  Short host name
#               is usually sufficient, but a fully qualified domain name
#               may be used if there may be host name overlap from different
#               subdomains.  Required for each client to be managed by
#               sshaide.sh.
#
#       ~/clients/[client-host]/aide.db_[client_host]
#               This file is the AIDE database for the [client-host] being
#               managed by sshaide.sh.  Required, but created automatically
#               by the -init process.
#
#       ~/clients/[client-host]/aide.db_[client-host].old
#               This file is the previous AIDE database before the last
#               -init process.  Not required.  Created automatically after
#               the second or additional database initialization.
#
#       ~/clients/[client-host]/sshaide.conf
#               This file contains client specific configuration information.
#               Optional.  The following three options are available:
#
#                 emaillist         comma-separated-list-of-addresses
#                       This option specifies the email addresses that
#                       sshaide.sh output should be delivered to.
#                 homedir           full-home-diretory-path-on-client
#                       This option specifies the fully qualified path
#                       used on the client host.  This would be equivalanet
#                       to the $HOME environment variable on the client
#                       system.
#                 userid             remote-user-id
#                       This option specifies the remote login id with
#                       which to login to the remote system with.
#
#               All configuration options are optional, but if present,
#               they must be begin in column 1 with whitespace separting
#               the desired value(s).
#
#       ~/clients/[client-host]/reinit
#               The existence of this file indicates that the AIDE database
#               for this client host should be reinitialized through the
#               -init process on the next run.  Simply `touch` this file
#               whenever you want to reinitialize the client host database.
#               This file will be automaticaly removed after the next -init
#               process.  Optional.
#
#       ~/clients/[client-host]/aide.conf
#               This is a soft link to the appropriate AIDE configuration
#               file in ~/configs.   The following two lines are required
#               for each configuration file:
#
#                 database=file:./aide.db
#                 database_out=file:./aide.newdb
#
#       ~/clients/[client-host]/aide
#               This is a soft link to the appropriate AIDE binary for
#               the client host platform in ~/bin.  Required.
#
#       ~/tmp
#               This is a temporary work directory for sshaide.sh.
#               Required.
#
# Original concept and coding from:
#   Judith A Freeman <jaf@uchicago.edu>
#   University of Chicago
#   Network Security Center <network-security@uchicago.edu>
#   <http://security.uchicago.edu>
#   28 June 1998 to 16 May 2000
#
# Updates by:
#   John Kristoff <jtk@northwestern.edu>
#   <http://aharp.ittns.northwestern.edu>
#   Northwestern University
#   Telecommunications and Network Services
#
#   Sam Stelfox <sam@stelfox.net>
#
# 2003-12-03,jtk: updated for AIDE v0.10 and Linux
#   newly packaged as sshaide.sh
#   adjusted default path to something more reasonable for linux
#   replaced tripwire references with aide naming conventions
#   replaced hard coded root user id with $userid variable from whoami
#   added LC_ALL=C for grep to work with traditional [] interpretations
#   added a cd to remote_aidedir on remote machine
#   forced remote_aidedir directory creation (with 'mkdir -p')
#   added quotes to ssh commands
#   changed the email subject and header format
#   changed mail delivery and email creation handling
#   minor commenting edits
#   adjusted wordlist for Linux and Solaris, exiting if file not found
#   removed $1 for wordlist
#   implemented config file for remote_aidedir and emaillist per machine
#   changed reinit check from read (-r) to write (-w) check
#   fixed tar/gzip'ing of reports only on -check mode
#   removed unncessary root directory variable, use just aidedir
# 2003-12-06,jtk:minor configuration updates
#   added userid option to config and created $useriddefault
#   set default remote home directory with $homedefault
# 2003-12-16,jtk: fixed sshaide.conf usage
#   added doc about userid config in sshaide.conf
#   changed order of sshaide.conf config options so remote_aidedir works
# 2004-02-12,jtk: minor doc editing
# 2011-08-24,sgs: Some reformatting and fixing around newer versions of AIDE

### Basic setup

# Get a limited path
PATH=/bin:/usr/bin:/usr/local/bin:/usr/ucb

# For debugging only
# set -x

### Local variable declarations

# set the remote username to login and run aide as
useriddefault=`whoami`

# Who gets the mail if not set in client dir?
maildefault=root@localhost

# remote home directory
homedefault=/home/${useriddefault}

# $date in the form year-month-day-hour
date=`date +%Y-%m-%d-%H`

# Where are we running out of
aidedir=${HOME}

# Setup local directories and files for use
clientdir=${aidedir}/clients
tmpdir=${aidedir}/tmp

progname=`basename $0`

### Functions

# Give usage statement
usage() {
  echo ""
  echo "Usage: `${progname}` <run-mode> ALL|<machine-list>"
  echo "  run-mode: -init | -check"
  echo "  machine-list: space separated list in quotes"
  echo ""
}

## gen_rand_word  - returns a semi-random word
##    only returns words that are all lowercase letters

gen_rand_word() {
  # Set the word list
  if test -r "/usr/share/dict/words" ; then
    # For Linux
    _wordlist="/usr/share/dict/words"
  elif test -r "/usr/dict/words" ; then
    # For Solaris
    _wordlist="/usr/dict/words"
  else
    echo ERROR: words file not found!  Exiting...
    exit 0
  fi

  _randnum=`date +%H%S%Y%m%H%d%S%S`
  _listlines=`cat ${_wordlist} | wc -l`
  _linenum=`expr ${_randnum} % ${_listlines}`

  # If we picked line 0, change it to 1 'cause line 0 doesn't exist
  if test ${_linenum} -eq 0; then
    _linenum=1
  fi

  _randword=`grep -n . ${_wordlist} | grep "^${_linenum}:" | cut -d: -f2`

  # If $_randword has anything other than lower-case chars, try again
  (echo ${_randword} | LC_ALL=C grep '[^a-z]' 2>&1 >> /dev/null \
    && gen_rand_word ) || \

  # Return the word
  echo ${_randword}
}

init_cmds() {
  if test ! -d ${aidedir}/reports/initlogs/ ; then
    mkdir -p ${aidedir}/reports/initlogs/
  fi

  ssh -l $userid $machine "(umask 077 ; cd ${remote_aidedir}; ${remote_aidedir}/aide --init --config=${remote_aidedir}/aide.conf 2>&1 | tee ${remote_aidedir}/initoutput >> /dev/null)"

  # Copy output back to file
  mkdir -p ${tmpdir}/initoutput/${date}
  scp -q ${userid}@${machine}:${remote_aidedir}/initoutput ${inittmp}/${machine}

  # backup old database if it exists
  if test -r ${clientdir}/${machine}/aide.db_${machine} ; then
    mv ${clientdir}/${machine}/aide.db_${machine} ${clientdir}/${machine}/aide.db_${machine}.old
  fi

  scp -q ${userid}@${machine}:${remote_aidedir}/aide.newdb ${clientdir}/${machine}/aide.db_${machine}
}

check_cmds() {
  scp -q $db ${userid}@${machine}:${remote_aidedir}/aide.db
  ssh -l $userid $machine "umask 077 && cd ${remote_aidedir} && ${remote_aidedir}/aide --config=${remote_aidedir}/aide.conf 2>&1 | tee ${remote_aidedir}/report >> /dev/null"

  # Copy output back to file
  if test ! -d ${aidedir}/reports/${date} ; then
    mkdir ${aidedir}/reports/${date}
  fi
  scp -q ${userid}@${machine}:${remote_aidedir}/report $reports/${machine}
}

### The program

# From the commandline
case $# in
  2) mode=$1; thehosts=$2 ;;
  *) usage; exit 1 ;;
esac

# Set mode specific variables
case $mode in
  -init)  initlogs=${aidedir}/reports/initlogs
          inittmp=${tmpdir}/initoutput/${date}
          mail_fordir=${inittmp} ;;
  -check) reports=${aidedir}/reports/${date}
          mail_fordir=${reports} ;;
esac

case $thehosts in
  ALL) forcmd=`ls ${clientdir}` ;;
    *) forcmd=$thehosts ;;
esac

for machine in $forcmd ; do
  sleep 2  # so we get a different random word

  (    ## background it (this is so it runs in parellel)
    # Set up local directories and files for use
    config=${clientdir}/${machine}/aide.conf
    db=${clientdir}/${machine}/aide.db_${machine}
    binary=${clientdir}/${machine}/aide
    log=${clientdir}/${machine}/log
    sshaide_conf=${clientdir}/${machine}/sshaide.conf

    # Set up temporary directory name for remote machine
    rand_word=`gen_rand_word`

    # Apply client host configuration options
    if  test ! -r ${sshaide_conf}; then
      remote_aidedir=${homedefault}/${rand_word}.$$
      mailrcpts=${maildefault}
      userid=${useriddefault}
    else
      # Get the email addresses to send reports to
      grep '^emaillist' ${sshaide_conf}

      if [ $? != 0 ]; then
        mailrcpts=${maildefault}
      else
        mailrcpts=`grep -m1 '^emaillist' ${sshaide_conf} |
          awk '{print $2}'`
      fi

      # Get the remote user id
      grep '^userid' ${sshaide_conf}
      if [ $? != 0 ]; then
        userid=${useriddefault}
      else
        userid=`grep -m1 '^userid' ${sshaide_conf} |
          awk '{print $2}'`
      fi

      # Get home directory to use on remote machine
      grep '^homedir' ${sshaide_conf}
      if [ $? != 0 ]; then
        remote_aidedir=/home/${userid}/${rand_word}.$$
      else
        remote_aidedir=`grep -m1 '^homedir' ${sshaide_conf} |
          awk '{print $2}'`/${rand_word}.$$
      fi
    fi

    # Do the dirty work
    ssh -l $userid $machine "mkdir -p $remote_aidedir"
    scp -q $config ${userid}@${machine}:${remote_aidedir}
    scp -q $binary ${userid}@${machine}:${remote_aidedir}

    case $mode in
      -init)   init_cmds ;;
      -check)  check_cmds ;;
    esac

    # Delete remote directory
    ssh -l $userid $machine "rm -rf $remote_aidedir"

    # If $mail_fordir doesn't exist, don't continue
    if test ! -d "${mail_fordir}"; then
      echo "${progname}:${mail_fordir} doesn't exist,"
      echo "exiting now, not sending mail"
      exit 1
    fi

    ### Mail reports out
    cat ${mail_fordir}/${machine} |
      mail -s "### AIDE ${mode} ${machine} ${date}" ${mailrcpts}
  )
done

# Wait for all bg processes to finish before continuing
wait

# Tar and compress the reports
if test $mode = -check; then
  tar cf ${reports}.tar ${reports}
  rm -rf ${reports}
  gzip -9 ${reports}.tar
fi

# If mode is check, examine clientdir for reinit file, and reinitialize if it
# exists
if test $mode = -check ; then
  for host in $forcmd ; do
    if test -w ${clientdir}/${host}/reinit ; then
      ${aidedir}/bin/${progname} -init ${host} &
      rm ${clientdir}/${host}/reinit
    fi
  done
fi

### Clean up init stuff
if test $mode = -init; then
  # Concatenate inittmp directories into initlogs
  for host in `ls -A ${mail_fordir}`; do
    (
      echo "********************************************"
      echo ${host} $date ${mode}
      echo "********************************************"
      echo ""
      cat ${mail_fordir}/${host}
      echo ""
    ) | tee -a $initlogs/`date +%Y-%m` >> /dev/null
  done

  # Delete inittmp directory
  rm -rf ${tmpdir}/initoutput
fi
