|
110
|
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 |
|
111
|
24 -V Show the program version and usage and exit. |
|
|
25 |
|
110
|
26 -8 Does nothing. Just a compatibility option for `bsmtp`. |
|
|
27 |
|
111
|
28 -c ADDRESS Set then "CC:" header. |
|
|
29 |
|
110
|
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 |
|
111
|
36 -l NUMBER Does nothing. Just a compatibility option for `bsmtp`. |
|
110
|
37 |
|
|
38 -r ADDRESS Set the "Reply-To:" header |
|
|
39 |
|
111
|
40 -s SUBJECT Set the "Subject:" header |
|
110
|
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 |
|
|
51 # |
|
|
52 # Configuration directory |
|
|
53 # |
|
|
54 : ${CONFIGDIR:=@@ETCDIR@@} |
|
|
55 |
|
|
56 test -r "${CONFIGDIR}/bsmtp2dma.conf" && . "${CONFIGDIR}/bsmtp2dma.conf" |
|
|
57 |
|
|
58 |
|
111
|
59 # |
|
110
|
60 # Default configuration values |
|
111
|
61 # |
|
|
62 # `sendmail` is also valid for `dma` because of the mapping within |
|
|
63 # `/etc/mail/mailer.conf` |
|
|
64 # |
|
110
|
65 : ${MAILER:=/usr/sbin/sendmail} |
|
|
66 |
|
|
67 |
|
|
68 parse_addr() { |
|
|
69 : 'Parse an possibly complex email address. |
|
|
70 |
|
|
71 Addresses can be of the form |
|
|
72 |
|
|
73 - Name Parts <user@domain.tld> |
|
|
74 - user@domain.tld |
|
|
75 |
|
|
76 `Name Parts` may not contain ``<`` or ``>`` characters. |
|
|
77 |
|
|
78 Args: |
|
|
79 _addr: the complex email address |
|
|
80 |
|
|
81 Returns: |
|
|
82 0 on success, 1 on errors |
|
|
83 |
|
|
84 Output (Globals): |
|
|
85 email_name: the name part (or empty) |
|
|
86 email_addr: the technical address part (or empty) |
|
|
87 |
|
|
88 ' |
|
|
89 local _addr |
|
|
90 |
|
|
91 _addr="$1" |
|
111
|
92 test -n "${_addr}" || return 1 |
|
110
|
93 |
|
|
94 if printf "%s" "${_addr}" | grep -q -E -e '^[^<>]+<[^<>]+@[^<>]+>$'; then |
|
|
95 email_name=$(printf '%s' "${_addr}" | sed -E -e 's/[[:space:]]*<.+$//') |
|
|
96 email_addr=$(printf '%s' "${_addr}" | sed -E -e 's/^[^<>]+<//' | sed -E -e 's/>$//') |
|
|
97 return 0 |
|
|
98 fi |
|
|
99 if printf "%s" "${_addr}" | grep -q -E -e '^[^<>]+@[^<>]+$'; then |
|
|
100 email_name="" |
|
|
101 email_addr="${_addr}" |
|
|
102 return 0 |
|
|
103 fi |
|
|
104 return 1 |
|
|
105 } |
|
|
106 |
|
|
107 |
|
|
108 send_mail() { |
|
|
109 : 'Send the mail via the underlying configured mailer (dma, sendmail et al.). |
|
|
110 |
|
|
111 Args: |
|
|
112 _recipient: The recipient name. |
|
|
113 |
|
|
114 Will be written into the "To:" header also. |
|
|
115 |
|
|
116 Input (Globals): |
|
|
117 MAILER |
|
|
118 MAILCONTENT |
|
|
119 MAILFIFO |
|
|
120 CC |
|
|
121 FROM |
|
|
122 REPLYTO |
|
|
123 SUBJECT |
|
|
124 |
|
|
125 Returns: |
|
|
126 0 on success, other values on errors or the error exit code from the |
|
|
127 underlying mailer |
|
|
128 |
|
111
|
129 This procedure starts the configured mailer as coproc and sends |
|
|
130 email headers and contents to the started mailer. |
|
|
131 |
|
110
|
132 ' |
|
111
|
133 local _recipient _rc _oifs _text _pid_mailer _recipient_addr |
|
|
134 local _from_from _from_addr _sender_addr |
|
110
|
135 |
|
|
136 _recipient="$1" |
|
|
137 _rc=0 |
|
|
138 |
|
111
|
139 if parse_addr "${_recipient}"; then |
|
|
140 _recipient_addr="${email_addr}" |
|
|
141 else |
|
|
142 echo "ERROR: unknown recipient address format in \`${_recipient}'" >&2 |
|
|
143 return 1 |
|
|
144 fi |
|
|
145 _sender_addr="$(whoami)@$(hostname -f)" |
|
|
146 if [ -z "${FROM}" ]; then |
|
|
147 _from_addr="${_sender_addr}" |
|
|
148 _from_from="${_from_addr}" |
|
|
149 else |
|
|
150 if parse_addr "${FROM}"; then |
|
|
151 _from_from="${FROM}" |
|
|
152 _from_addr="${email_addr}" |
|
|
153 else |
|
|
154 echo "ERROR: unknown sender name in \`${FROM}'" >&2 |
|
|
155 return 1 |
|
|
156 fi |
|
|
157 fi |
|
|
158 |
|
110
|
159 mkfifo -m 0600 "${MAILFIFO}" |
|
|
160 _rc=$? |
|
|
161 if [ ${_rc} -ne 0 ]; then |
|
|
162 return ${_rc} |
|
|
163 fi |
|
|
164 |
|
|
165 exec 3<>"${MAILFIFO}" |
|
|
166 |
|
111
|
167 "$MAILER" -f "${_sender_addr}" "${_recipient_addr}" <&3 & |
|
110
|
168 _pid_mailer=$! |
|
|
169 |
|
111
|
170 printf "To: %s\n" "${_recipient}" >&3 |
|
|
171 printf "From: %s\n" "${_from_from}" >&3 |
|
|
172 if [ "${_sender_addr}" != "${_from_addr}" ]; then |
|
|
173 printf "Sender: %s\n" "${_sender_addr}" >&3 |
|
|
174 fi |
|
|
175 if [ -n "${SUBJECT}" ]; then |
|
|
176 printf "Subject: %s\n" "${SUBJECT}" >&3 |
|
|
177 fi |
|
|
178 if [ -n "${REPLYTO}" ]; then |
|
114
|
179 # |
|
|
180 # XXX TBD proper Reply-To header value checks: |
|
|
181 # a comma separated list of full mail addresses |
|
|
182 # |
|
111
|
183 printf "Reply-To: %s\n" "${REPLYTO}" >&3 |
|
|
184 fi |
|
|
185 if [ -n "${CC}" ]; then |
|
114
|
186 # |
|
|
187 # XXX TBD proper CC header value checks: |
|
|
188 # a comma separated list of full mail addresses |
|
|
189 # |
|
111
|
190 printf "Cc: %s\n" "${CC}" >&3 |
|
|
191 fi |
|
|
192 printf "\n" >&3 |
|
|
193 |
|
|
194 # preserve leading white space when reading with `read` |
|
110
|
195 _oifs="$IFS" |
|
|
196 IFS=" |
|
|
197 " |
|
|
198 cat "${MAILCONTENT}" | |
|
|
199 while read _text; do |
|
|
200 printf "%s\n" "$_text" >&3 |
|
|
201 done |
|
|
202 printf ".\n" >&3 |
|
|
203 |
|
|
204 IFS="$_oifs" |
|
|
205 |
|
|
206 # close the fd to the pipe: coproc should get EOF |
|
|
207 exec 3>&- |
|
|
208 |
|
|
209 wait $_pid_mailer |
|
|
210 _rc=$? |
|
|
211 |
|
|
212 # we are done with the named pipe |
|
|
213 rm -f "${MAILFIFO}" |
|
|
214 |
|
|
215 return ${_rc} |
|
|
216 } |
|
|
217 |
|
|
218 |
|
111
|
219 while getopts "V8c:d:f:h:l:nr:s:" _opt; do |
|
110
|
220 case ${_opt} in |
|
|
221 V) |
|
|
222 echo "bsmtp2dma v${VERSION} (rv:@@HGREVISION@@)" |
|
|
223 echo "$USAGE" |
|
|
224 exit 0; |
|
|
225 ;; |
|
|
226 8) |
|
|
227 : # VOID |
|
|
228 ;; |
|
|
229 c) |
|
|
230 CC="$OPTARG" |
|
|
231 ;; |
|
111
|
232 d) |
|
|
233 : # VOID |
|
|
234 ;; |
|
110
|
235 f) |
|
|
236 FROM="$OPTARG" |
|
|
237 ;; |
|
|
238 h) |
|
|
239 : # VOID |
|
|
240 ;; |
|
|
241 l) |
|
|
242 : # VOID |
|
|
243 ;; |
|
|
244 r) |
|
|
245 REPLYTO="$OPTARG" |
|
|
246 ;; |
|
|
247 s) |
|
|
248 SUBJECT="$OPTARG" |
|
|
249 ;; |
|
|
250 \?) |
|
|
251 exit 2; |
|
|
252 ;; |
|
|
253 *) |
|
|
254 echo "ERROR: inconsistent option handling" >&2 |
|
|
255 exit 2; |
|
|
256 ;; |
|
|
257 esac |
|
|
258 done |
|
|
259 |
|
|
260 # return code |
|
|
261 _rc=0 |
|
|
262 |
|
|
263 MAILTMPDIR="$(mktemp -d)" |
|
|
264 MAILFIFO="${MAILTMPDIR}/mail-stdin" |
|
|
265 MAILCONTENT="${MAILTMPDIR}/mail-text" |
|
|
266 |
|
|
267 # |
|
|
268 # Clean up existing temporary stuff on all sorts of exit |
|
|
269 # (including the "exit" call (signal 0)) |
|
|
270 # |
|
111
|
271 trap 'if [ -d "${MAILTMPDIR}" ]; then rm -rf "${MAILTMPDIR}"; fi; exit;' 0 1 2 15 |
|
110
|
272 |
|
|
273 test -d "${MAILTMPDIR}" || { echo "ERROR: no existing private tmp dir" >&2; exit 1; } |
|
|
274 |
|
|
275 # |
|
|
276 # Reset the Shell's option handling system to prepare for handling |
|
|
277 # other arguments and probably command-local options |
|
|
278 # |
|
|
279 shift $((OPTIND-1)) |
|
|
280 OPTIND=1 |
|
|
281 |
|
|
282 # early check whether some recipients are given |
|
|
283 if [ $# -eq 0 ]; then |
|
|
284 echo "ERROR: no recipient given" >&2 |
|
|
285 exit 2; |
|
|
286 fi |
|
|
287 |
|
|
288 # |
|
|
289 # Collect the mail text from stdin into a temporary file |
|
|
290 # |
|
|
291 exec 3>"${MAILCONTENT}" |
|
111
|
292 # preserve leading white space when reading with `read` |
|
110
|
293 _oifs="$IFS" |
|
|
294 IFS=" |
|
|
295 " |
|
|
296 while read _text; do |
|
|
297 if [ "${_text}" = "." ]; then |
|
|
298 break |
|
|
299 else |
|
|
300 printf "%s\n" "${_text}" >&3 |
|
|
301 fi |
|
|
302 done |
|
|
303 exec 3>&- |
|
|
304 IFS="$_oifs" |
|
|
305 |
|
|
306 # |
|
|
307 # Now send the content of the collected mail content to all recipients |
|
|
308 # |
|
|
309 until [ $# -eq 0 ]; do |
|
|
310 send_mail "$1" |
|
|
311 _rcsm=$? |
|
|
312 if [ \( ${_rcsm} -ne 0 \) -a \( ${_rc} -eq 0 \) ]; then |
|
|
313 _rc=${_rcsm} |
|
|
314 fi |
|
|
315 shift |
|
|
316 done |
|
|
317 |
|
|
318 exit ${_rc} |