#!/bin/bash

verwendung() {
  >&2 echo 'sendmailadvanced encrypts emails and generates stamps either in situ or in a pipe. For that it uses hashcash and gnupg.'
  >&2 echo ''
  >&2 echo 'Usage: sendmailadvanced [OPTIONS]'
  >&2 echo '  -h,--hook=hook      activate hook; "ALL" chooses all available hooks'
  >&2 echo '  -i,--inline=file    enhance email file in situ.'
  >&2 echo '                        "--inline -" has the same effect as "-t".'
  >&2 echo '                        Conflicts with -t|--no-inline.'
  >&2 echo '  -t,--no-inline      enhance email from stdin to stdout.'
  >&2 echo '                        Conflicts with -i|--inline.'
  >&2 echo '  -e,--[no-]encrypt   do (not) encrypt'
  >&2 echo '  -s,--[no-]stamp     do (not) stamp'
  >&2 echo \
'  --help       display this help and exit
  --version    display version and exit'
  exit 1
}

neueAdressaten() {
  oldIFS="${IFS}"

  IFS=','
  for adressat in ${adressatenString}; do
    if [[ "${adressat}" == *"<"*">"* ]]; then
      adressat="${adressat#*<}"
      adressat="${adressat%>*}"
    fi
    adressat="$(
      echo "${adressat}" | \
        tr -d "[:space:]"
    )"
    echo "${adressat}" | grep -q "\S" || continue
    adressaten["${adressat}"]=1
  done

  adressatenString=""

  IFS="${oldIFS}"
}

stempeln() {
  hashcash -b ${hashcash_bits} -Xm "${!adressaten[@]}"
}

gpgAdressaten() {
  for adressat in "${!adressaten[@]}"; do
    if \
      (
        gpg --with-colons --list-public-keys "${adressat}" 2> /dev/null | \
          sed -n '
            /^pub\(:[^:]*\)\{10\}:[^:]*E[^:]*:/,/^tru:/ p
          ' | \
          grep '^uid:[mfuw]:' | \
          cut -d: -f10 | \
          tr '<>' '\n' | \
          sort -u
        echo "${adressat}"
      ) | \
        sort | \
        uniq -d | \
        grep -q '\S'; then
      echo -n "-r ${adressat} "
    else
      printf -- '-r %s ' "${gpg_recipients[@]}"
    fi
  done
}

gen_boundary() {
  [ -n "$1" ] && len=$1 || len=33
  local bnd
  bnd=''
  while [ "${#bnd}" -lt ${len} ]; do
    bnd="${bnd}$(
      head -c1 /dev/urandom | \
        base64 -w0 | \
        tr -d '/+='
    )"
  done
  echo "${bnd}"
}

datei=""
encrypt=true
stamp=true

dForced=false
eForced=false
sForced=false

if [ -r '/etc/sendmailadvanced.conf' ]; then
  . '/etc/sendmailadvanced.conf'
else
  for konfig in $(readlink -f "$0").conf $(find . -name sendmailadvanced.conf 2> /dev/null); do
    [ -r "${konfig}" ] || continue
    . "${konfig}"
    break
  done
fi

# BUG: gpg_recipients should not be relevant, if non-default keys are being found
[ -z "${gpg_recipients[*]}" ] && encrypt=false
for gpg_recipient in "${gpg_recipients[@]}"; do
  gpg --list-keys "${gpg_recipient}" &> /dev/null || encrypt=false
done
command -v hashcash &> /dev/null || stamp=false

eval set -- "$(getopt -o eh:i:st --long encrypt,no-encrypt,hook:,help,inline:,no-inline,stamp,no-stamp,version -n "$(basename "$0")" -- "$@" || echo verwendung)"

while true; do
  case "$1" in
    -h|--hook)
      shift
      if printf '%s\n' "$1" | grep -qixF 'ALL'; then
        for hook in $(find '/etc/sendmailadvanced.hooks' -type f); do
          if [ -x "${hook}" ] && [ -z "${hook%%/etc/sendmailadvanced.hooks/*}" ]; then
            hooks[${#hooks[@]}]="${hook#/etc/sendmailadvanced.hooks/}"
          fi
        done
      else
        hooks[${#hooks[@]}]="$1"
      fi
    ;;
    --help)
      verwendung 0
      ;;
    --version)
      echo '1.8.0'
      exit 0
      ;;
    -i|--inline)
      ${dForced} && verwendung
      shift
      datei="$1"
      [ "${datei}" == "-" ] && datei=""
      dForced=true
    ;;
    -t|--no-inline)
      ${dForced} && verwendung
      datei=""
      dForced=true
    ;;
    -e|--encrypt)
      ${eForced} && verwendung
      encrypt=true
      eForced=true
    ;;
    --no-encrypt)
      ${eForced} && verwendung
      encrypt=false
      eForced=true
    ;;
    -s|--stamp)
      ${sForced} && verwendung
      stamp=true
      sForced=true
    ;;
    --no-stamp)
      ${sForced} && verwendung
      stamp=false
      sForced=true
    ;;
    --)
      shift
      break
    ;;
    *)
      >&2 echo "FEHLER: Verstehe Option \"$1\" doch nicht! Ich beende."
      verwendung
    ;;
  esac
  shift
done

[ $# -ne 0 ] && verwendung

MAILER="cantfind"
for executable in sendmail msmtp; do
  for prefix in '/usr/bin' '/usr/bin' '/usr/sbin' '/bin' '/sbin'; do
    [ "${MAILER}" == "cantfind" ] && [ -x "${prefix}/${executable}" ] && MAILER="${prefix}/${executable}"
  done
done

if [ -z "${datei}" ] && [ "${MAILER}" == "cantfind" ]; then
  >&2 echo "ERROR: Can't find suitable mailer."
  exit 1
fi

{
  [ -z "${datei}" ] && cat || cat "${datei}"
} | \
(
  IFS=''
  adressatenString=""
  contentType='Content-Type: text/plain; charset=utf-8'
  contentTE='Content-Transfer-Encoding: 7bit'
  unset adressaten
  declare -A adressaten
  adressatenSammeln=false
  while read -r zeile; do
    echo "${zeile}" | grep -q "\S" || break
    s="${zeile}"
    if [ "${s:0:3}" == "To:" ] || [ "${s:0:3}" == "Cc:" ]; then
      adressatenSammeln=true
      s=" ,${s:4}"
    fi
    if [ ! "${s:0:1}" == " " ]; then
      ${adressatenSammeln} && neueAdressaten
      adressatenSammeln=false
    fi
    ${adressatenSammeln} && adressatenString="${adressatenString}${s}"
    if ${encrypt}; then
      if [ "${zeile:0:14}" == "Content-Type: " ]; then
        contentType="${zeile}"
        continue
      fi
      if [ "${zeile:0:27}" == "Content-Transfer-Encoding: " ]; then
        contentTE="${zeile}"
        continue
      fi
      if [ "${zeile:0:14}" == "MIME-Version: " ]; then
        continue
      fi
    fi
    echo "${zeile}"
  done

  ${stamp} && stempeln

  body=$(
    (
      for hookParam in "${hooks[@]}"; do
        "/etc/sendmailadvanced.hooks/${hookParam}" head
        echo
      done \
      | sed '
        /^$/ {
          :a
          N
          s@^\n$@@
          ta
        }
      '
      cat
      for hookParam in "${hooks[@]}"; do
        "/etc/sendmailadvanced.hooks/${hookParam}" foot
        echo
      done \
      | sed '
        /^$/ {
          :a
          N
          s@^\n$@@
          ta
        }
      '
    ) | \
    if ${encrypt}; then
      (
        echo "${contentType}"
        echo "${contentTE}"
        echo ''
        cat
        echo ''
      ) | \
        eval "gpg -e --no-tty --batch -a -s $(gpgAdressaten | sed 's| $||')"
    else
      cat
    fi
  ) \
  || exit $?

  if ${encrypt}; then
    boundary=""
    while
      printf '%s\n' "${body}" | \
        grep -qF "${boundary}"; do
      boundary=$(gen_boundary)
    done

    echo 'MIME-Version: 1.0'
    echo 'Content-Type: multipart/encrypted;'
    echo ' protocol="application/pgp-encrypted";'
    echo ' boundary="'"${boundary}"'"'
    echo ''
    echo 'This is an OpenPGP/MIME encrypted message (RFC 4880 and 3156)'
    echo '--'"${boundary}"
    echo 'Content-Type: application/pgp-encrypted'
    echo 'Content-Description: PGP/MIME version identification'
    echo ''
    echo 'Version: 1'
    echo ''
    echo '--'"${boundary}"
    echo 'Content-Type: application/octet-stream; name="encrypted.asc"'
    echo 'Content-Description: OpenPGP encrypted message'
    echo 'Content-Disposition: inline; filename="encrypted.asc"'
  fi

  printf '%s\n' \
    "${zeile}" \
    "${body}"

  if ${encrypt}; then
    echo ''
    echo '--'"${boundary}"'--'
  fi

) | \
(
  if [ -z "${datei}" ]; then
    ${MAILER} -t -i
  else
    tmpFile="$(mktemp)"
    cat > "${tmpFile}"
    cat "${tmpFile}" > "${datei}"
    rm -f "${tmpFile}"
  fi
)
