comparison 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
comparison
equal deleted inserted replaced
123:397bf58e85d2 124:47c7223bea76
1 #!/bin/sh
2 # -*- indent-tabs-mode: nil; -*-
3 : 'A simple replacement for Bacula `bsmtp` when the underlying mailer does
4 not listen on TCP ports (e.g. `dma`, `ssmtp` et al.).
5
6 :Author: Franz Glasner
7 :Copyright: (c) 2019 Franz Glasner.
8 All rights reserved.
9 :License: BSD 3-Clause "New" or "Revised" License.
10 See LICENSE for details.
11 If you cannot find LICENSE see
12 <https://opensource.org/licenses/BSD-3-Clause>
13 :ID: @(#)@@PKGORIGIN@@ $HGid$
14
15 '
16
17 VERSION="@@VERSION@@"
18
19 USAGE='
20 USAGE: bsmtp2dma [OPTIONS] RECIPIENT ...
21
22 Options:
23
24 -V Show the program version and usage and exit.
25
26 -8 Does nothing. Just a compatibility option for `bsmtp`.
27
28 -c ADDRESS Set then "CC:" header.
29
30 -d n Does nothing. Just a compatibility option for `bsmtp`.
31
32 -f ADDRESS Set the "From:" header.
33
34 -h MAILHOST:PORT Does nothing. Just a compatibility option for `bsmtp`.
35
36 -l NUMBER Does nothing. Just a compatibility option for `bsmtp`.
37
38 -r ADDRESS Set the "Reply-To:" header
39
40 -s SUBJECT Set the "Subject:" header
41
42
43 Usage:
44
45 The body of the email message is read from standard input. Message is
46 ended by sending the `EOF` character (`Ctrl-D` on many systems) on the
47 start of a new line, much like many `mail` commands.
48
49
50 Files:
51
52 The shell style configuration file in
53
54 @@ETCDIR@@/bsmtp2dma.conf is
55
56 sourced in at script start.
57
58 '
59
60 #
61 # Configuration directory
62 #
63 : ${CONFIGDIR:=@@ETCDIR@@}
64
65 test -r "${CONFIGDIR}/bsmtp2dma.conf" && . "${CONFIGDIR}/bsmtp2dma.conf"
66
67
68 #
69 # Default configuration values
70 #
71 # `sendmail` is also valid for `dma` because of the mapping within
72 # `/etc/mail/mailer.conf`
73 #
74 : ${MAILER:=/usr/sbin/sendmail}
75
76
77 parse_addr() {
78 : 'Parse an possibly complex email address.
79
80 Addresses can be of the form
81
82 - Name Parts <user@domain.tld>
83 - user@domain.tld
84
85 `Name Parts` may not contain ``<`` or ``>`` characters.
86
87 Args:
88 _addr: the complex email address
89
90 Returns:
91 0 on success, 1 on errors
92
93 Output (Globals):
94 email_name: the name part (or empty)
95 email_addr: the technical address part (or empty)
96
97 '
98 local _addr
99
100 _addr="$1"
101 test -n "${_addr}" || return 1
102
103 if printf "%s" "${_addr}" | grep -q -E -e '^[^<>]+<[^<>]+@[^<>]+>$'; then
104 email_name=$(printf '%s' "${_addr}" | sed -E -e 's/[[:space:]]*<.+$//')
105 email_addr=$(printf '%s' "${_addr}" | sed -E -e 's/^[^<>]+<//' | sed -E -e 's/>$//')
106 return 0
107 fi
108 if printf "%s" "${_addr}" | grep -q -E -e '^[^<>]+@[^<>]+$'; then
109 email_name=""
110 email_addr="${_addr}"
111 return 0
112 fi
113 return 1
114 }
115
116
117 send_mail() {
118 : 'Send the mail via the underlying configured mailer (dma, sendmail et al.).
119
120 Args:
121 _recipient: The recipient name.
122
123 Will be written into the "To:" header also.
124
125 Input (Globals):
126 MAILER
127 MAILCONTENT
128 MAILFIFO_STDIN
129 MAILFIFO_STDOUT
130 CC
131 FROM
132 REPLYTO
133 SUBJECT
134
135 Returns:
136 0 on success, other values on errors or the error exit code from the
137 underlying mailer
138
139 This procedure starts the configured mailer as coproc and sends
140 email headers and contents to the started mailer.
141
142 '
143 local _recipient _rc _oifs _text _pid_mailer _recipient_addr
144 local _from_from _from_addr _sender_addr _dummy
145
146 _recipient="$1"
147 _rc=0
148
149 if parse_addr "${_recipient}"; then
150 _recipient_addr="${email_addr}"
151 else
152 echo "ERROR: unknown recipient address format in \`${_recipient}'" >&2
153 return 1
154 fi
155 _sender_addr="$(whoami)@$(hostname -f)"
156 if [ -z "${FROM}" ]; then
157 _from_addr="${_sender_addr}"
158 _from_from="${_from_addr}"
159 else
160 if parse_addr "${FROM}"; then
161 _from_from="${FROM}"
162 _from_addr="${email_addr}"
163 else
164 echo "ERROR: unknown sender name in \`${FROM}'" >&2
165 return 1
166 fi
167 fi
168
169 mkfifo -m 0600 "${MAILFIFO_STDIN}"
170 _rc=$?
171 if [ ${_rc} -ne 0 ]; then
172 return ${_rc}
173 fi
174 mkfifo -m 0600 "${MAILFIFO_STDOUT}"
175 _rc=$?
176 if [ ${_rc} -ne 0 ]; then
177 rm -f "${MAILFIFO_STDIN}"
178 return ${_rc}
179 fi
180
181 #
182 # Start the mailer **before** opening the pipe; otherwise a
183 # deadlock occurs
184 #
185 "$MAILER" -f "${_sender_addr}" "${_recipient_addr}" <${MAILFIFO_STDIN} >${MAILFIFO_STDOUT} &
186 _pid_mailer=$!
187
188 exec 3>"${MAILFIFO_STDIN}"
189 exec 4<"${MAILFIFO_STDOUT}"
190
191 printf "To: %s\n" "${_recipient}" >&3
192 printf "From: %s\n" "${_from_from}" >&3
193 if [ "${_sender_addr}" != "${_from_addr}" ]; then
194 printf "Sender: %s\n" "${_sender_addr}" >&3
195 fi
196 if [ -n "${SUBJECT}" ]; then
197 printf "Subject: %s\n" "${SUBJECT}" >&3
198 fi
199 if [ -n "${REPLYTO}" ]; then
200 #
201 # XXX TBD proper Reply-To header value checks:
202 # a comma separated list of full mail addresses
203 #
204 printf "Reply-To: %s\n" "${REPLYTO}" >&3
205 fi
206 if [ -n "${CC}" ]; then
207 #
208 # XXX TBD proper CC header value checks:
209 # a comma separated list of full mail addresses
210 #
211 printf "Cc: %s\n" "${CC}" >&3
212 fi
213 printf "\n" >&3
214
215 # preserve leading white space when reading with `read`
216 _oifs="$IFS"
217 IFS="
218 "
219 cat "${MAILCONTENT}" |
220 while read _text; do
221 printf "%s\n" "$_text" >&3
222 done
223 # not all mailer recognize this
224 # printf ".\n" >&3
225 IFS="$_oifs"
226
227 # close the fd to the pipe: coproc should get EOF and terminate
228 exec 3>&-
229 # read eventually remaining stuff from the mailer until EOF
230 IFS='' read _dummy <&4
231 exec 4<&-
232
233 wait $_pid_mailer
234 _rc=$?
235
236 # we are done with the named pipes
237 rm -f "${MAILFIFO_STDIN}"
238 rm -f "${MAILFIFO_STDOUT}"
239
240 return ${_rc}
241 }
242
243
244 while getopts "V8c:d:f:h:l:nr:s:" _opt; do
245 case ${_opt} in
246 V)
247 echo "bsmtp2dma v${VERSION} (rv:@@HGREVISION@@)"
248 echo "$USAGE"
249 exit 0;
250 ;;
251 8)
252 : # VOID
253 ;;
254 c)
255 CC="$OPTARG"
256 ;;
257 d)
258 : # VOID
259 ;;
260 f)
261 FROM="$OPTARG"
262 ;;
263 h)
264 : # VOID
265 ;;
266 l)
267 : # VOID
268 ;;
269 r)
270 REPLYTO="$OPTARG"
271 ;;
272 s)
273 SUBJECT="$OPTARG"
274 ;;
275 \?)
276 exit 2;
277 ;;
278 *)
279 echo "ERROR: inconsistent option handling" >&2
280 exit 2;
281 ;;
282 esac
283 done
284
285 # return code
286 _rc=0
287
288 MAILTMPDIR="$(mktemp -d)"
289 MAILFIFO_STDIN="${MAILTMPDIR}/mail-stdin"
290 MAILFIFO_STDOUT="${MAILTMPDIR}/mail-stdout"
291 MAILCONTENT="${MAILTMPDIR}/mail-text"
292
293 #
294 # Clean up existing temporary stuff on all sorts of exit
295 # (including the "exit" call (signal 0))
296 #
297 trap 'if [ -d "${MAILTMPDIR}" ]; then rm -rf "${MAILTMPDIR}"; fi; exit;' 0 1 2 15
298
299 test -d "${MAILTMPDIR}" || { echo "ERROR: no existing private tmp dir" >&2; exit 1; }
300
301 #
302 # Reset the Shell's option handling system to prepare for handling
303 # other arguments and probably command-local options
304 #
305 shift $((OPTIND-1))
306 OPTIND=1
307
308 # early check whether some recipients are given
309 if [ $# -eq 0 ]; then
310 echo "ERROR: no recipient given" >&2
311 exit 2;
312 fi
313
314 #
315 # Collect the mail text from stdin into a temporary file
316 #
317 exec 3>"${MAILCONTENT}"
318 # preserve leading white space when reading with `read`
319 _oifs="$IFS"
320 IFS="
321 "
322 while read _text; do
323 if [ "${_text}" = "." ]; then
324 break
325 else
326 printf "%s\n" "${_text}" >&3
327 fi
328 done
329 exec 3>&-
330 IFS="$_oifs"
331
332 #
333 # Now send the content of the collected mail content to all recipients
334 #
335 until [ $# -eq 0 ]; do
336 send_mail "$1"
337 _rcsm=$?
338 if [ \( ${_rcsm} -ne 0 \) -a \( ${_rc} -eq 0 \) ]; then
339 _rc=${_rcsm}
340 fi
341 shift
342 done
343
344 exit ${_rc}