#!/usr/bin/bash
# Copyright (C) 2015 sys4 AG
#
# project     :
# file        : /usr/local/sbin/postproof
# date        : 19.01.2015
# created by  : jz@sys4.de
# modified at :
# modified by :
# description :
#

#- locales
export LANG=POSIX

#- root privileges required
test "$(id -u)" -ne 0 && echo "This program requires root privileges" && exit

#- test for binaries
RETVAL=0
MESS=''
for bin in postqueue postsuper postcat gpg date mkdir md5sum sha1sum sha256sum grep hostname bzip2 gzip find tar; do
  test -x "$(which ${bin})"
  if [[ $? -ne 0 ]]; then
    RETVAL=1
    MESS="command ${bin} not found"
  fi
done
#- editor
test -n "$EDITOR" && test -x "$(which "${EDITOR}")" && _editor=$EDITOR
test -z "$_editor" && _editor=vi
test -x "$(which $_editor)" || RETVAL=1 && MESS="command $_editor not found"


#- some vars
START=$(date '+%F_%T')
FQDN=$(hostname -f)
BASE_DIR='/var/lib/postproof'
PROT_FILE='protocol.txt'
INCIDENT_FILE='incident.txt'
PRG=$(basename "$0")
CONFIG_DIR=''
COMPRESSOR=''
INCIDENT=''
INCIDENT_EDITOR=0
PURGE=0
SPLITT=0
VERBOSE=0
RECIPIENT=''
SENDER='root'
DO=0
# binaries
_postqueue="postqueue"
_postsuper="postsuper"
_postcat="postcat"
_tar='tar cf'


##- help
function helper ()
{
  if [ "${RETVAL}" -ne 0 ]
  then
    echo -e "\n    ${MESS}"
  fi
  echo -e "\n    usage = ${PRG} [-m <msg> ] [-M] [-j] [-z] [-p] [-c <config_dir>] [-s] [-v] [-h] [-o <out_dir>] [-n <recipient>] envelope-sender [ envelope-sender]\n"
  echo "            -c    Search queue from instance identified by <config_dir> (default: /etc/postfix)"
  echo "            -h    Print this help"
  echo "            -j    Compress incident data using bzip2 (excludes -z)"
  echo "            -m    Use <msg> as incident message"
  echo "            -M    Use \$EDITOR to create incident message"
  echo "            -o    Save data to named output directory"
  echo "            -p    Purge mail from queue after preservation of evidence"
  echo "            -n    Notify <recipient> about incident"
  echo "            -s    Store messages grouped by sender to subdirectories"
  echo "            -v    Print verbose output"
  echo "            -z    Compress incident data using gzip (excludes -j)"
  echo ""

  exit "$RETVAL"
}


##- logger, log messages
function logger()
{
  echo "${MESS}" >> "${BASE_DIR}/${DIR_NAME}/${PROT_FILE}"
  test "${VERBOSE}" -eq 1 && echo "${MESS}"
}


##- process abuse
function abuse()
{
  DO=1
  #- stop mail
  MESS="stop delivering mail, queue_id: ${QUEUE_ID} "; logger
  $_postsuper -h "${QUEUE_ID}" > /dev/null 2>&1

  #- save queue
  MESS="save queue_id: ${QUEUE_ID}"; logger
  $_postcat -q "${QUEUE_ID}" > "${BASE_DIR}/${DIR_NAME}/raw/${QUEUE_ID}"

  #- save message in maildir format
  MESS="save mail to ${BASE_DIR}/${DIR_NAME}/messages/new/${START}.${FQDN}.${QUEUE_ID}.msg (maildir format)"; logger
  $_postcat -h -q "${QUEUE_ID}" > "${BASE_DIR}/${DIR_NAME}/messages/new/${START}.${FQDN}.${QUEUE_ID}.msg"
  $_postcat -b -q "${QUEUE_ID}" >> "${BASE_DIR}/${DIR_NAME}/messages/new/${START}.${FQDN}.${QUEUE_ID}.msg"

  #- delete mail
  test "${PURGE}" -eq 1 && MESS="delete mail with queue_id ${QUEUE_ID}"; logger
  test "${PURGE}" -eq 1 && $_postsuper -d "${QUEUE_ID}"
  MESS=''; logger
}


##- report actions
function report()
{
  #- incident file
  echo -e "#- Incident -#" > "${BASE_DIR}/${DIR_NAME}/${INCIDENT_FILE}"
  echo -n "Date: " >> "${BASE_DIR}/${DIR_NAME}/${INCIDENT_FILE}"
  date '+%F_%T' >> "${BASE_DIR}/${DIR_NAME}/${INCIDENT_FILE}"
  echo "Count Envelope From: $COUNT_FROM" >> "${BASE_DIR}/${DIR_NAME}/${INCIDENT_FILE}"
  echo "Count Queue IDs: $COUNT_QUEUE_ID" >> "${BASE_DIR}/${DIR_NAME}/${INCIDENT_FILE}"
  echo -n "Description: " >> "${BASE_DIR}/${DIR_NAME}/${INCIDENT_FILE}"
  test -n "${INCIDENT}" && echo "${INCIDENT}" >> "${BASE_DIR}/${DIR_NAME}/${INCIDENT_FILE}"
  test "${INCIDENT_EDITOR}" -eq	1 && $_editor "${BASE_DIR}/${DIR_NAME}/${INCIDENT_FILE}"

  #- create hash for every file
  MESS="build checksums for queue_id: ${QUEUE_ID}"; logger
  _pwd=$(pwd)
  cd "${BASE_DIR}"
  files=$(find "${DIR_NAME}" -type f)
  for file in $files
  do
    echo -n "md5 " >> "${BASE_DIR}/${DIR_NAME}/integrity.txt"
    md5sum "${file}"  >> "${BASE_DIR}/${DIR_NAME}/integrity.txt"
    echo -n "sha1 " >> "${BASE_DIR}/${DIR_NAME}/integrity.txt"
    sha1sum "${file}"  >> "${BASE_DIR}/${DIR_NAME}/integrity.txt"
    echo -n "sha256 " >> "${BASE_DIR}/${DIR_NAME}/integrity.txt"
    sha256sum "${file}"  >> "${BASE_DIR}/${DIR_NAME}/integrity.txt"
  done

  #- create hash for directory
  MESS="build directory checksums for queue_id: ${QUEUE_ID}"; logger
  files=$(find "${DIR_NAME}" -ls)
  cd "${_pwd}"
  md5=$(echo "${files}" | md5sum)
  sha1=$(echo "${files}" | sha1sum)
  sha256=$(echo "${files}" | sha256sum)
  echo "md5 ${QUEUE_ID} ${md5}" >> "${BASE_DIR}/integrity/${DIR_NAME}.chksum"
  echo "sha1 ${QUEUE_ID} ${sha1}" >> "${BASE_DIR}/integrity/${DIR_NAME}.chksum"
  echo "sha256 ${QUEUE_ID} ${sha256}" >> "${BASE_DIR}/integrity/${DIR_NAME}.chksum"

  #- do backup
  if test "{$COMPRESSOR}" == "z" || test "$COMPRESSOR" == "j"
  then
    test "${COMPRESSOR}" == "z" && _tar='tar czf' && SUFFIX='tgz'
    test "${COMPRESSOR}" == "j" && _tar='tar cjf' && SUFFIX='tar.bz2'
    MESS="Backup: ${QUEUE_ID}"; logger
    MESS=''; logger
    $_tar "${BASE_DIR}/backups/${DIR_NAME}.${SUFFIX}" "${BASE_DIR}/${DIR_NAME}" "${BASE_DIR}/integrity/${DIR_NAME}.chksum" > /dev/null
  fi
}


##- get directory name
function get_dir_name()
{
  DIR_NAME=$(date '+%F_%T.%s').$(tr -dc 'a-z0-9' < /dev/urandom | head -c 4 | xargs)
  mkdir -p "${BASE_DIR}/${DIR_NAME}/raw"
  mkdir -p "${BASE_DIR}/${DIR_NAME}/messages/cur"
  mkdir -p "${BASE_DIR}/${DIR_NAME}/messages/new"
  mkdir -p "${BASE_DIR}/${DIR_NAME}/messages/tmp"
}


##- send mail
function sendMail () {

cat <<EOF | /usr/lib/sendmail -i -t
From: $SENDER
To: $RECIPIENT
Subject: [$PRG] processed $COUNT_QUEUE_ID incidents

#- processed incidents -#
Date: $START
Count Envelope From: $COUNT_FROM
Count Queue IDs: $COUNT_QUEUE_ID

Processed Incidents:
$ALL_INCIDENT

EOF
}
#/


##- main
#- missing command
test "$RETVAL" -ne 0 && helper

#- arguments and options
while getopts ":c:hjm:Mo:pn:svz" opt; do
  case $opt in
    c)
      test -z "${OPTARG}" && MESS="option -c require a valid config directory" && RETVAL=2 && helper
      ! test -d "${OPTARG}" && MESS="${OPTARG} is no valid config directory" && RETVAL=2 && helper
      CONFIG_DIR="${OPTARG}"
      ;;
    h)
      helper
      ;;
    j)
      COMPRESSOR='j'
      ;;
    z)
      test -n "${COMPRESSOR}" && MESS="you can't use bzip2 and gzip at the same time" && RETVAL=3 && helper
      COMPRESSOR='z'
      ;;
    m)
      test -z "${OPTARG}" && MESS="option -m require a incident description" && RETVAL=2 && helper
      INCIDENT=$OPTARG
      ;;
    M)
      INCIDENT_EDITOR=1
      ;;
    o)
      BASE_DIR=$OPTARG
      ;;
    p)
      PURGE=1
      ;;
    n)
      RECIPIENT=$OPTARG
      ;;
    s)
      SPLITT=1
      ;;
    v)
      VERBOSE=1
      ;;
    :)
      test "$OPTARG" == 'c' && MESS="option -c require a valid config directory" && RETVAL=2 && helper
      test "$OPTARG" == 'o' && MESS="option -o require a valid output directory" && RETVAL=2 && helper
      test "$OPTARG" == 'r' && MESS="option -r require a valid recipient" && RETVAL=2 && helper
      test "$OPTARG" == 'm' && MESS="option -m require a incident decription" && RETVAL=2 && helper
      ;;
    *)
      MESS="unknown option -${OPTARG}" && RETVAL=2 && helper
      ;;
  esac
done
shift $((OPTIND-1))

#- no argument
test $# -eq 0 && MESS="no envelope-sender" && RETVAL=3 && helper

#- select postfix instance
test -n "$CONFIG_DIR" && _postqueue="postqueue -c $CONFIG_DIR" && _postsuper="postsuper -c $CONFIG_DIR" && _postcat="postcat -c $CONFIG_DIR"

#- working directory
test -e "${BASE_DIR}" && ! test -d "${BASE_DIR}" && MESS="name ${BASE_DIR} is a file, not a directory" && RETVAL=3 && helper
test -d "${BASE_DIR}" || mkdir -p "${BASE_DIR}"
test $? -ne 0 && MESS="could not create output directory ${BASE_DIR}" && RETVAL=3 && helper
#- integrity directory
test -e "${BASE_DIR}/integrity" && ! test -d "${BASE_DIR}/integrity" && MESS="name ${BASE_DIR}/integrity is a file, not a directory" && RETVAL=3 && helper
test -d "${BASE_DIR}/integrity" || mkdir -p "${BASE_DIR}/integrity"
test $? -ne 0 && MESS="could not create integrity directory ${BASE_DIR}/integrity" && RETVAL=3 && helper
#- backup directory
test -e "${BASE_DIR}/backups" && ! test -d "${BASE_DIR}/backups" && MESS="name ${BASE_DIR}/backups is a file, not a directory" && RETVAL=3 && helper
test -d "${BASE_DIR}/backups" || mkdir -p "${BASE_DIR}/backups"
test $? -ne 0 && MESS="could not create backup directory ${BASE_DIR}/backups" && RETVAL=3 && helper


##- main loop
MESS=''
QUEUE_IDS=''
DIR_NAME=''
COUNT_FROM=0
COUNT_QUEUE_ID=0
ALL_INCIDENT=''
for from in "$@"
do
  COUNT_FROM=$((COUNT_FROM + 1))
  #- log
  test "${SPLITT}" -ne 0 && get_dir_name
  MESS="searching for $from"

  #- search envelope_from
  QUEUE_IDS=$($_postqueue -p | grep -E " $from$" | grep -v '!' | cut -f1 -d' ')

  #- found envelope_from
  test -n "${QUEUE_IDS}" && test -z ${DIR_NAME} && test "${SPLITT}" -eq 0 && get_dir_name

  #- multiple directories
  test -n "${QUEUE_IDS}" && test "${SPLITT}" -eq 1 && COUNT_QUEUE_ID=0 && get_dir_name

  logger
  test -n "${QUEUE_IDS}" && MESS="found mails from $from"; logger
  MESS='-----------------------------'; logger
  MESS=''; logger
  #- for every envelope from
  for QUEUE_ID in $QUEUE_IDS
  do
    QUEUE_ID=${QUEUE_ID%\*}
    COUNT_QUEUE_ID=$((COUNT_QUEUE_ID + 1))
    ALL_INCIDENT="${ALL_INCIDENT}sender: ${from} with queue id: ${QUEUE_ID}
"
    abuse
  done

  #- report
  test "${DO}" -eq 1 && test "${SPLITT}" -ne 0 && report
done

test "${DO}" -eq 1 && test "${SPLITT}" -eq 0 && report
test -n "${RECIPIENT}" && test "${DO}" -eq 1 && sendMail

#- found no mails
test "${DO}" -eq 0 && echo "no message found"

exit 0

#/
