jaromail

a commandline tool to easily and privately handle your e-mail
git clone git://parazyd.org/jaromail.git
Log | Files | Refs | Submodules | README

commit 542c25a9ae5aa03a8e25d5cb9d2872a9ae0c9478
parent 255f6ed7c0bb74a939aa78ccbc072efc50769e8d
Author: Jaromil <jaromil@dyne.org>
Date:   Wed, 28 Sep 2011 23:22:17 +0200

new queue and sendmail script imported from msmtp examples
also removed port from config files
and fixed postino peek

Diffstat:
Mdoc/postino-whitepaper.org | 23+++++++++++++----------
Msrc/postino | 93+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
Asrc/postino-queue | 412+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/postino-sendmail | 2++
4 files changed, 499 insertions(+), 31 deletions(-)

diff --git a/doc/postino-whitepaper.org b/doc/postino-whitepaper.org @@ -64,11 +64,6 @@ targeted for GNU/Linux/BSD desktop usage. *** Configure to filter mail -#+BEGIN_EXAMPLE -unset POSTRULE -POSTRULE+="from: list@kernel.org save: ml.kernel %%" -POSTRULE+="to: jaromil@nimk.nl save: job.nimk %%" -#+END_EXAMPLE @@ -92,15 +87,23 @@ REMOTE_FILTER=/var/mail/... **** send.conf #+BEGIN_EXAMPLE -# name host port login -gmail smtp.gmail.com 25 jaromil.rojo@gmail.com -dyne assata.dyne.org 25 username@dyne.im +# name host login +gmail smtp.gmail.com jaromil.rojo@gmail.com +dyne assata.dyne.org username@dyne.im #+END_EXAMPLE **** receive.conf + +#+BEGIN_EXAMPLE +# name host login +gmail imap.gmail.com jaromil.rojo@gmail.com +#+END_EXAMPLE + +**** filters.conf + #+BEGIN_EXAMPLE -# name host port login -gmail imap.gmail.com 443 jaromil.rojo@gmail.com +from list@kernel.org save ml.kernel +from dynebolic.lists.dyne save ml.dynebolic #+END_EXAMPLE diff --git a/src/postino b/src/postino @@ -29,11 +29,11 @@ for arg in ${argv}; do OLDARGS+=($arg); done #declare global variables QUIET=0 DEBUG=0 +PARAM=() typeset -A global_opts typeset -A opts -# parse configuration -source $HOME/.postinorc +# make sure we have a temp dir mkdir -p $WORKDIR/tmp autoload colors; colors @@ -53,6 +53,21 @@ act() { fi } + +# parse configuration +if [ -r $HOME/.postinorc ]; then source $HOME/.postinorc +else WORKDIR=$HOME/.postino; fi # default + + +if [ -r $WORKDIR/main.conf ]; then + source $WORKDIR/main.conf +else # first run (main conf not found) + notice "This is the first time you run postino on this machine," + act "we will now call a wizard that will assist you through the configuration" + error "WIZARD TODO" + return 0 +fi + # we use pinentry # comes from gpg project and is secure # it also conveniently uses the right toolkit @@ -98,18 +113,53 @@ option_value() { <<< ${opts[$1]} } +queue() { + local base; + local tmp; + local mailfile; + local msmtpfile; + + # add mails to the sendout queue + mkdir -p $MAILDIRS/queue + umask 077 + cd $MAILDIRS/queue || return 1 + # Create new unique filenames of the form + # MAILFILE: ccyy-mm-dd-hh.mm.ss[-x].mail + # MSMTPFILE: ccyy-mm-dd-hh.mm.ss[-x].msmtp + # where x is a consecutive number only appended if you send more than one + # mail per second. + base="`date +%Y-%m-%d-%H.%M.%S`" + echo "[$base] queue called with params: ${PARAM[@]}" > $WORKDIR/queue.log + + if [ -f "$base.mail" -o -f "$base.msmtp" ]; then + tmp="$base" + i=1 + while [ -f "$tmp-$i.mail" -o -f "$tmp-$i.msmtp" ]; do + i=`expr $i + 1` + done + base="$base-$i" + fi + mailfile="$base.mail" + msmtpfile="$base.msmtp" + # Write command line to $MSMTPFILE + echo "$@" > "$msmtpfile" || return 1 + # Write the mail to $MAILFILE + cat > "$mailfile" || return 1 + return 0 +} + + ###### # SMTP send() { # this function should send all mails in queue local -aU smtp_set #behave like a set; that is, an array with unique elements - smtp_set=`awk '!/^#/ { print $1 ";" $2 ";" $3 ";" $4 }' $WORKDIR/send.conf` + smtp_set=`awk '!/^#/ { print $1 ";" $2 ";" $3 }' $WORKDIR/send.conf` for s in ${(f)smtp_set}; do sname="${s[(ws:;:)1]}" shost="${s[(ws:;:)2]}" - sport="${s[(ws:;:)3]}" - slogin="${s[(ws:;:)4]}" + slogin="${s[(ws:;:)3]}" func "SMTP: $sname $slogin $shost:$sport" done error "TODO" @@ -121,21 +171,18 @@ send() { peek() { # this function will open the MTA to the imap server without fetching mails locally local -aU imap_set #behave like a set; that is, an array with unique elements - imap_set=`awk '!/^#/ { print $1 ";" $2 ";" $3 ";" $4 }' $WORKDIR/receive.conf` + imap_set=`awk '!/^#/ { print $1 ";" $2 ";" $3 }' $WORKDIR/receive.conf` for i in ${(f)imap_set}; do - iname="${i[(ws:;:)1]}" - # look for selection, not default - if [ $1 ] && [ "$1" != "$iname" ]; then continue; fi ihost="${i[(ws:;:)2]}" - iport="${i[(ws:;:)3]}" - ilogin="${i[(ws:;:)4]}" + ilogin="${i[(ws:;:)3]}" + iname="${i[(ws:;:)1]}" func "IMAP: $iname $ilogin $ihost:$iport" - if ! [ $1 ]; then break; fi # take first as default + # if there is a selection, check if its the one + if [ $1 ] && [ "$1" == "$iname" ]; then break; + elif ! [ $1 ]; then break; fi # no selection: take first as default done # escape at sign in login - escilogin=`echo $ilogin | sed 's/@/\\@/'` - print "mutt -f imaps://${escilogin}@${ihost}:$iport" - mutt -f imaps://`echo $ilogin | sed 's/@/\\@/'`@${ihost}:$iport + mutt -f imaps://`echo $ilogin | sed 's/@/\\@/'`@${ihost} return 0 } @@ -154,8 +201,8 @@ sync() { ###### # MUTT mkdir -p $WORKDIR/mutt - touch $WORKDIR/mutt/rc - cat<<EOF >> $WORKDIR/mutt/rc + rm -f $WORKDIR/mutt/rc + cat<<EOF > $WORKDIR/mutt/rc # mutt config generated by postino unset use_domain set hostname = "dyne.org" @@ -166,6 +213,7 @@ set record = ~/$MAILDIRS/sent/ set postponed=~/$MAILDIRS/postponed/ set tmpdir = $WORKDIR/tmp set query_command = "postino query '%s'" +set sendmail = "postino queue" set header_cache=~/$WORKDIR/mutt/cache set maildir_header_cache_verify=no # mailboxes in order of priority @@ -227,10 +275,10 @@ EOF echo -n " +${destination} " >> $WORKDIR/mutt/mboxes done - uniq $WORKDIR/mutt/mboxes > $WORKDIR/tmp/mboxes - mv $WORKDIR/tmp/mboxes $WORKDIR/mutt/mboxes echo " \\" >> $WORKDIR/mutt/mboxes echo " +unsorted" >> $WORKDIR/mutt/mboxes + uniq $WORKDIR/mutt/mboxes > $WORKDIR/tmp/mboxes + mv $WORKDIR/tmp/mboxes $WORKDIR/mutt/mboxes cat <<EOF >> $WORKDIR/procmail/rc } @@ -287,11 +335,13 @@ main() # (it will say "option defined more than once, and he's right) main_opts=(q -quiet=q D -debug=D h -help=h v -version=v n -dry-run=n) subcommands_opts[__default]="" + subcommands_opts[queue]="" subcommands_opts[fetch]="k -keep=k" subcommands_opts[send]="" subcommands_opts[peek]="" subcommands_opts[sync]="" subcommands_opts[conf]="" + subcommands_opts[list]="" # subcommands_opts[mount]=${subcommands_opts[open]} # subcommands_opts[create]="s: -size=s -ignore-swap k: -key=k" ### Detect subcommand @@ -327,8 +377,8 @@ main() fi fi #build PARAM (array of arguments) and check if there are unrecognized options - ok=0 - PARAM=() + local ok=0 +# PARAM=() for arg in $*; do if [[ $arg == '--' || $arg == '-' ]]; then ok=1 @@ -354,6 +404,7 @@ main() if option_is_set -D; then func "Debug messages ON"; DEBUG=1; fi case "$subcommand" in + queue) queue ${PARAM} ;; fetch) ;; send) ;; peek) peek ;; diff --git a/src/postino-queue b/src/postino-queue @@ -0,0 +1,412 @@ +#!/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 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 both by an email client - +# in 'sendmail' mode +# for this purpose, it is evoked by the symlink 'msmtpQ' +# ( created as ln -s msmtpq msmtpQ ) +# it is also meant to be used to maintain the msmtp queue +# as 'msmtpq' + +# 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 +# ) + + +#-------------------------------------------------------------- +# 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 ; } + +if [ -r $HOME/.postinorc ]; then + . $HOME/.postinorc +elif [ -r $HOME/.postino/main.conf ]; then + . $HOME/.postino/main.conf +else + echo "Postino not in use on this machine, operation aborted." + return 1 +fi + + +## ====================================================================================== +## !!! please define or confirm the following three vars !!! +## !!! before using the msmtpq/msmtpQ routine !!! +## ====================================================================================== +## +## 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=$MAILDIRS/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=$WORKDIR/queue.log +## ====================================================================================== + +umask 077 # set secure permissions on created directories and files + +declare -i CNT # a count of mail(s) currently in the queue + +trap on_exit 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 <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 + + 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 "$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 + while [ -e "$LOK" ] && (( SEC < MAX )) ; do # lock file present + sleep 1 # wait a second + (( ++SEC )) # accumulate seconds + done # try again while locked for MAX secs + [ -e "$LOK" ] && \ + err '' "cannot use queue $Q : waited $MAX seconds for"\ + " lockfile [ $LOK ] to vanish ; giving up"\ + 'if you are certain that no other instance of this script'\ + ' is running, then delete the lock file manually' '' # lock file still there, give up + + touch "$LOK" && \ + LKD='t' || \ + log -e "$?" "couldn't create queue lock file [ $LOK ]" # lock queue + + elif [ "$1" = '-u' ] ; then # unlock queue + 'rm' -f "$LOK" # remove the lock + fi +} + +# +## ----------------------------------- functions for queue management +## ----------------------------------- queue maintenance mode - (msmtpq) +# + +## show queue maintenance functions +usage() { # <-- error msg + dsp ''\ + 'usage : msmtpq functions' ''\ + ' msmtpq <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='Yn' # default to yes + + [ "$1" = '-n' ] && \ + { YN='yN' ; 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" = 'Yn' ] && return 0 || return 1 ;; + *) echo 'yYnN<cr> only please' ;; + esac + done +} + +## is anything in the queue ? +## sets CNT global var or +## exits w/passed msg +cnt_queue() { # <-- op label { 'purge' | 'send' } + CNT=$('ls' -1 ${Q}/*.mail 2>/dev/null | 'wc' -l) # take num mails in queue + + if (( CNT == 0 )) ; then # no mail in Q + dsp '' "mail queue is empty (nothing to $1)" '' # inform user + exit 0 # depart + fi +} + +## 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 + # verify net connection - ping ip address of debian.org + if ping -qnc1 -w2 debian.org > /dev/null 2>&1 ; then # connected + if $MSMTP $(< "${FQP}.msmtp") < "${FQP}.mail" ; then # this mail goes out the door + 'rm' -f ${FQP}.* # nuke both queue mail files + log "mail [ $1 ] from queue ; send was successful ; purged from queue" # good news to user + ALT='t' # set queue changed flag + else # send was unsuccessful + RC=$? # take msmtp exit code + log "mail [ $1 ] from queue ; send failed ; msmtp rc = $RC" # bad news ... + fi + return $RC # func returns exit code + else # not connected + dsp "mail [ $1 ] from queue ; couldn't be sent - host not connected" + return 1 + fi + else # corresponding MSF file not found + log "preparing to send .mail file [ ${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() { # run queue + local M LST="$('ls' $Q/*.mail 2>/dev/null)" # list of mails in queue + + if [ -n "$LST" ] ; then # something in queue + for M in $LST ; do # process all mails + send_queued_mail "$(basename $M .mail)" # send mail - pass {id} only + done + else # queue is empty + dsp '' 'mail queue is empty (nothing to send)' '' # inform user + fi +} + +## display queue contents +display_queue() { + local M LST="$('ls' $Q/*.mail 2>/dev/null)" # list of mails in queue + + if [ -n "$LST" ] ; then # list has contents (any mails in queue) + for M in $LST ; do # cycle through each + dsp '' "mail id = [ $(basename $M .mail) ]" # show mail id + 'egrep' -s --colour -h '(^From:|^To:|^Subject:)' "$M" # show mail info + done + echo + else # no mails ; no contents + dsp '' 'no mail in queue' '' # inform user + fi +} + +## delete all mail in queue, after confirmation +purge_queue() { + local YN # confirmation response + + cnt_queue 'purge' + display_queue # show queue contents + if ok -n 'remove (purge) all mail from the queue' ; then + 'rm' -f "$Q"/*.* + log 'msmtp queue purged (all mail)' + else + dsp '' 'nothing done ; queue is untouched' '' + fi +} + +## select a single mail from queue ; delete it or send it +select_mail() { # <-- '-purge' or '-send' + local ONE ID # mail id + + while true ; do # purge an individual mail from queue + cnt_queue "${1:1}" # count queue entries + display_queue # show queue contents + + if (( CNT == 1 )) ; then # only one mail in queue ; take its id + ID="$(basename $('ls' $Q/*.mail 2>/dev/null) .mail)" + ONE='t' # mark single mail + else # more than one mail ; select its id + while true ; do # get mail id + dsp '' "enter mail id to ${1:1}" # <-- file name (only, no suff) + echo -n ' ( <cr> alone to exit ) ..: ' ; read ID + [ -n "$ID" ] || return # entry made - or say good bye + 'ls' "$Q"/"$ID".* >/dev/null 2>&1 && \ + break || \ + dsp '' "mail [ $ID ] not found ; bad id" # id valid or complain & ask again + done + fi + + if ok "${1:1} mail - id = [ $ID ]" ; then # confirm mail op + if [ "$1" = '-purge' ] ; then # purging + 'rm' -f "$Q"/"$ID".* # msmtp - nukes single mail (both files) in queue + log "mail [ $ID ] purged from queue" # log op + ALT='t' # mark that a queue alteration has taken place + else # sending + send_queued_mail "$ID" # send out the mail + fi + else # user opts out + dsp '' 'nothing done to this queued email' # soothe user + [ -n "$ONE" ] && 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 + FQP="${Q}/$ID" # make fully qualified pathname + if [ -f "${FQP}.*" ] ; then # ensure fqp name is unique + INC=1 # initial increment + while [ -f "${FQP}-${INC}.*" ] ; 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 + # verify net connection - ping ip address of debian.org + if ping -qnc1 -w4 debian.org > /dev/null 2>&1 ; then # we're online, connected + if $MSMTP "$@" < "${FQP}.mail" > /dev/null ; then # send mail using queue .mail fil + log "mail for [ $* ] : send was successful" # log it + 'rm' -f "${FQP}.mail" # remove queue .mail file + run_queue # run/flush any other mails in queue + else # send failed + log "mail for [ $* ] : send was unsuccessful ; msmtp exit code was $?" + enqueue_mail "$@" # enqueue the mail + fi + else # not connected to net ; log msg + log "mail for [ $* ] : couldn't be sent - host not connected" + enqueue_mail "$@" # enqueue the mail + fi +} + +# +## -- entry point +# + +case "$(basename $0)" in # get name script called as + postino-sendmail) # called as 'msmtpQ' - sendmail mode + lock_queue # lock here + make_id # make base queue filename id for this mail + cat > "${FQP}.mail" || \ + log -e "$?" "creating mail body file [ ${FQP}.mail ] : failed" # test for error + send_mail "$@" # send the mail if possible, queue it if not + ;; + postino-queue) # called as 'msmtpq' - queue management mode + OP=${1:1} # trim off first char of OP + case "$OP" in # sort ops ; run according to spec + r) lock_queue ; run_queue ;; # run (flush) the queue + R) lock_queue ; select_mail -send ;; # send individual mail(s) in queue + d|'') display_queue ;; # display (list) all mail in queue (defaykt) + p) lock_queue ; select_mail -purge ;; # purge individual mail(s) from queue + a) lock_queue ; purge_queue ;; # purge all mail in queue + h) usage ;; # show help + *) usage "[ $OP ] is an unknown msmtpq option" ;; + esac + ;; + *) # invalid name + err "msmtpq can only be called as 'msmtpq' or 'msmtpQ'"\ + "{ $(basename $0) } doesn't work" + ;; +esac + +exit 0 diff --git a/src/postino-sendmail b/src/postino-sendmail @@ -0,0 +1 @@ +postino-queue+ \ No newline at end of file