[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [msmtp-users] msmtpq invokes msmtp while offline
Hello Josiah,
Apologies for the late reply (I've been away for a while) ; I maintain
the msmtpq script and wonder what is going on here. I, as well, would
like to ensure that msmtpq works correctly with emacs mailers ;
hopefully we can work this out. You're correct ; what you are
describing is not the intended behaviour. It seems to me that you may
have an older or a corrupt version of the script ; the current version
does not have this problem (as far as I can tell). If the connect test
is failed then msmtp is *not* invoked - and, of course, then wouldn't
produce spurious messages. I've attached the latest version of msmtpq
; would you test it and see whether or not it produces the same
effect... Please let me know the results ; I'd like to see proper
functionality for emacs...
Thanks & best regards,
Chris
On Sun 1.Sep'13 at 19:47:00 -0700, Josiah Schwab wrote:
> Hi All,
>
> I am in the process of trying to switch from msmtp to msmtpq as my
> sendmail program in emacs. As has previously been discussed on this
> list, it appears that emacs expects silence upon success.
>
> http://sourceforge.net/mailarchive/message.php?msg_id=29729814[1]
>
> After reading the documentation, I have set the environment variable
> EMAIL_QUEUE_QUIET=t and that works great when sending while online.
>
> I do not have similar sucess offline. The messages queue correctly, but
> the subsequent failure of msmtp (because I'm offline), causes problems.
>
> My logs look like:
>
> 2013 01 Sep 19:06:28 : mail for [ -a XXXXXXXX jschwab@...20... ] : couldn't be sent - host not connected
> 2013 01 Sep 19:06:28 : enqueued mail as : [ 2013-09-01-19.06.28 ] ( -a XXXXXXXX jschwab@...20... ) : successful
> 2013 01 Sep 19:06:28 : mail for [ -a XXXXXXXX jschwab@...20... ] : send was unsuccessful ; msmtp exit code was 68
> 2013 01 Sep 19:06:28 : enqueued mail as : [ 2013-09-01-19.06.28 ] ( -a XXXXXXXX jschwab@...20... )
>
> and msmtp also complains on stderr:
>
> msmtp: cannot locate host smtp.gmail.com: Name or service not known
> msmtp: could not send mail (account XXXXXXXX from /home/jschwab/.msmtprc)
>
> Poking around in the code, the issue arises in during the send_mail
> (line 436) function. After failing the connection test (line 438) and
> enquing the message, msmtp is invovked (line 445) almost immediately
> thereafter. Naively, that seems surprising to me. Is this the intended
> behavior?
>
> This is relatively simple to workaround, either by wrapping the msmtp
> call in a connection test (so it doesn't run) or by sending stderr to
> /dev/null (so emacs doesn't hear the complaints.)
>
> I'd like to figure out how to set this up "correctly", so it can just
> be drop-in and work out-of-the-box for emacs users.
>
> Best,
> Josiah Schwab
>
>
> ------------------------------------------------------------------------------
> Learn the latest--Visual Studio 2012, SharePoint 2013, SQL 2012, more!
> Discover the easy way to master current and previous Microsoft technologies
> and advance your career. Get an incredible 1,500+ hours of step-by-step
> tutorial videos with LearnDevNow. Subscribe today and save!
> http://pubads.g.doubleclick.net/gampad/clk?id=58040911&iu=/4140/ostg.clktrk
> _______________________________________________
> msmtp-users mailing list
> msmtp-users@lists.sourceforge.net
> https://lists.sourceforge.net/lists/listinfo/msmtp-users
>
#!/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
## )
##
## 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 commands 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 )
## and uncomment the test for its existence
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, create it (0700)
## before using this script
## 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=~/log/msmtp.queue.log
## ======================================================================================
## msmtpq can use the following environment variables :
## EMAIL_CONN_NOTEST if set will suppress any testing for a connection
## EMAIL_CONN_TEST if =p or unset 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'
##
## ======================================================================================
## !!! define or confirm the following vars if you wish to set !!!
## !!! these properties here in the script - the same properties !!!
## !!! may be set externally, by means of environment variables !!!
## !!! note the internal environment variables, if set, will take !!!
## !!! precedence over properties set via environment variables !!!
## ======================================================================================
##
#EMAIL_CONN_NOTEST=y
#EMAIL_CONN_TEST={ |p|P|n|s} # see settings above for EMAIL_CONN_TEST
EMAIL_CONN_TEST=n
#EMAIL__QUIET=t
## ======================================================================================
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
}
#
## ----------------------------------- functions common to both modes
## ----------------------------------- (msmtpq & msmtp-queue)
#
## 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 <exit code> 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" ] && \
#[ -z "$__QUIET" ] && dsp "$@" # display msg to user, as well as logging it
[ -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" ] || \
# [ -z "$__TEST" ] ) || \
# ( [ "$EMAIL_CONN_TEST" = 'p' ] || \
# [ "$__TEST" = 'p' ] ) ; then # use ping test (default)
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
ping -qnc2 -w10 debian.org >/dev/null 2>&1 || return 1
#elif [ "$EMAIL_CONN_TEST" = 'P' ] || \
# [ "$__TEST" = 'P' ] ; then # use quicker ping test
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' ] || \
# [ "$__TEST" = 'n' ] ; then # use netcat (nc) test
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 >/dev/null 2>&1 || return 1
#elif [ "$EMAIL_CONN_TEST" = 's' ] || \
# [ "$__TEST" = 's' ] ; then # use sh sockets test
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
# thank you to Brian Goose, on the list, for encouraging this
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<cr> 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" ] && \
#[ -z "$__NOTEST" ] && { # connection test
[ -z "$EMAIL_CONN_NOTEST" ] && { # connection test
connect_test || {
log "mail [ $2 ] [ $1 ] from queue ; couldn't be sent - host not connected"
return
}
}
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 ' ( <cr> 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" ] && \
#[ -z "$__NOTEST" ] && { # connection test
[ -z "$EMAIL_CONN_NOTEST" ] && { # connection test
connect_test || {
log "mail for [ $* ] : couldn't be sent - host not connected"
enqueue_mail "$@" # enqueue the mail
return
}
}
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