diff sbin/bsmtp2dma @ 124:47c7223bea76

Move bsmtp2dma to sbin. This is because Bacula's bsmtp lives in sbin also.
author Franz Glasner <fzglas.hg@dom66.de>
date Wed, 16 Oct 2019 00:49:48 +0200
parents bin/bsmtp2dma@397bf58e85d2
children 6be3742d21f7
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sbin/bsmtp2dma	Wed Oct 16 00:49:48 2019 +0200
@@ -0,0 +1,344 @@
+#!/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.
+
+
+Files:
+
+  The shell style configuration file in 
+
+      @@ETCDIR@@/bsmtp2dma.conf is
+
+  sourced in at script start.
+
+'
+
+#
+# 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_STDIN
+        MAILFIFO_STDOUT
+        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 _dummy
+
+    _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_STDIN}"
+    _rc=$?
+    if [ ${_rc} -ne 0 ]; then
+        return ${_rc}
+    fi
+    mkfifo -m 0600 "${MAILFIFO_STDOUT}"
+    _rc=$?
+    if [ ${_rc} -ne 0 ]; then
+        rm -f "${MAILFIFO_STDIN}"
+        return ${_rc}
+    fi
+
+    #
+    # Start the mailer **before** opening the pipe; otherwise a
+    # deadlock occurs
+    #
+    "$MAILER" -f "${_sender_addr}" "${_recipient_addr}" <${MAILFIFO_STDIN} >${MAILFIFO_STDOUT} &
+    _pid_mailer=$!
+
+    exec 3>"${MAILFIFO_STDIN}"
+    exec 4<"${MAILFIFO_STDOUT}"
+
+    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:
+        #     a comma separated list of full mail addresses
+        #
+        printf "Reply-To: %s\n" "${REPLYTO}" >&3
+    fi
+    if [ -n "${CC}" ]; then
+        #
+        # XXX TBD proper CC header value checks:
+        #     a comma separated list of full mail addresses
+        #
+        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
+    # not all mailer recognize this
+    # printf ".\n" >&3
+    IFS="$_oifs"
+
+    # close the fd to the pipe: coproc should get EOF and terminate
+    exec 3>&-
+    # read eventually remaining stuff from the mailer until EOF
+    IFS='' read _dummy <&4
+    exec 4<&-
+
+    wait $_pid_mailer
+    _rc=$?
+
+    # we are done with the named pipes
+    rm -f "${MAILFIFO_STDIN}"
+    rm -f "${MAILFIFO_STDOUT}"
+
+    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_STDIN="${MAILTMPDIR}/mail-stdin"
+MAILFIFO_STDOUT="${MAILTMPDIR}/mail-stdout"
+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}