]> git.rmz.io Git - dotfiles.git/blob - msmtp/msmtpq
nvim: remove neo-tree
[dotfiles.git] / msmtp / msmtpq
1 #!/usr/bin/env bash
2
3 ##--------------------------------------------------------------
4 ##
5 ## msmtpq : queue funtions to both use & manage the msmtp queue,
6 ## as it was defined by Martin Lambers
7 ## Copyright (C) 2008, 2009, 2010, 2011 Chris Gianniotis
8 ##
9 ## This program is free software: you can redistribute it and/or modify
10 ## it under the terms of the GNU General Public License as published by
11 ## the Free Software Foundation, either version 3 of the License, or, at
12 ## your option, any later version.
13 ##
14 ##--------------------------------------------------------------
15
16 ## msmtpq is meant to be used by an email client - in 'sendmail' mode
17 ## for this purpose, it is invoked directly as 'msmtpq'
18 ## it is also meant to be used to maintain the msmtp queue
19 ## evoked by the wrapper script 'msmtp-queue'
20 ## (which calls this script as msmtpq --q-mgmt)
21
22 ## there is a queue log file, distinct from the msmtp log,
23 ## for all events & operations on the msmtp queue
24 ## that is defined below
25
26 ## (mutt users, using msmtpq in 'sendmail' mode,
27 ## should make at least the following two settings in their .muttrc
28 ## set sendmail = /path/to/msmtpq
29 ## set sendmail_wait = -1
30 ##
31 ## please see the msmtp man page and docs for further mutt settings
32 ## and optimisations
33 ## )
34
35 ## msmtpq now uses the following environment variables :
36 ## EMAIL_CONN_NOTEST if set will suppress any testing for a connection
37 ## EMAIL_CONN_TEST if unset or =p will use a ping test (debian.org) for a connection
38 ## if =P will use a fast ping test (8.8.8.8) for a connection
39 ## if =n will use netcat (nc) to test for a connection
40 ## if =s will use bash sockets to test for a connection
41 ## EMAIL_QUEUE_QUIET if set will cause suppression of messages and 'chatter'
42
43 ## two essential patches by Philipp Hartwig
44 ## 19 Oct 2011 & 27 Oct 2011
45
46 ##--------------------------------------------------------------
47 ## the msmtp queue contains unique filenames of the following form :
48 ## two files for each mail in the queue
49 ##
50 ## creates new unique filenames of the form :
51 ## MLF: ccyy-mm-dd-hh.mm.ss[-x].mail -- mail file
52 ## MSF: ccyy-mm-dd-hh.mm.ss[-x].msmtp -- msmtp command line file
53 ## where x is a consecutive number only appended for uniqueness
54 ## if more than one mail per second is sent
55 ##--------------------------------------------------------------
56
57
58 dsp() { local L ; for L ; do [ -n "$L" ] && echo " $L" || echo ; done ; }
59 err() { dsp '' "$@" '' ; exit 1 ; }
60
61
62 ## ======================================================================================
63 ## !!! please define or confirm the following three vars !!!
64 ## !!! before using the msmtpq or msmtp-queue scripts !!!
65 ## ======================================================================================
66 ##
67 ## only if necessary (in unusual circumstances - e.g. embedded systems),
68 ## enter the location of the msmtp executable (no quotes !!)
69 ## e.g. ( MSMTP=/path/to/msmtp )
70 MSMTP=msmtp
71 #[ -x "$MSMTP" ] || \
72 # log -e 1 "msmtpq : can't find the msmtp executable [ $MSMTP ]" # if not found - complain ; quit
73 ##
74 ## set the queue var to the location of the msmtp queue directory
75 ## if the queue dir doesn't yet exist, better to create it (0700)
76 ## before using this routine
77 ## e.g. ( mkdir msmtp.queue )
78 ## ( chmod 0700 msmtp.queue )
79 ##
80 ## the queue dir - modify this to reflect where you'd like it to be (no quotes !!)
81 Q=~/.msmtp/queue
82 [ -d "$Q" ] || \
83 err '' "msmtpq : can't find msmtp queue directory [ $Q ]" '' # if not present - complain ; quit
84 ##
85 ## set the queue log file var to the location of the msmtp queue log file
86 ## where it is or where you'd like it to be
87 ## ( note that the LOG setting could be the same as the )
88 ## ( 'logfile' setting in .msmtprc - but there may be )
89 ## ( some advantage in keeping the two logs separate )
90 ## if you don't want the log at all unset (comment out) this var
91 ## LOG=~/log/msmtp.queue.log --> #LOG=~/log/msmtp.queue.log
92 ## (doing so would be inadvisable under most conditions, however)
93 ##
94 ## the queue log file - modify (or comment out) to taste (but no quotes !!)
95 LOG=~/.msmtp/queue.log
96 ## ======================================================================================
97
98 umask 077 # set secure permissions on created directories and files
99
100 declare -i CNT # a count of mail(s) currently in the queue
101 declare -a Q_LST # queue list array ; used selecting a mail (to send or remove)
102
103 ## do ; test this !
104 #for sig in INT TERM EXIT; do
105 # trap "rm -f \"\$TMPFILE\"; [[ $sig == EXIT ]] || kill -$sig $$" $sig
106 #done
107 trap on_exit INT TERM EXIT # run 'on_exit' on exit
108 on_exit() {
109 [ -n "$LKD" ] && lock_queue -u 2>/dev/null # unlock the queue on exit if the lock was set here
110 }
111
112 #
113 ## ----------------------------------- common functions
114 #
115
116 ## make an entry to the queue log file, possibly an error
117 ## (log queue changes only ; not interactive chatter)
118 ## usage : log [ -e errcode ] msg [ msg ... ]
119 ## opts : -e <exit code> an error ; log msg & terminate w/prejudice
120 ## display msg to user, as well
121 log() {
122 local ARG RC PFX="$('date' +'%Y %d %b %H:%M:%S')"
123 # time stamp prefix - "2008 13 Mar 03:59:45 "
124 if [ "$1" = '-e' ] ; then # there's an error exit code
125 RC="$2" # take it
126 shift 2 # shift opt & its arg off
127 fi
128
129 [ -z "$EMAIL_QUEUE_QUIET" ] && dsp "$@" # display msg to user, as well as logging it
130
131 if [ -n "$LOG" ] ; then # log is defined and in use
132 for ARG ; do # each msg line out
133 [ -n "$ARG" ] && \
134 echo "$PFX : $ARG" >> "$LOG" # line has content ; send it to log
135 done
136 fi
137
138 if [ -n "$RC" ] ; then # an error ; leave w/error return
139 [ -n "$LKD" ] && lock_queue -u # unlock here (if locked)
140 [ -n "$LOG" ] && \
141 echo " exit code = $RC" >> "$LOG" # logging ok ; send exit code to log
142 exit $RC # exit w/return code
143 fi
144 }
145
146 ## write/remove queue lockfile for a queue op
147 lock_queue() { # <-- '-u' to remove lockfile
148 local LOK="${Q}/.lock" # lock file name
149 local -i MAX=240 SEC=0 # max seconds to gain a lock ; seconds waiting
150
151 if [ -z "$1" ] ; then # lock queue
152 ## Philipp Hartwig patch #2
153 'mkdir' "$LOK" 2>/dev/null && LKD='t'
154 while [ -z "$LKD" ] && (( SEC < MAX )) ; do # lock file present
155 sleep 1 # wait a second
156 (( ++SEC )) # accumulate seconds
157 'mkdir' "$LOK" 2>/dev/null && LKD='t' # make lockdir ; lock queue ; set flag
158 done # try again while locked for MAX secs
159 [ -z "$LKD" ] && \
160 err '' "cannot use queue $Q : waited $MAX seconds for"\
161 " lockdir [ $LOK ] to vanish ; giving up"\
162 'if you are certain that no other instance of this script'\
163 " is running, then 'rmdir' the lock dir manually" '' # lock file still there, give up
164
165 elif [ "$1" = '-u' ] ; then # unlock queue
166 'rmdir' "$LOK" # remove the lock
167 unset LKD # unset flag
168 fi
169 }
170
171 ## test whether system is connected
172 ## returns t/f (0/1)
173 connect_test() {
174 if [ -z "$EMAIL_CONN_TEST" ] || \
175 [ "$EMAIL_CONN_TEST" = 'p' ] ; then # use ping test (default)
176 # verify net connection - ping ip address of debian.org
177 # would ping -qnc2 -w4 be better ?
178 # would ping -qnc1 -w10 or -w20 be better ?
179 ping -qnc1 -w4 debian.org >/dev/null 2>&1 || return 1
180 elif [ "$EMAIL_CONN_TEST" = 'P' ] ; then # use quicker ping test
181 # I personally think that including a DNS lookup
182 # is a better connection test but some
183 # have found the above test too slow
184 ping -qnc1 -w4 8.8.8.8 >/dev/null 2>&1 || return 1
185 elif [ "$EMAIL_CONN_TEST" = 'n' ] ; then # use netcat (nc) test
186 # must, of course, have netcat (nc) installed
187 which nc >/dev/null 2>&1 || \
188 log -e 1 "msmtpq : can't find netcat executable [ nc ]" # if not found - complain ; quit
189 'nc' -vz www.debian.org 80 || return 1
190 elif [ "$EMAIL_CONN_TEST" = 's' ] ; then # use sh sockets test
191 # note that this does not work on debian systems
192 # where bash opened sockets are suppressed for security reasons
193 # this should be ok for single user systems (including embedded systems)
194 # test whether this is supported on your system before using
195 exec 3<>/dev/udp/debian.org/80 || return 1 # open socket on site ; use dns
196 exec 3<&- ; exec 3>&- # close socket
197 fi
198 return 0
199 }
200
201 #
202 ## ----------------------------------- functions for queue management
203 ## ----------------------------------- queue maintenance mode - (msmtp-queue)
204 #
205
206 ## show queue maintenance functions
207 usage() { # <-- error msg
208 dsp ''\
209 'usage : msmtp-queue functions' ''\
210 ' msmtp-queue < op >'\
211 ' ops : -r run (flush) mail queue - all mail in queue'\
212 ' -R send selected individual mail(s) in queue'\
213 ' -d display (list) queue contents (<-- default)'\
214 ' -p purge individual mail(s) from queue'\
215 ' -a purge all mail in queue'\
216 ' -h this helpful blurt' ''\
217 ' ( one op only ; any others ignored )' ''
218 [ -z "$1" ] && exit 0 || { dsp "$@" '' ; exit 1 ; }
219 }
220
221 ## get user [y/n] acknowledgement
222 ok() {
223 local R YN='Y/n' # default to yes
224
225 [ "$1" = '-n' ] && \
226 { YN='y/N' ; shift ; } # default to no ; change prompt ; shift off spec
227
228 dsp "$@"
229 while true ; do
230 echo -n " ok [${YN}] ..: "
231 read R
232 case $R in
233 y|Y) return 0 ;;
234 n|N) return 1 ;;
235 '') [ "$YN" = 'Y/n' ] && return 0 || return 1 ;;
236 *) echo 'yYnN<cr> only please' ;;
237 esac
238 done
239 }
240
241 ## send a queued mail out via msmtp
242 send_queued_mail() { # <-- mail id
243 local FQP="${Q}/${1}" # fully qualified path name
244 local -i RC=0 # for msmtp exit code
245
246 if [ -f "${FQP}.msmtp" ] ; then # corresponding .msmtp file found
247 [ -z "$EMAIL_CONN_NOTEST" ] && { # connection test
248 connect_test || \
249 log "mail [ $2 ] [ $1 ] from queue ; couldn't be sent - host not connected"
250 }
251
252 if $MSMTP $(< "${FQP}.msmtp") < "${FQP}.mail" ; then # this mail goes out the door
253 log "mail [ $2 ] [ $1 ] from queue ; send was successful ; purged from queue" # good news to user
254 'rm' -f ${FQP}.* # nuke both queue mail files
255 ALT='t' # set queue changed flag
256 else # send was unsuccessful
257 RC=$? # take msmtp exit code
258 log "mail [ $2 ] [ $1 ] from queue ; send failed ; msmtp rc = $RC" # bad news ...
259 fi
260 return $RC # func returns exit code
261 else # corresponding MSF file not found
262 log "preparing to send .mail file [ $1 ] [ ${FQP}.mail ] but"\
263 " corresponding .msmtp file [ ${FQP}.msmtp ] was not found in queue"\
264 ' skipping this mail ; this is worth looking into' # give user the bad news
265 fi # (but allow continuation)
266 }
267
268 ## run (flush) queue
269 run_queue() { # <- 'sm' mode # run queue
270 local M LST="$('ls' $Q/*.mail 2>/dev/null)" # list of mails in queue
271 local -i NDX=0
272
273 if [ -n "$LST" ] ; then # something in queue
274 for M in $LST ; do # process all mails
275 ((NDX++))
276 send_queued_mail "$(basename $M .mail)" "$NDX" # send mail - pass {id} only
277 done
278 else # queue is empty
279 [ -z "$1" ] && \
280 dsp '' 'mail queue is empty (nothing to send)' ''
281 fi # inform user (if not running in sendmail mode)
282 }
283
284 ## display queue contents
285 display_queue() { # <-- { 'purge' | 'send' } (op label) ; { 'rec' } (record array of mail ids)
286 local M ID LST="$('ls' ${Q}/*.mail 2>/dev/null)" # list of mail ids in queue
287
288 CNT=0
289 if [ -n "$LST" ] ; then # list has contents (any mails in queue)
290 for M in $LST ; do # cycle through each
291 ID="$(basename $M .mail)" # take mail id from filename
292 ((CNT++))
293 dsp '' "mail num=[ $CNT ] id=[ $ID ]" # show mail id ## patch in
294 'egrep' -s --colour -h '(^From:|^To:|^Subject:)' "$M" # show mail info
295 [ -n "$2" ] && Q_LST[$CNT]="$ID" # bang mail id into array (note 1-based array indexing)
296 done
297 echo
298 else # no mails ; no contents
299 [ -z "$1" ] && \
300 dsp '' 'no mail in queue' '' || \
301 dsp '' "mail queue is empty (nothing to $1)" '' # inform user
302 exit 0
303 fi
304 }
305
306 ## delete all mail in queue, after confirmation
307 purge_queue() {
308 display_queue 'purge' # show queue contents
309 if ok -n 'remove (purge) all mail from the queue' ; then
310 lock_queue # lock here
311 'rm' -f "$Q"/*.*
312 log 'msmtp queue purged (all mail)'
313 lock_queue -u # unlock here
314 else
315 dsp '' 'nothing done ; queue is untouched' ''
316 fi
317 }
318
319 ## select a single mail from queue ; delete it or send it
320 ## select by mail index (position in queue) or mail id
321 select_mail() { # <-- < 'purge' | 'send' >
322 local OK ID # mail id
323 local -i I NDX # mail index (position in queue)
324
325 while true ; do # purge an individual mail from queue
326 display_queue "$1" 'rec' # show queue contents ; make mail ids array
327
328 ## allow selection also by mail index
329 if [ $CNT -eq 1 ] ; then # only one mail in queue ; take its id
330 NDX=1
331 ID="${Q_LST[1]}"
332 else # more than one mail ; select its id
333 while true ; do # get mail id
334 OK='t' # optimistic to a fault
335 dsp "enter mail number or id to $1" # <-- num or file name (only, no suff)
336 echo -n ' ( <cr> alone to exit ) ..: '
337 read ID
338 [ -n "$ID" ] || return # entry made - or say good bye
339
340 if [ "${ID:4:1}" != '-' ] ; then # mail id *not* entered ; test index num
341 for (( I=0 ; I<${#ID} ; I++ )) ; do # test index number
342 if [[ "${ID:${I}:1}" < '0' || \
343 "${ID:${I}:1}" > '9' ]] ; then
344 dsp '' "[ $ID ] is neither a valid mail id"\
345 'nor a valid mail number' ''
346 unset OK
347 fi
348 done
349 [ -z "$OK" ] && continue # format not ok (not all nums)
350
351 NDX=$ID
352 if [ $NDX -lt 1 ] || [ $NDX -gt $CNT ] ; then # test number range (1 - $CNT)
353 dsp '' "[ $NDX ] is out of range as a mail number"\
354 "validity is from 1 to $CNT"
355 continue # try again
356 fi
357
358 ID="${Q_LST[$NDX]}" # format & range were ok ; use it
359 break # valid mail selection
360 else # mail id entered
361 for (( NDX=1 ; NDX<=${#Q_LST[*]} ; NDX++ )) ; do # find entered id in queue list
362 [ "$ID" = "${Q_LST[$NDX]}" ] && break
363 done
364 [ $NDX -le ${#Q_LST[*]} ] && \
365 break || \
366 dsp '' "mail [ $ID ] not found ; invalid id" # mail selection valid (found) or
367 fi # fell through (not found) complain
368 done # and ask again
369 fi
370
371 if ok '' "$1 :"\
372 " mail num=[ $NDX ]"\
373 " id=[ $ID ]" '' ; then # confirm mail op
374 if [ "$1" = 'purge' ] ; then # purging
375 lock_queue # lock here
376 'rm' -f "$Q"/"$ID".* # msmtp - nukes single mail (both files) in queue
377 log "mail [ $ID ] purged from queue" # log op
378 lock_queue -u # unlock here
379 ALT='t' # mark that a queue alteration has taken place
380 else # sending
381 lock_queue # lock here
382 send_queued_mail "$ID" "$NDX" # send out the mail
383 lock_queue -u # unlock here
384 fi
385 else # user opts out
386 dsp '' 'nothing done to this queued email' # soothe user
387 [ $CNT -eq 1 ] && break # single mail ; user opted out
388 fi
389 dsp '' "--------------------------------------------------"
390 done
391
392 if [ -n "$ALT" ] ; then # queue was changed
393 dsp '' 'done' ''
394 else # queue is untouched
395 dsp '' 'nothing done ; queue is untouched' ''
396 fi
397 }
398
399 #
400 ## ----------------------------------- functions for directly sending mail
401 ## ----------------------------------- 'sendmail' mode - (msmtpq)
402 #
403
404 ## ('sendmail' mode only)
405 ## make base filename id for queue
406 make_id() {
407 local -i INC # increment counter for (possible) base fqp name collision
408
409 ID="$(date +%Y-%m-%d-%H.%M.%S)" # make filename id for queue (global
410 FQP="${Q}/$ID" # make fully qualified pathname vars)
411 ## Philipp Hartwig patch #1
412 if [ -f "${FQP}.mail" -o -f "${FQP}.msmtp" ] ; then # ensure fqp name is unique
413 INC=1 # initial increment
414 while [ -f "${FQP}-${INC}.mail" -o -f "${FQP}-${INC}.msmtp" ] ; do # fqp name w/incr exists
415 (( ++INC )) # bump increment
416 done
417 ID="${ID}-${INC}" # unique ; set id
418 FQP="${FQP}-${INC}" # unique ; set fqp name
419 fi
420 }
421
422 ## ('sendmail' mode only)
423 ## enqueue a mail
424 enqueue_mail() { # <-- all mail args ; mail text via TMP
425 if echo "$@" > "${FQP}.msmtp" ; then # write msmtp command line to queue .msmtp file
426 log "enqueued mail as : [ $ID ] ( $* ) : successful" # (queue .mail file is already there)
427 else # write failed ; bomb
428 log -e "$?" "queueing - writing msmtp cmd line { $* }"\
429 " to [ ${ID}.msmtp ] : failed"
430 fi
431 }
432
433 ## ('sendmail' mode only)
434 ## send a mail (if possible, otherwise enqueue it)
435 ## if send is successful, msmtp will also log it (if enabled in ~/.msmtprc)
436 send_mail() { # <-- all mail args ; mail text via TMP
437 [ -z "$EMAIL_CONN_NOTEST" ] && { # connection test
438 connect_test || {
439 log "mail for [ $* ] : couldn't be sent - host not connected"
440 enqueue_mail "$@" # enqueue the mail
441 }
442 }
443
444 if $MSMTP "$@" < "${FQP}.mail" > /dev/null ; then # send mail using queue .mail fil
445 log "mail for [ $* ] : send was successful" # log it
446 'rm' -f ${FQP}.* # remove all queue mail files .mail & .msmtp file
447 run_queue 'sm' # run/flush any other mails in queue
448 else # send failed - the mail stays in the queue
449 log "mail for [ $* ] : send was unsuccessful ; msmtp exit code was $?"\
450 "enqueued mail as : [ $ID ] ( $* )" # (queue .mail file is already there)
451 fi
452 }
453
454 #
455 ## -- entry point
456 #
457
458 if [ ! "$1" = '--q-mgmt' ] ; then # msmtpq - sendmail mode
459 lock_queue # lock here
460 make_id # make base queue filename id for this mail
461 # write mail body text to queue .mail file
462 cat > "${FQP}.mail" || \
463 log -e "$?" "creating mail body file [ ${FQP}.mail ] : failed" # test for error
464 # write msmtp command line to queue .msmtp file
465 echo "$@" > "${FQP}.msmtp" || \
466 log -e "$?" "creating msmtp cmd line file { $* }"\
467 " to [ ${ID}.msmtp ] : failed" # test for error
468 send_mail "$@" # send the mail if possible, queue it if not
469 lock_queue -u # unlock here
470 else # msmtp-queue - queue management mode
471 shift # trim off first (--q-mgmt) arg
472 OP=${1:1} # trim off first char of OP arg
473 case "$OP" in # sort ops ; run according to spec
474 r) lock_queue
475 run_queue
476 lock_queue -u ;; # run (flush) the queue
477 R) select_mail send ;; # send individual mail(s) in queue
478 d|'') display_queue ;; # display (list) all mail in queue (default)
479 p) select_mail purge ;; # purge individual mail(s) from queue
480 a) purge_queue ;; # purge all mail in queue
481 h) usage ;; # show help
482 *) usage "[ -$OP ] is an unknown msmtp-queue option" ;;
483 esac
484 fi
485
486 exit 0