view bin/bsmtp2dma @ 113:2d531cbd0feb

Explicitely disable the automatic repo update (using "-U") in the fast-track check
author Franz Glasner <fzglas.hg@dom66.de>
date Tue, 15 Oct 2019 09:24:56 +0200
parents fa73423234bf
children dad9f2d80c10
line wrap: on
line source

#!/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:

  -V           Show the program version and usage and exit.

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

  -c ADDRESS   Set then "CC:" header.

  -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`.

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

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

  -s SUBJECT   Set the "Subject:" header


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
#
# `sendmail` is also valid for `dma` because of the mapping within
#  `/etc/mail/mailer.conf`
#
: ${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"
    test -n "${_addr}" || return 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

    This procedure starts the configured mailer as coproc and sends
    email headers and contents to the started mailer.

    '
    local _recipient _rc _oifs _text _pid_mailer _recipient_addr
    local _from_from _from_addr _sender_addr

    _recipient="$1"
    _rc=0

    if parse_addr "${_recipient}"; then
        _recipient_addr="${email_addr}"
    else
        echo "ERROR: unknown recipient address format in \`${_recipient}'" >&2
        return 1
    fi
    _sender_addr="$(whoami)@$(hostname -f)"
    if [ -z "${FROM}" ]; then
        _from_addr="${_sender_addr}"
        _from_from="${_from_addr}"
    else
       if parse_addr "${FROM}"; then
           _from_from="${FROM}"
           _from_addr="${email_addr}"
       else
           echo "ERROR: unknown sender name in \`${FROM}'" >&2
           return 1
       fi
    fi

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

    exec 3<>"${MAILFIFO}"

    "$MAILER" -f "${_sender_addr}" "${_recipient_addr}" <&3 &
    _pid_mailer=$!

    printf "To: %s\n" "${_recipient}" >&3
    printf "From: %s\n" "${_from_from}" >&3
    if [ "${_sender_addr}" != "${_from_addr}" ]; then
        printf "Sender: %s\n" "${_sender_addr}" >&3
    fi
    if [ -n "${SUBJECT}" ]; then
        printf "Subject: %s\n" "${SUBJECT}" >&3
    fi
    if [ -n "${REPLYTO}" ]; then
        # XXX TBD proper Reply-To header value checks
        printf "Reply-To: %s\n" "${REPLYTO}" >&3
    fi
    if [ -n "${CC}" ]; then
        # XXX TBD proper CC header value checks
        printf "Cc: %s\n" "${CC}" >&3
    fi
    printf "\n" >&3

    # preserve leading white space when reading with `read`
    _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:d: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"
            ;;
        d)
            : # VOID
            ;;
        f)
            FROM="$OPTARG"
            ;;
        h)
            : # VOID
            ;;
        l)
            : # VOID
            ;;
        r)
            REPLYTO="$OPTARG"
            ;;
        s)
            SUBJECT="$OPTARG"
            ;;
        \?)
            exit 2;
            ;;
        *)
            echo "ERROR: inconsistent option handling" >&2
            exit 2;
            ;;
    esac
done

# return code
_rc=0

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 -rf "${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 with `read`
_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}