X-Git-Url: https://git.rmz.io/dotfiles.git/blobdiff_plain/e8a6cf2a1c5ab04da7f837ff863165782293bb4a..c3a03e540dae7adafed0124b1a83c63436b597d7:/msmtp/msmtpq diff --git a/msmtp/msmtpq b/msmtp/msmtpq new file mode 100755 index 0000000..7a4850e --- /dev/null +++ b/msmtp/msmtpq @@ -0,0 +1,486 @@ +#!/usr/bin/env bash + +##-------------------------------------------------------------- +## +## msmtpq : queue funtions to both use & manage the msmtp queue, +## as it was defined by Martin Lambers +## Copyright (C) 2008, 2009, 2010, 2011 Chris Gianniotis +## +## This program is free software: you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation, either version 3 of the License, or, at +## your option, any later version. +## +##-------------------------------------------------------------- + +## msmtpq is meant to be used by an email client - in 'sendmail' mode +## for this purpose, it is invoked directly as 'msmtpq' +## it is also meant to be used to maintain the msmtp queue +## evoked by the wrapper script 'msmtp-queue' +## (which calls this script as msmtpq --q-mgmt) + +## there is a queue log file, distinct from the msmtp log, +## for all events & operations on the msmtp queue +## that is defined below + +## (mutt users, using msmtpq in 'sendmail' mode, +## should make at least the following two settings in their .muttrc +## set sendmail = /path/to/msmtpq +## set sendmail_wait = -1 +## +## please see the msmtp man page and docs for further mutt settings +## and optimisations +## ) + +## msmtpq now uses the following environment variables : +## EMAIL_CONN_NOTEST if set will suppress any testing for a connection +## EMAIL_CONN_TEST if unset or =p will use a ping test (debian.org) for a connection +## if =P will use a fast ping test (8.8.8.8) for a connection +## if =n will use netcat (nc) to test for a connection +## if =s will use bash sockets to test for a connection +## EMAIL_QUEUE_QUIET if set will cause suppression of messages and 'chatter' + +## two essential patches by Philipp Hartwig +## 19 Oct 2011 & 27 Oct 2011 + +##-------------------------------------------------------------- +## the msmtp queue contains unique filenames of the following form : +## two files for each mail in the queue +## +## creates new unique filenames of the form : +## MLF: ccyy-mm-dd-hh.mm.ss[-x].mail -- mail file +## MSF: ccyy-mm-dd-hh.mm.ss[-x].msmtp -- msmtp command line file +## where x is a consecutive number only appended for uniqueness +## if more than one mail per second is sent +##-------------------------------------------------------------- + + +dsp() { local L ; for L ; do [ -n "$L" ] && echo " $L" || echo ; done ; } +err() { dsp '' "$@" '' ; exit 1 ; } + + +## ====================================================================================== +## !!! please define or confirm the following three vars !!! +## !!! before using the msmtpq or msmtp-queue scripts !!! +## ====================================================================================== +## +## only if necessary (in unusual circumstances - e.g. embedded systems), +## enter the location of the msmtp executable (no quotes !!) +## e.g. ( MSMTP=/path/to/msmtp ) +MSMTP=msmtp +#[ -x "$MSMTP" ] || \ +# log -e 1 "msmtpq : can't find the msmtp executable [ $MSMTP ]" # if not found - complain ; quit +## +## set the queue var to the location of the msmtp queue directory +## if the queue dir doesn't yet exist, better to create it (0700) +## before using this routine +## e.g. ( mkdir msmtp.queue ) +## ( chmod 0700 msmtp.queue ) +## +## the queue dir - modify this to reflect where you'd like it to be (no quotes !!) +Q=~/.msmtp/queue +[ -d "$Q" ] || \ + err '' "msmtpq : can't find msmtp queue directory [ $Q ]" '' # if not present - complain ; quit +## +## set the queue log file var to the location of the msmtp queue log file +## where it is or where you'd like it to be +## ( note that the LOG setting could be the same as the ) +## ( 'logfile' setting in .msmtprc - but there may be ) +## ( some advantage in keeping the two logs separate ) +## if you don't want the log at all unset (comment out) this var +## LOG=~/log/msmtp.queue.log --> #LOG=~/log/msmtp.queue.log +## (doing so would be inadvisable under most conditions, however) +## +## the queue log file - modify (or comment out) to taste (but no quotes !!) +LOG=~/.msmtp/queue.log +## ====================================================================================== + +umask 077 # set secure permissions on created directories and files + +declare -i CNT # a count of mail(s) currently in the queue +declare -a Q_LST # queue list array ; used selecting a mail (to send or remove) + +## do ; test this ! +#for sig in INT TERM EXIT; do +# trap "rm -f \"\$TMPFILE\"; [[ $sig == EXIT ]] || kill -$sig $$" $sig +#done +trap on_exit INT TERM EXIT # run 'on_exit' on exit +on_exit() { + [ -n "$LKD" ] && lock_queue -u 2>/dev/null # unlock the queue on exit if the lock was set here +} + +# +## ----------------------------------- common functions +# + +## make an entry to the queue log file, possibly an error +## (log queue changes only ; not interactive chatter) +## usage : log [ -e errcode ] msg [ msg ... ] +## opts : -e an error ; log msg & terminate w/prejudice +## display msg to user, as well +log() { + local ARG RC PFX="$('date' +'%Y %d %b %H:%M:%S')" + # time stamp prefix - "2008 13 Mar 03:59:45 " + if [ "$1" = '-e' ] ; then # there's an error exit code + RC="$2" # take it + shift 2 # shift opt & its arg off + fi + + [ -z "$EMAIL_QUEUE_QUIET" ] && dsp "$@" # display msg to user, as well as logging it + + if [ -n "$LOG" ] ; then # log is defined and in use + for ARG ; do # each msg line out + [ -n "$ARG" ] && \ + echo "$PFX : $ARG" >> "$LOG" # line has content ; send it to log + done + fi + + if [ -n "$RC" ] ; then # an error ; leave w/error return + [ -n "$LKD" ] && lock_queue -u # unlock here (if locked) + [ -n "$LOG" ] && \ + echo " exit code = $RC" >> "$LOG" # logging ok ; send exit code to log + exit $RC # exit w/return code + fi +} + +## write/remove queue lockfile for a queue op +lock_queue() { # <-- '-u' to remove lockfile + local LOK="${Q}/.lock" # lock file name + local -i MAX=240 SEC=0 # max seconds to gain a lock ; seconds waiting + + if [ -z "$1" ] ; then # lock queue + ## Philipp Hartwig patch #2 + 'mkdir' "$LOK" 2>/dev/null && LKD='t' + while [ -z "$LKD" ] && (( SEC < MAX )) ; do # lock file present + sleep 1 # wait a second + (( ++SEC )) # accumulate seconds + 'mkdir' "$LOK" 2>/dev/null && LKD='t' # make lockdir ; lock queue ; set flag + done # try again while locked for MAX secs + [ -z "$LKD" ] && \ + err '' "cannot use queue $Q : waited $MAX seconds for"\ + " lockdir [ $LOK ] to vanish ; giving up"\ + 'if you are certain that no other instance of this script'\ + " is running, then 'rmdir' the lock dir manually" '' # lock file still there, give up + + elif [ "$1" = '-u' ] ; then # unlock queue + 'rmdir' "$LOK" # remove the lock + unset LKD # unset flag + fi +} + +## test whether system is connected +## returns t/f (0/1) +connect_test() { + if [ -z "$EMAIL_CONN_TEST" ] || \ + [ "$EMAIL_CONN_TEST" = 'p' ] ; then # use ping test (default) + # verify net connection - ping ip address of debian.org + # would ping -qnc2 -w4 be better ? + # would ping -qnc1 -w10 or -w20 be better ? + ping -qnc1 -w4 debian.org >/dev/null 2>&1 || return 1 + elif [ "$EMAIL_CONN_TEST" = 'P' ] ; then # use quicker ping test + # I personally think that including a DNS lookup + # is a better connection test but some + # have found the above test too slow + ping -qnc1 -w4 8.8.8.8 >/dev/null 2>&1 || return 1 + elif [ "$EMAIL_CONN_TEST" = 'n' ] ; then # use netcat (nc) test + # must, of course, have netcat (nc) installed + which nc >/dev/null 2>&1 || \ + log -e 1 "msmtpq : can't find netcat executable [ nc ]" # if not found - complain ; quit + 'nc' -vz www.debian.org 80 || return 1 + elif [ "$EMAIL_CONN_TEST" = 's' ] ; then # use sh sockets test + # note that this does not work on debian systems + # where bash opened sockets are suppressed for security reasons + # this should be ok for single user systems (including embedded systems) + # test whether this is supported on your system before using + exec 3<>/dev/udp/debian.org/80 || return 1 # open socket on site ; use dns + exec 3<&- ; exec 3>&- # close socket + fi + return 0 +} + +# +## ----------------------------------- functions for queue management +## ----------------------------------- queue maintenance mode - (msmtp-queue) +# + +## show queue maintenance functions +usage() { # <-- error msg + dsp ''\ + 'usage : msmtp-queue functions' ''\ + ' msmtp-queue < op >'\ + ' ops : -r run (flush) mail queue - all mail in queue'\ + ' -R send selected individual mail(s) in queue'\ + ' -d display (list) queue contents (<-- default)'\ + ' -p purge individual mail(s) from queue'\ + ' -a purge all mail in queue'\ + ' -h this helpful blurt' ''\ + ' ( one op only ; any others ignored )' '' + [ -z "$1" ] && exit 0 || { dsp "$@" '' ; exit 1 ; } +} + +## get user [y/n] acknowledgement +ok() { + local R YN='Y/n' # default to yes + + [ "$1" = '-n' ] && \ + { YN='y/N' ; shift ; } # default to no ; change prompt ; shift off spec + + dsp "$@" + while true ; do + echo -n " ok [${YN}] ..: " + read R + case $R in + y|Y) return 0 ;; + n|N) return 1 ;; + '') [ "$YN" = 'Y/n' ] && return 0 || return 1 ;; + *) echo 'yYnN only please' ;; + esac + done +} + +## send a queued mail out via msmtp +send_queued_mail() { # <-- mail id + local FQP="${Q}/${1}" # fully qualified path name + local -i RC=0 # for msmtp exit code + + if [ -f "${FQP}.msmtp" ] ; then # corresponding .msmtp file found + [ -z "$EMAIL_CONN_NOTEST" ] && { # connection test + connect_test || \ + log "mail [ $2 ] [ $1 ] from queue ; couldn't be sent - host not connected" + } + + if $MSMTP $(< "${FQP}.msmtp") < "${FQP}.mail" ; then # this mail goes out the door + log "mail [ $2 ] [ $1 ] from queue ; send was successful ; purged from queue" # good news to user + 'rm' -f ${FQP}.* # nuke both queue mail files + ALT='t' # set queue changed flag + else # send was unsuccessful + RC=$? # take msmtp exit code + log "mail [ $2 ] [ $1 ] from queue ; send failed ; msmtp rc = $RC" # bad news ... + fi + return $RC # func returns exit code + else # corresponding MSF file not found + log "preparing to send .mail file [ $1 ] [ ${FQP}.mail ] but"\ + " corresponding .msmtp file [ ${FQP}.msmtp ] was not found in queue"\ + ' skipping this mail ; this is worth looking into' # give user the bad news + fi # (but allow continuation) +} + +## run (flush) queue +run_queue() { # <- 'sm' mode # run queue + local M LST="$('ls' $Q/*.mail 2>/dev/null)" # list of mails in queue + local -i NDX=0 + + if [ -n "$LST" ] ; then # something in queue + for M in $LST ; do # process all mails + ((NDX++)) + send_queued_mail "$(basename $M .mail)" "$NDX" # send mail - pass {id} only + done + else # queue is empty + [ -z "$1" ] && \ + dsp '' 'mail queue is empty (nothing to send)' '' + fi # inform user (if not running in sendmail mode) +} + +## display queue contents +display_queue() { # <-- { 'purge' | 'send' } (op label) ; { 'rec' } (record array of mail ids) + local M ID LST="$('ls' ${Q}/*.mail 2>/dev/null)" # list of mail ids in queue + + CNT=0 + if [ -n "$LST" ] ; then # list has contents (any mails in queue) + for M in $LST ; do # cycle through each + ID="$(basename $M .mail)" # take mail id from filename + ((CNT++)) + dsp '' "mail num=[ $CNT ] id=[ $ID ]" # show mail id ## patch in + 'egrep' -s --colour -h '(^From:|^To:|^Subject:)' "$M" # show mail info + [ -n "$2" ] && Q_LST[$CNT]="$ID" # bang mail id into array (note 1-based array indexing) + done + echo + else # no mails ; no contents + [ -z "$1" ] && \ + dsp '' 'no mail in queue' '' || \ + dsp '' "mail queue is empty (nothing to $1)" '' # inform user + exit 0 + fi +} + +## delete all mail in queue, after confirmation +purge_queue() { + display_queue 'purge' # show queue contents + if ok -n 'remove (purge) all mail from the queue' ; then + lock_queue # lock here + 'rm' -f "$Q"/*.* + log 'msmtp queue purged (all mail)' + lock_queue -u # unlock here + else + dsp '' 'nothing done ; queue is untouched' '' + fi +} + +## select a single mail from queue ; delete it or send it +## select by mail index (position in queue) or mail id +select_mail() { # <-- < 'purge' | 'send' > + local OK ID # mail id + local -i I NDX # mail index (position in queue) + + while true ; do # purge an individual mail from queue + display_queue "$1" 'rec' # show queue contents ; make mail ids array + + ## allow selection also by mail index + if [ $CNT -eq 1 ] ; then # only one mail in queue ; take its id + NDX=1 + ID="${Q_LST[1]}" + else # more than one mail ; select its id + while true ; do # get mail id + OK='t' # optimistic to a fault + dsp "enter mail number or id to $1" # <-- num or file name (only, no suff) + echo -n ' ( alone to exit ) ..: ' + read ID + [ -n "$ID" ] || return # entry made - or say good bye + + if [ "${ID:4:1}" != '-' ] ; then # mail id *not* entered ; test index num + for (( I=0 ; I<${#ID} ; I++ )) ; do # test index number + if [[ "${ID:${I}:1}" < '0' || \ + "${ID:${I}:1}" > '9' ]] ; then + dsp '' "[ $ID ] is neither a valid mail id"\ + 'nor a valid mail number' '' + unset OK + fi + done + [ -z "$OK" ] && continue # format not ok (not all nums) + + NDX=$ID + if [ $NDX -lt 1 ] || [ $NDX -gt $CNT ] ; then # test number range (1 - $CNT) + dsp '' "[ $NDX ] is out of range as a mail number"\ + "validity is from 1 to $CNT" + continue # try again + fi + + ID="${Q_LST[$NDX]}" # format & range were ok ; use it + break # valid mail selection + else # mail id entered + for (( NDX=1 ; NDX<=${#Q_LST[*]} ; NDX++ )) ; do # find entered id in queue list + [ "$ID" = "${Q_LST[$NDX]}" ] && break + done + [ $NDX -le ${#Q_LST[*]} ] && \ + break || \ + dsp '' "mail [ $ID ] not found ; invalid id" # mail selection valid (found) or + fi # fell through (not found) complain + done # and ask again + fi + + if ok '' "$1 :"\ + " mail num=[ $NDX ]"\ + " id=[ $ID ]" '' ; then # confirm mail op + if [ "$1" = 'purge' ] ; then # purging + lock_queue # lock here + 'rm' -f "$Q"/"$ID".* # msmtp - nukes single mail (both files) in queue + log "mail [ $ID ] purged from queue" # log op + lock_queue -u # unlock here + ALT='t' # mark that a queue alteration has taken place + else # sending + lock_queue # lock here + send_queued_mail "$ID" "$NDX" # send out the mail + lock_queue -u # unlock here + fi + else # user opts out + dsp '' 'nothing done to this queued email' # soothe user + [ $CNT -eq 1 ] && break # single mail ; user opted out + fi + dsp '' "--------------------------------------------------" + done + + if [ -n "$ALT" ] ; then # queue was changed + dsp '' 'done' '' + else # queue is untouched + dsp '' 'nothing done ; queue is untouched' '' + fi +} + +# +## ----------------------------------- functions for directly sending mail +## ----------------------------------- 'sendmail' mode - (msmtpq) +# + +## ('sendmail' mode only) +## make base filename id for queue +make_id() { + local -i INC # increment counter for (possible) base fqp name collision + + ID="$(date +%Y-%m-%d-%H.%M.%S)" # make filename id for queue (global + FQP="${Q}/$ID" # make fully qualified pathname vars) + ## Philipp Hartwig patch #1 + if [ -f "${FQP}.mail" -o -f "${FQP}.msmtp" ] ; then # ensure fqp name is unique + INC=1 # initial increment + while [ -f "${FQP}-${INC}.mail" -o -f "${FQP}-${INC}.msmtp" ] ; do # fqp name w/incr exists + (( ++INC )) # bump increment + done + ID="${ID}-${INC}" # unique ; set id + FQP="${FQP}-${INC}" # unique ; set fqp name + fi +} + +## ('sendmail' mode only) +## enqueue a mail +enqueue_mail() { # <-- all mail args ; mail text via TMP + if echo "$@" > "${FQP}.msmtp" ; then # write msmtp command line to queue .msmtp file + log "enqueued mail as : [ $ID ] ( $* ) : successful" # (queue .mail file is already there) + else # write failed ; bomb + log -e "$?" "queueing - writing msmtp cmd line { $* }"\ + " to [ ${ID}.msmtp ] : failed" + fi +} + +## ('sendmail' mode only) +## send a mail (if possible, otherwise enqueue it) +## if send is successful, msmtp will also log it (if enabled in ~/.msmtprc) +send_mail() { # <-- all mail args ; mail text via TMP + [ -z "$EMAIL_CONN_NOTEST" ] && { # connection test + connect_test || { + log "mail for [ $* ] : couldn't be sent - host not connected" + enqueue_mail "$@" # enqueue the mail + } + } + + if $MSMTP "$@" < "${FQP}.mail" > /dev/null ; then # send mail using queue .mail fil + log "mail for [ $* ] : send was successful" # log it + 'rm' -f ${FQP}.* # remove all queue mail files .mail & .msmtp file + run_queue 'sm' # run/flush any other mails in queue + else # send failed - the mail stays in the queue + log "mail for [ $* ] : send was unsuccessful ; msmtp exit code was $?"\ + "enqueued mail as : [ $ID ] ( $* )" # (queue .mail file is already there) + fi +} + +# +## -- entry point +# + +if [ ! "$1" = '--q-mgmt' ] ; then # msmtpq - sendmail mode + lock_queue # lock here + make_id # make base queue filename id for this mail + # write mail body text to queue .mail file + cat > "${FQP}.mail" || \ + log -e "$?" "creating mail body file [ ${FQP}.mail ] : failed" # test for error + # write msmtp command line to queue .msmtp file + echo "$@" > "${FQP}.msmtp" || \ + log -e "$?" "creating msmtp cmd line file { $* }"\ + " to [ ${ID}.msmtp ] : failed" # test for error + send_mail "$@" # send the mail if possible, queue it if not + lock_queue -u # unlock here +else # msmtp-queue - queue management mode + shift # trim off first (--q-mgmt) arg + OP=${1:1} # trim off first char of OP arg + case "$OP" in # sort ops ; run according to spec + r) lock_queue + run_queue + lock_queue -u ;; # run (flush) the queue + R) select_mail send ;; # send individual mail(s) in queue + d|'') display_queue ;; # display (list) all mail in queue (default) + p) select_mail purge ;; # purge individual mail(s) from queue + a) purge_queue ;; # purge all mail in queue + h) usage ;; # show help + *) usage "[ -$OP ] is an unknown msmtp-queue option" ;; + esac +fi + +exit 0