#!/bin/sh
# -*- indent-tabs-mode: nil; -*-
: 'A simple replacement for Bacula `bsmtp` when the underlying mailer does
not listen on TCP ports (e.g. `dma`, `ssmtp` et al.).

:Author:    Franz Glasner
:Copyright: (c) 2019 Franz Glasner.
            All rights reserved.
:License:   BSD 3-Clause "New" or "Revised" License.
            See LICENSE for details.
            If you cannot find LICENSE see
            <https://opensource.org/licenses/BSD-3-Clause>
:ID:        @(#)@@PKGORIGIN@@ $HGid$

'

VERSION="@@VERSION@@"

USAGE='
USAGE: bsmtp2dma [OPTIONS] RECIPIENT ...

Options:

  -8           Does nothing. Just a compatibility option for `bsmtp`.

  -d n         Does nothing. Just a compatibility option for `bsmtp`.

  -f ADDRESS   Set the "From:" header.

  -h MAILHOST:PORT  Does nothing. Just a compatibility option for `bsmtp`.

  -n           Do a dry-run: just print out what would be done

  -s SUBJECT   Set the "Subject:" header

  -r ADDRESS   Set the "Reply-To:" header

  -l NUMBER    Does nothing. Just a compatibility option for `bsmtp`.

  -V           Show the program version and usage and exit.


Usage:

  The body of the email message is read from standard input. Message is
  ended by sending the `EOF` character (`Ctrl-D` on many systems) on the
  start of a new line, much like many `mail` commands.

'

#
# Configuration directory
#
: ${CONFIGDIR:=@@ETCDIR@@}

test -r "${CONFIGDIR}/bsmtp2dma.conf" && . "${CONFIGDIR}/bsmtp2dma.conf"


# Default configuration values
: ${MAILER:=/usr/sbin/sendmail}


parse_addr() {
    : 'Parse an possibly complex email address.

    Addresses can be of the form

    - Name Parts <user@domain.tld>
    - user@domain.tld

    `Name Parts` may not contain ``<`` or ``>`` characters.

    Args:
        _addr: the complex email address

    Returns:
        0 on success, 1 on errors

    Output (Globals):
        email_name: the name part (or empty)
        email_addr: the technical address part (or empty)

    '
    local _addr

    _addr="$1"

    if printf "%s" "${_addr}" | grep -q -E -e '^[^<>]+<[^<>]+@[^<>]+>$'; then
        email_name=$(printf '%s' "${_addr}" | sed -E -e 's/[[:space:]]*<.+$//')
        email_addr=$(printf '%s' "${_addr}" | sed -E -e 's/^[^<>]+<//' | sed -E -e 's/>$//')
        return 0
    fi
    if printf "%s" "${_addr}" | grep -q -E -e '^[^<>]+@[^<>]+$'; then
        email_name=""
        email_addr="${_addr}"
        return 0
    fi
    return 1
}


send_mail() {
    : 'Send the mail via the underlying configured mailer (dma, sendmail et al.).

    Args:
        _recipient: The recipient name.

                    Will be written into the "To:" header also.

    Input (Globals):
        MAILER
        MAILCONTENT
        MAILFIFO
        CC
        FROM
        REPLYTO
        SUBJECT

    Returns:
        0 on success, other values on errors or the error exit code from the
        underlying mailer

    '
    local _recipient _rc _oifs _text _pid_mailer

    _recipient="$1"
    _rc=0

    mkfifo -m 0600 "${MAILFIFO}"
    _rc=$?
    if [ ${_rc} -ne 0 ]; then
        return ${_rc}
    fi

    exec 3<>"${MAILFIFO}"

    "$MAILER" -f bacula@fmgapp7-bacula9.intern.feldmann-mg.com hostmaster@feldmann-mg.com <&3 &
    _pid_mailer=$!

    # preserve leading white space when reading
    _oifs="$IFS"
    IFS="
"
    cat "${MAILCONTENT}" |
        while read _text; do
            printf "%s\n" "$_text" >&3
        done
    printf ".\n" >&3

    IFS="$_oifs"

    # close the fd to the pipe: coproc should get EOF
    exec 3>&-

    wait $_pid_mailer
    _rc=$?

    # we are done with the named pipe
    rm -f "${MAILFIFO}"

    return ${_rc}
}


while getopts "V8c:f:h:l:nr:s:" _opt; do
    case ${_opt} in
        V)
            echo "bsmtp2dma v${VERSION} (rv:@@HGREVISION@@)"
            echo "$USAGE"
            exit 0;
            ;;
        8)
            : # VOID
            ;;
        c)
            CC="$OPTARG"
            ;;
        f)
            FROM="$OPTARG"
            ;;
        h)
            : # VOID
            ;;
        l)
            : # VOID
            ;;
        n)
            DRYRUN="YES"
            ;;
        r)
            REPLYTO="$OPTARG"
            ;;
        s)
            SUBJECT="$OPTARG"
            ;;
        \?)
            exit 2;
            ;;
        *)
            echo "ERROR: inconsistent option handling" >&2
            exit 2;
            ;;
    esac
done

# return code
_rc=0

DRYRUN="NO"

MAILTMPDIR="$(mktemp -d)"
MAILFIFO="${MAILTMPDIR}/mail-stdin"
MAILCONTENT="${MAILTMPDIR}/mail-text"

#
# Clean up existing temporary stuff on all sorts of exit
# (including the "exit" call (signal 0))
#
trap 'if [ -d "${MAILTMPDIR}" ]; then rm -r "${MAILTMPDIR}"; fi; exit;' 0 1 2 15

test -d "${MAILTMPDIR}" || { echo "ERROR: no existing private tmp dir" >&2; exit 1; }

#
# Reset the Shell's option handling system to prepare for handling
# other arguments and probably command-local options
#
shift $((OPTIND-1))
OPTIND=1

# early check whether some recipients are given
if [ $# -eq 0 ]; then
    echo "ERROR: no recipient given" >&2
    exit 2;
fi

#
# Collect the mail text from stdin into a temporary file
#
exec 3>"${MAILCONTENT}"
# preserve leading white space when reading
_oifs="$IFS"
IFS="
"
while read _text; do
    if [ "${_text}" = "." ]; then
        break
    else
        printf "%s\n" "${_text}" >&3
    fi
done
exec 3>&-
IFS="$_oifs"

#
# Now send the content of the collected mail content to all recipients
#
until [ $# -eq 0 ]; do
    send_mail "$1"
    _rcsm=$?
    if [ \( ${_rcsm} -ne 0 \) -a \( ${_rc} -eq 0 \) ]; then
        _rc=${_rcsm}
    fi
    shift
done

exit ${_rc}
