[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [msmtp-users] [denis.golovan@...20...: msmtp queue script]



Hi Denis,

Thanks for the input & critique of the msmtpq script. I, as well, am
(obviously) not a bash guru ... I am in agreement with most of your
changes - but with some points of discussion (below). Thank you
particularly for pointing out that the /bin/cat function doesn't
repeat its output after the first run ! I hadn't known that that was
the case.

Actually, let me run through the patches in sequence :

- The queue directory ($Q) was meant to be user definable/changeable ;
it seems to need somewhat better, more obvious
documentation/labelling.

- I am not certain that the simple 'dsp' function should be redirected
to the log file. It was meant for simple interactive feedback of the
msmtpq maintenance functions : interactive display/edit/purge the
queue, etc. It seems to me that logging is better done by setting the
'logfile' var in the .msmtprc file. That will take care of all mail
transmission by msmtp - although perhaps it *does* make sense to log
whenever a mail is written to the queue ... hmmm.

- Quite right ; it doesn't make sense to background the sending of a
mail and to not wait for a return code. I think that I didn't want to
wait for the process to return in the event of a long mail being sent
- but your revision makes more sense : send it out and wait for a
result code.

- I am not certain of the last major point. Your approach of always
using a temporary file for the mail content would definitely always
work but it has the same functional pattern as the original
'msmtp-enqueue.sh' - that is, a mail is always written to a file in
the queue and then, if the host is connected the file is immediately
sent out (the queue is immediately flushed, if possible). I would
rather try to avoid the overhead of always writing a temporary file -
only writing a mail to the queue when necessary (when the machine is
disconnected). Naturally, the fact that the script's standard input
can only be read once (thanks again Denis !) complicates things a
bit. Two possible methods to avoid always having *to* write a temp
file occur to me :

    -- First, before stdin is read, the connection to the net can be
    tested and then whether to pass the mail to msmtp or to write to
    the queue can be decided. There is then a brief time of exposure
    or risk where the connection might be interrupted before the mail
    transmission would be complete - but then an error code may be
    returned to the mail client indicating a failed send. E.g. mutt
    would then report a failed send to the user. The mail could easily
    enough be resent.

    -- Second, the script's stdin can be read into a variable and the
    send method can then be decided accordingly. As I understand it,
    shell vars can be of any length and could accomodate any length of
    email body text and attachments, etc. If this is the case, I would
    prefer to do it this way ; it seems simpler, more direct and
    less flakey than the above method. Moreover, a file only needs to
    be written (to the queue) when the machine is disconnected. 
    However, not being a bash guru, would you (or anyone !) care to
    comment on the idea of reading all incoming mails into a variable
    ? Is it in any way a bad idea ? Am I being simplistic/naif to
    assume that any length of mail text can be accomodated into a
    shell var ?

- Come to think of it, the mail handling functions of msmtpq & msmtpQ
(not necessarily the queue maintenance functions, though) should
always return a code ... Another oversight ...

Thanks again for the critique ; please let me know about the above.

Best wishes & kind regards,

Chris

On Wed 27.Feb'08 at  8:19:12 +0200, Denis Golovan wrote:
> Hi, all!
> 
> If somebody uses msmtpq script for queue management. Please give feedback on my patch.
> Thanks.
> 
> ----- Forwarded message from Denis Golovan <denis.golovan@...20...> -----
> 
> From: Denis Golovan <denis.golovan@...20...>
> Subject: msmtp queue script
> To: marlam@...23...
> Date: Tue, 26 Feb 2008 15:40:49 +0200
> User-Agent: Mutt/1.5.16 (2007-06-09)
> Message-ID: <20080226134049.GY8748@...134...>
> 
> Hi, Martin.
> 
> I've read your post in msmtp mailing list on queue script.
> I found your work useful, thanks.
> 
> But, I found some bugs.
> 
> Here is patched version of send_mail function (I am not bash guru :) ). 
> ** quotes mean my comments. Patch is also in attach.
> 
> Using previous version of send_mail function lead to enqueueing successfully sent messages in mail folder. All *.mail files were empty though (cat 
> patch part).
> Also I don't quite understand & in msmtp invocation - it seems to me, that result code could be wrong??
> 
> My app versions:
> GNU bash, version 3.2.17(1)-release (i686-pc-linux-gnu)
> msmtp version 1.4.7
> 
> Some feature request, if you please :)
> 1. Form some kind of options area with constants. Such as google ip, log file and etc
> 2. Write dsp function output to log
> 3. Insert script version :). BTW what is the current (msmtp mailing list 2008-01-27) version number?
> 
> If you are not against, I'd like to make those feature request changes myself and send you for "publishing".
> 
> Best regards,
> Denis Golovan
> 
> --- msmtpq	2008-02-26 15:28:03.000000000 +0200
> +++ msmtpq.1	2008-02-26 15:32:28.000000000 +0200
> @@ -25,9 +25,10 @@
>  #--------------------------------------------------------------
>  
>  #Q=$HOME/.msmtp.queue                 # the queue
> -Q=~/.msmtp.queue                     # the queue
> +Q=~/mail/mqueue                      # the queue
>  umask 077                            # set secure permissions on created directories and files
>  declare -i CNT                       # a count of mail(s) in the queue
> +declare TFILE
>  
>  usage() {
>    echo
> @@ -86,7 +87,7 @@
>  
>    for M ; do
>      if [ -n "$M" ] ; then
> -      echo "  $M"
> +      echo "  $M" >> ~/mail/logs/msmtpq.log
>      else
>        echo
>      fi
> @@ -100,9 +101,11 @@
>    local -i RC                        # msmtp return code
>  
>    if /bin/ping -qnc 1 -w 2 64.233.183.147 &> /dev/null ; then  # ip addr of www.google.com
> -    /bin/cat | /usr/bin/msmtp "$@" > /dev/null &   # send mail immediately
> +    #/bin/cat | /usr/bin/msmtp "$@" > /dev/null &   # **seems pretty strange - return without waiting for result**  send mail immediately  
> +    /bin/cat "$TFILE" | /usr/bin/msmtp "$@" > /dev/null   # send mail immediately
>      RC=$?
> -    if $RC ; then                    # send was successful
> +    #if $RC; then   #**by some reason 0 result leads to "mail send was unsuccessful"**
> +    if [ $RC = 0 ]; then     # send was successful, **works as expected**
>        dsp 'mail sent'
>      else
>        dsp 'mail send was unsuccessful' \
> @@ -130,7 +133,7 @@
>  
>    echo "$@" > "${BASE}.msmtp" || dsp -e 'writing msmtp cmd line' \
>             "to ${BASE}.msmtp failed" # write command line to .msmtp file
> -  /bin/cat  > "${BASE}.mail" || dsp -e 'writing mail' \
> +  /bin/cat "$TFILE" > "${BASE}.mail" || dsp -e 'writing mail' \
>             "to ${BASE}.mail failed"  # write mail to .mail file
>    dsp 'mail queued'
>  }
> @@ -252,10 +255,22 @@
>    done
>  }
>  
> +cleanup()
> +{
> +  #remove temporary message file
> +  rm -f $TFILE
> +}
> +
>  #
>  ## -- entry point
>  #
>  
> +trap cleanup 0 1 2 3 6 9 14 15
> +#message from standart input
> +#because /bin/cat does not repeat its output on subsequent runs :)
> +TFILE="/tmp/$(basename $0).$$.tmp"
> +/bin/cat > $TFILE
> +
>  # check what we're being called as - 'msmtpq' or 'msmtpQ'
>  if [ "$(/usr/bin/basename $0)" == 'msmtpQ' ] ; then  # called as msmtpQ
>    if [ -n "$1" ] ; then              # if arg(s)
> 
> #!/bin/sh
> 
> #--------------------------------------------------------------
> #
> #  msmtpq : queue funtions to use and manage the msmtp queue,
> #    as it was defined by Martin Lambers
> #  Copyright (C) 2008 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.
> #
> #--------------------------------------------------------------
> 
> #--------------------------------------------------------------
> # the msmtp queue contains unique filenames of the following form :
> #   two for each mail in the queue
> #
> # create 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 you send more than one mail per second
> #--------------------------------------------------------------
> 
> #Q=$HOME/.msmtp.queue                 # the queue
> Q=~/mail/mqueue                      # the queue
> umask 077                            # set secure permissions on created directories and files
> declare -i CNT                       # a count of mail(s) in the queue
> declare TFILE
> 
> usage() {
>   echo
>   echo '  usage : msmtpq functions'
>   echo
>   echo '          msmtpq <op> --'
>   echo '          ops : -s   send a mail'
>   echo '                -sr  send a mail, then run queue'
>   echo '                -e   enqueue a mail'
>   echo '                -er  enqueue a mail, then run queue'
>   echo
>   echo '                -r   run (flush) mail queue'
>   echo '                -d   display (list) queue'
>   echo '                -m   purge a single mail from queue'
>   echo '                -a   purge all mail in queue'
>   echo '                -h   this helpful blurt'
>   echo
>   echo '  -s   an outgoing mail will normally be sent directly out,'
>   echo '         if connected, otherwise it is queued'
>   echo '  -e   causes the mail to be enqueued'
>   echo '  -r   flushes the mail queue, if anything is there'
>   echo '  -sr  if connected sends out the mail then flushes the queue'
>   echo '         otherwise enqueues it'
>   echo '  -er  enqueues the mail then flushes the queue'
>   echo '         (but why would anyone want to do this ?)'
>   echo
>   echo '       - note that only one op per invocation is allowed'
>   echo '       - the only combined ops are the ones listed above'
>   echo '       - if more than one op is specified, the first one'
>   echo '           only is executed'
>   echo '       - for -s, -e, -sr or -er the msmtpq op MUST be followed'
>   echo "           by '--' to separate it from any msmtp options"
>   echo
> }
> 
> # verify that msmtp queue is present
>   # the first version can be used if you'd like to create the queue dir
>   # if it's not found ; I'd rather just be warned if it's not there
> check_queue() {                      # make certain queue dir is present
>   #if [ ! -d "$Q" ] ; then            # queue dir present or
> 	#  /bin/mkdir -p "$Q" || dsp -e 'could not create queue dir'
>   #fi                                 # create it
>   if [ ! -d "$Q" ] ; then            # queue dir not present
>     dsp -e "  can't find $Q"         # complain
>   fi
> }
> 
> # display a message, possibly an error
> dsp() {
>   local M ERR
> 
>   if [ "$1" == '-e' ] ; then
>     ERR='t'
>     shift
>   fi
> 
>   for M ; do
>     if [ -n "$M" ] ; then
>       echo "  $M" >> ~/mail/logs/msmtpq.log
>     else
>       echo
>     fi
>   done
> 
>   [ -n "$ERR" ] && exit 1
> }
> 
> # send a mail (otherwise queue it)
> send_mail() {    # <-- all mail args # send or enqueue a mail
>   local -i RC                        # msmtp return code
> 
>   if /bin/ping -qnc 1 -w 2 64.233.183.147 &> /dev/null ; then  # ip addr of www.google.com
>     #/bin/cat | /usr/bin/msmtp "$@" > /dev/null &   # **seems pretty strange - return without waiting for result**  send mail immediately  
>     /bin/cat "$TFILE" | /usr/bin/msmtp "$@" > /dev/null   # send mail immediately
>     RC=$?
>     #if $RC; then   #**by some reason 0 result leads to "mail send was unsuccessful"**
>     if [ $RC = 0 ]; then     # send was successful, **works as expected**
>       dsp 'mail sent'
>     else
>       dsp 'mail send was unsuccessful' \
>       dsp "msmtp rc = $RC ; enqueuing mail"
>       enqueue_mail "$@"
>     fi
>   else                               # not connected
>     enqueue_mail "$@"                # enqueue mail
>   fi
> }
> 
> # enqueue a mail
> enqueue_mail() { # <-- all mail args # enqueue a mail
>   local BASE                         # base name
>   local -i INC                       # increment counter for basename collision
> 
>   BASE="$Q/`/bin/date +%Y-%m-%d-%H.%M.%S`"  # make unique base filename
>   if [ -f "$BASE.*" ] ; then         # base filename exists
> 	  INC=1                            # initial increment to 1
> 	  while [ -f "${BASE}-${INC}.*" ] ; do
>       INC="(( ++INC ))"              # filename exists ; bump increment
> 	  done
> 	  BASE="${BASE}-${INC}"            # unique ; set base filename
>   fi
> 
>   echo "$@" > "${BASE}.msmtp" || dsp -e 'writing msmtp cmd line' \
>            "to ${BASE}.msmtp failed" # write command line to .msmtp file
>   /bin/cat "$TFILE" > "${BASE}.mail" || dsp -e 'writing mail' \
>            "to ${BASE}.mail failed"  # write mail to .mail file
>   dsp 'mail queued'
> }
> 
> # run (flush) queue
> run_queue() {                        # run queue
>   local LOC="$Q/.lock" MLF MSF       # lock file name ; queued mail filename pairs
>   local -i MAX=120 SEC=0 RC          # max seconds to gain a lock ; seconds waiting
> 
>   if (( $(/bin/ls -1 *.mail 2> /dev/null | /usr/bin/wc -l) > 0 )) ; then # if any mail in Q
>                                                      # attempt to lock queue
>     while [ -e "$LOC" ] && (( SEC < MAX )) ; do      # if a lock file there
> 	    /bin/sleep 1                                   # wait a second
> 	    (( ++SEC ))                                    # accumulate seconds
>     done                                             # try again while locked for MAX secs
>     if [ -e "$LOC" ] ; then                          # lock file still there, give up
> 	    dsp -e '' "cannot use $Q : waited $MAX seconds for" \
> 	              "  lockfile $LOC to vanish, giving up" \
> 	              'if you are sure that no other instance of this script' \
> 	              '  is running, then delete the lock file' ''
>     fi
> 
>     /bin/touch "$LOC" || exit 1      # lock queue
> 
>     for MLF in *.mail ; do           # process all mails
> 	    dsp "sending $MLF ..."         # scratch user's itch
> 	    MSF="${MLF%.*}.msmtp"
> 	    if [ ! -f "$MSF" ] ; then      # no corresponding MSF file found
> 		    dsp "corresponding file $MSF not found" \
> 		        'check this ...'         # give user the bad news
> 		    continue                     # crank on
> 	    fi
> 	    /usr/bin/msmtp $(/bin/cat "$MSF") < "$MLF"  # the mail goes out the door
>       RC=$?
> 	    if $RC ; then                  # send was successful
> 		    /bin/rm "$MLF" "$MSF"        # nuke the files
> 		    dsp "$MLF sent successfully" # good news to user
> 	    else                           # send unsuccessful
> 		    dsp "$MLF send failed" \
>             "msmtp rc = $RC" \
>             'check this out ...'     # bad news ...
> 	    fi
>     done
>   else                               # no mails in queue
>     dsp '' 'mail queue is empty' ''  # inform user
>   fi
> 
>   /bin/rm -f "$LOC"                  # remove the lock
> }
> 
> # display queue
> display_queue() {
>   local LST M                        # list of mails in queue ; each mail
> 
>   echo
>   LST="$(/bin/ls $Q/*.mail 2>/dev/null)"
>   (( CNT = 0 ))
>   if [ -n "$LST" ] ; then
>     for M in $LST ; do
>       echo "  mail id = [$(/usr/bin/basename $M .mail)]"       # show mail id
>       /bin/egrep -s --colour -h '(^From:|^To:|^Subject:)' "$M" # show mail info
>       (( CNT++ ))
>       echo
>     done
>   else
>     dsp 'no mail in queue' ''
>   fi
> }
> 
> # delete all mail in queue
> purge_all_mail() { # <-- question txt, positive ack txt
>   local YN I C                       # confirmation response ; question text ; ack text
> 
>   if [ "$1" == '-1' ] ; then         # queue contains single mail
>     I="remove the only mail from the queue"
>     C="single mail purged from queue"
>   else                               # queue contains multiple mails
>     I="remove (purge) all mail from the queue"
>     C="msmtp queue purged"
>     display_queue
>   fi
> 
>   echo -n "  $I [y/N] ? ...: " ; read YN
>   case $YN in
>     y|Y) /bin/rm -f "$Q"/*.*         # msmtp - nukes all mail in queue (dir)
>          dsp '' "$C ..." ''
>          ;;
>     *)   dsp '' 'nothing done ; queue is untouched ...' ''
>          ;;
>   esac
> }
> 
> purge_a_mail() {
>   local ID                           # id of mail being deleted
> 
>   while true ; do                    # purge an individual mail from queue
>     display_queue                    # show queue contents, if any
>     if (( CNT > 0 )) ; then          # something there
>       if (( CNT == 1 )) ; then       # only one mail
>         purge_all_mail -1            # purge it
>       else                           # more than one mail
>         echo '  remove (purge) a mail from the queue ; enter its id'
>         echo -n '    ( <cr> to exit ) ...: ' ; read ID
>         if [ -n "$ID" ] ; then       # <-- file name (only, no suff)
>           if [ -n "$(/bin/ls "$Q"/"$ID".* 2>/dev/null)" ] ; then
>             /bin/rm "$Q"/"$ID".*     # msmtp - nukes a single mail in queue (dir)
>             dsp '' "mail [$ID] purged ..."
>           else                       # invalid id entered
>             dsp '' "mail [$ID] not found ; bad id ..."
>           fi
>         else                         # nothing entered
>           dsp '' 'nothing done ; queue is untouched ...' ''
>           break
>         fi
>       fi
>     else
>       break
>     fi
>   done
> }
> 
> cleanup()
> {
>   #remove temporary message file
>   rm -f $TFILE
> }
> 
> #
> ## -- entry point
> #
> 
> trap cleanup 0 1 2 3 6 9 14 15
> #message from standart input
> #because /bin/cat does not repeat its output on subsequent runs :)
> TFILE="/tmp/$(basename $0).$$.tmp"
> /bin/cat > $TFILE
> 
> # check what we're being called as - 'msmtpq' or 'msmtpQ'
> if [ "$(/usr/bin/basename $0)" == 'msmtpQ' ] ; then  # called as msmtpQ
>   if [ -n "$1" ] ; then              # if arg(s)
>     check_queue                      # verify that queue directory is present ...
>     send_mail "$@"                   # send mail (or queue it if not connected)
>     exit 0
>   else
>     dsp -e 'msmtpQ requires input of a mail'
>   fi
> fi
> 
> if [ -z "$1" ] ; then
>   dsp 'msmtpq requires an instruction &/or a mail'
>   usage
>   exit 1
> fi
> 
> for A ; do                           # sort opts, args, etc.
>   case "$A" in
>     --) shift                        # end of msmtp opts - shift off '--'
>         break                        # anything further belongs to msmtp
>         ;;
>     -h) usage                        # show help
>         exit 0
>         ;;
>     -s|-sr|-e|-er|-r|-d|-m|-a)       # all other valid ops
>         [ -z "$OP" ] && OP=${A:1}    # op not already spec'd ; take all but first char (hyphen)
>         shift
>         ;;
>     *)  dsp '' "[$A] is an unknown option"
>         usage
>         exit 1
>         ;;
>   esac
> done
> 
> if [ -z "$OP" ] ; then               # check for an empty op
>   dsp -e 'must specify an operation for msmtpq'
> fi
> 
> check_queue                          # check that queue directory is present ...
> while [ -n "$OP" ] ; do              # while op contains a spec
>   case "$OP" in                      # sort ops ; run according to spec
>     s)  send_mail "@"     ;;         # send the mail
>     e)  enqueue_mail "$@" ;;         # enqueue the mail
>     r)  run_queue         ;;         # run (flush) the queue
>     d)  display_queue     ;;         # display (list) all mail in queue
>     m)  purge_a_mail      ;;         # purge an individual mail from queue
>     a)  purge_all_mail    ;;         # purge all mail in queue
>   esac
>   OP=${OP:1}                         # trim off first char of OP
> done
> 
> exit 0
> 
> 
> ----- End forwarded message -----
>