]>
git.rmz.io Git - dotfiles.git/blob - msmtp/msmtpq
   3 ##-------------------------------------------------------------- 
   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 
   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. 
  14 ##-------------------------------------------------------------- 
  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) 
  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 
  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 
  31 ##  please see the msmtp man page and docs for further mutt settings 
  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' 
  43 ## two essential patches by Philipp Hartwig 
  44 ## 19 Oct 2011 & 27 Oct 2011 
  46 ##-------------------------------------------------------------- 
  47 ## the msmtp queue contains unique filenames of the following form : 
  48 ##   two files for each mail in the queue 
  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 ##-------------------------------------------------------------- 
  58 dsp
() { local L 
; for L 
; do [ -n "$L" ] && echo "  $L" || echo ; done ; } 
  59 err
() { dsp 
'' "$@" '' ; exit 1 ; } 
  62 ## ====================================================================================== 
  63 ##      !!!          please define or confirm the following three vars           !!! 
  64 ##      !!!           before using the msmtpq or msmtp-queue scripts             !!! 
  65 ## ====================================================================================== 
  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 ) 
  72 #  log -e 1 "msmtpq : can't find the msmtp executable [ $MSMTP ]"   # if not found - complain ; quit 
  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 ) 
  80 ## the queue dir - modify this to reflect where you'd like it to be  (no quotes !!) 
  83   err 
'' "msmtpq : can't find msmtp queue directory [ $Q ]" ''     # if not present - complain ; quit 
  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) 
  94 ## the queue log file - modify (or comment out) to taste  (but no quotes !!) 
  95 LOG
=~
/.msmtp
/queue.log
 
  96 ## ====================================================================================== 
  98 umask 077                            # set secure permissions on created directories and files 
 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) 
 104 #for sig in INT TERM EXIT; do 
 105 #  trap "rm -f \"\$TMPFILE\"; [[ $sig == EXIT ]] || kill -$sig $$" $sig 
 107 trap on_exit INT TERM EXIT           
# run 'on_exit' on exit 
 109   [ -n "$LKD" ] && lock_queue 
-u 2>/dev
/null   
# unlock the queue on exit if the lock was set here 
 113 ## ----------------------------------- common functions 
 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 
 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 
 126     shift 2                          # shift opt & its arg off 
 129   [ -z "$EMAIL_QUEUE_QUIET" ] && dsp 
"$@"  # display msg to user, as well as logging it 
 131   if [ -n "$LOG" ] ; then            # log is defined and in use 
 132     for ARG 
; do                     # each msg line out 
 134         echo "$PFX : $ARG" >> "$LOG" # line has content ; send it to log 
 138   if [ -n "$RC" ] ; then             # an error ; leave w/error return 
 139     [ -n "$LKD" ] && lock_queue 
-u   # unlock here (if locked) 
 141       echo "    exit code = $RC" >> "$LOG" # logging ok ; send exit code to log 
 142     exit $RC                         # exit w/return code 
 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 
 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 
 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 
 165   elif [ "$1" = '-u' ] ; then        # unlock queue 
 166     'rmdir' "$LOK"                   # remove the lock 
 167     unset LKD                        
# unset flag 
 171 ## test whether system is connected 
 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 
 202 ## ----------------------------------- functions for queue management 
 203 ## ----------------------------------- queue maintenance mode - (msmtp-queue) 
 206 ## show queue maintenance functions 
 207 usage
() {        # <-- error msg 
 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 ; } 
 221 ## get user [y/n] acknowledgement 
 223   local R YN
='Y/n'                   # default to yes 
 226     { YN
='y/N' ; shift ; }           # default to no ; change prompt ; shift off spec 
 230     echo -n "  ok [${YN}] ..: " 
 235       '')  [ "$YN" = 'Y/n' ] && return 0 || return 1 ;; 
 236       *)   echo 'yYnN<cr> only please' ;; 
 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 
 246   if [ -f "${FQP}.msmtp" ] ; then    # corresponding .msmtp file found 
 247     [ -z "$EMAIL_CONN_NOTEST" ] && { # connection test 
 249         log 
"mail [ $2 ] [ $1 ] from queue ; couldn't be sent - host not connected" 
 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 ... 
 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) 
 269 run_queue
() {    # <- 'sm' mode      # run queue 
 270   local M LST
="$('ls' $Q/*.mail 2>/dev/null)"            # list of mails in queue 
 273   if [ -n "$LST" ] ; then            # something in queue 
 274     for M 
in $LST ; do               # process all mails 
 276       send_queued_mail 
"$(basename $M .mail)" "$NDX"     # send mail - pass {id} only 
 278   else                               # queue is empty 
 280       dsp 
'' 'mail queue is empty (nothing to send)' '' 
 281   fi                                 # inform user (if not running in sendmail mode) 
 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 
 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 
 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) 
 298   else                               # no mails ; no contents 
 300       dsp 
'' 'no mail in queue' '' || \
 
 301       dsp 
'' "mail queue is empty (nothing to $1)" ''    # inform user 
 306 ## delete all mail in queue, after confirmation 
 308   display_queue 
'purge'              # show queue contents 
 309   if ok 
-n 'remove (purge) all mail from the queue' ; then 
 310     lock_queue                       
# lock here 
 312     log 
'msmtp queue purged (all mail)' 
 313     lock_queue 
-u                    # unlock here 
 315     dsp 
'' 'nothing done ; queue is untouched' '' 
 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) 
 325   while true 
; do                                    # purge an individual mail from queue 
 326     display_queue 
"$1" 'rec'                         # show queue contents ; make mail ids array 
 328     ## allow selection also by mail index 
 329     if [ $CNT -eq 1 ] ; then                         # only one mail in queue ; take its id 
 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 ) ..: ' 
 338         [ -n "$ID" ] || return                       # entry made - or say good bye 
 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' '' 
 349           [ -z "$OK" ] && continue                   # format not ok (not all nums) 
 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" 
 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 
 364           [ $NDX -le ${#Q_LST[*]} ] && \
 
 366             dsp 
'' "mail [ $ID ] not found ; invalid id" # mail selection valid (found) or 
 367         fi                                               # fell through (not found) complain 
 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 
 381         lock_queue                                   
# lock here 
 382         send_queued_mail 
"$ID" "$NDX"                # send out the mail 
 383         lock_queue 
-u                                # unlock here 
 386       dsp 
'' 'nothing done to this queued email'     # soothe user 
 387       [ $CNT -eq 1 ] && break                        # single mail ; user opted out 
 389     dsp 
'' "--------------------------------------------------" 
 392   if [ -n "$ALT" ] ; then            # queue was changed 
 394   else                               # queue is untouched 
 395     dsp 
'' 'nothing done ; queue is untouched' '' 
 400 ## ----------------------------------- functions for directly sending mail 
 401 ## ----------------------------------- 'sendmail' mode - (msmtpq) 
 404 ## ('sendmail' mode only) 
 405 ## make base filename id for queue 
 407   local -i INC                       
# increment counter for (possible) base fqp name collision 
 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 
 417           ID
="${ID}-${INC}"                # unique ; set id 
 418           FQP
="${FQP}-${INC}"              # unique ; set fqp name 
 422 ## ('sendmail' mode only) 
 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" 
 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 
 439       log 
"mail for [ $* ] : couldn't be sent - host not connected" 
 440       enqueue_mail 
"$@"              # enqueue the mail 
 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) 
 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 
 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" ;;