#!/bin/bash

set -o pipefail
set -e

BASE_PATH=/tmp/system-audit
GIT_DIR=/var/local/system-audit

function collect_hashes() {
  target=$1
  shift
  for dir in $@; do
    if ! [ -e "${dir}" ]; then
      echo "${dir}" >>${BASE_PATH}/missing
      wrap_git add --intent-to-add ${BASE_PATH}/missing
      continue
    fi

    find ${dir} -mindepth 1 \( -type f -or -type l \) -print0 | xargs -0 shasum -a 512 >>${target}
  done

  if [ -e ${target} ]; then
    wrap_git add --intent-to-add ${target}
  fi
}

function wrap_git() {
  git --work-tree=${BASE_PATH} --git-dir=${GIT_DIR} "$@"
  return $?
}

# Create target directory
mkdir -p ${BASE_PATH}

# Initialize the dir repo if not present
if ! [ -e "${GIT_DIR}" ]; then
  mkdir -p $(dirname ${GIT_DIR})
  wrap_git init
fi

if [ $# -lt 1 ]; then
  echo "Usage: $0 <collect|freeze|init|check>"
  exit 1
fi

case "$1" in

  "collect")
    # Remove old hash-files
    rm -rf ${BASE_PATH}/*

    # [OSX / Linux] Changing this script will cause a different behaviour
    # so also this needs to be monitored.
    collect_hashes ${BASE_PATH}/script $0

    # [OSX only] LaunchAgents and LaunchDaemons can be used to execute
    # programs on behalf of the user or the root user. They may be used
    # as attack vectors.
    collect_hashes ${BASE_PATH}/agents_daemons /System/Library/LaunchDaemons /Library/LaunchDaemons /System/Library/LaunchAgents /Library/LaunchAgents ~/Library/LaunchAgents

    # [OSX / Linux ] /etc (or /private/etc on OSX) does contain configuration
    # for system applications and might be used to change their behaviour.
    collect_hashes ${BASE_PATH}/etc /etc /private/etc

    # [OSX / Linux] Binary folders do contain the executables used by the
    # system itself. Exchanging them can cause harm to the system or leak
    # data.
    collect_hashes ${BASE_PATH}/bin /usr/bin /usr/local/bin ~/bin
    ;;

  "freeze")
    wrap_git commit -S -a -m "Status freeze as of $(date)"
    ;;

  "check")
    $0 collect
    $0 diff --exit-code
    echo "Everything is still in recorded state"
    ;;

  "init")
    if [ $($0 log --pretty=format:'%h [%G?]%d %s (%cr) <%an>' --abbrev-commit | wc -l) -gt 0 ]; then
      echo "The status was already initialized. Use 'collect' and 'diff' to review the state and 'freeze' to save it"
      exit 1
    fi

    $0 collect
    $0 freeze
    ;;

  *)
    wrap_git "$@"
    ;;

esac