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 0e1e1e9cb2ff662780c3ab3a9131cf253844812e
parent 0857fed85fe514e1da491d3756b8e2df18d06a03
Author: Jaromil <jaromil@dyne.org>
Date:   Thu, 15 Jan 2015 13:59:47 +0100

filter delivery cleanup and optimizations, fixes to exitcodes and more code cleanups

Diffstat:
Msrc/jaro | 189++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
Msrc/zlibs/addressbook | 10----------
Msrc/zlibs/email | 2+-
Msrc/zlibs/filters | 93++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Msrc/zlibs/maildirs | 63+++++++++++++++++++++------------------------------------------
5 files changed, 207 insertions(+), 150 deletions(-)

diff --git a/src/jaro b/src/jaro @@ -34,6 +34,9 @@ DEBUG=0 DRYRUN=0 CALLMUTT=1 +# use gnome-keyring for passwords on GNU systems +GNOMEKEY=0 + # default permission on files umask 066 @@ -86,7 +89,7 @@ typeset -al maildirs typeset -h mutt pgpewrap dotlock # global variable for exit code -typeset exitcode +typeset -h exitcode # exitcode=0 # global variable for infos on imap folder @@ -255,10 +258,10 @@ KEYRING="$MAILDIRS/Keyring" # Cleanup anything sensitive before exiting. -_endgame() { +endgame() { func "endgame() $1" - [[ "$1" = "EXIT" ]] || { - error "QUIT signal sent to processes, waiting 2 seconds..." + [[ "$1" = "NOERRORS" ]] || { + error "$1 signal sent to processes, waiting to quit..." global_quit=1 sleep 2 } @@ -274,17 +277,18 @@ _endgame() { unlock "$MAILDIRS"/cache/notmuch } func "endgame() exit code: $exitcode" + return $exitcode } # Trap functions for the _endgame event -TRAPINT() { _endgame INT; return $exitcode } -# TRAPEXIT() { _endgame EXIT; return $exitcode } -TRAPHUP() { _endgame HUP; return $exitcode } -TRAPQUIT() { _endgame QUIT; return $exitcode } -TRAPABRT() { _endgame ABORT; return $exitcode } -TRAPKILL() { _endgame KILL; return $exitcode } -TRAPPIPE() { _endgame PIPE; return $exitcode } -TRAPTERM() { _endgame TERM; return $exitcode } -TRAPSTOP() { _endgame STOP; return $exitcode } +TRAPINT() { _endgame INT; return $? } +# TRAPEXIT() { _endgame EXIT; return $? } +TRAPHUP() { _endgame HUP; return $? } +TRAPQUIT() { _endgame QUIT; return $? } +TRAPABRT() { _endgame ABORT; return $? } +TRAPKILL() { _endgame KILL; return $? } +TRAPPIPE() { _endgame PIPE; return $? } +TRAPTERM() { _endgame TERM; return $? } +TRAPSTOP() { _endgame STOP; return $? } # TRAPZERR() { func "function returns non-zero." } case $OS in @@ -334,11 +338,6 @@ MUTTDIR="$MAILDIRS/.mutt" cp "$WORKDIR/Mutt.txt" "$MAILDIRS/Mutt.txt" notice "Default Mutt configuration template created" } -# use gnome-keyring for passwords on GNU systems -GNOMEKEY=0 -pidof gnome-keyring-daemon > /dev/null && { - act "using gnome-keyring to store secrets" - GNOMEKEY=1 } # binary programs recognition check_bin() { @@ -354,47 +353,53 @@ check_bin() { # make sure a gnupg dir exists { test -r $HOME/.gnupg/pubring.gpg } || { - ${=mkdir} $HOME/.gnupg - touch $HOME/.gnupg/pubring.gpg - touch $HOME/.gnupg/secring.gpg + ${=mkdir} $HOME/.gnupg + touch $HOME/.gnupg/pubring.gpg + touch $HOME/.gnupg/secring.gpg } # which find command to use case $OS in - GNU) find="find -O3" ;; - MAC) find="gfind -O3" ;; - *) find="find" + GNU) find="find -O3" ;; + MAC) find="gfind -O3" ;; + *) find="find" esac # which wipe command to use if command -v wipe > /dev/null; then - rm="wipe -f -s -q -R /dev/urandom" + rm="wipe -f -s -q -R /dev/urandom" elif command -v srm > /dev/null; then - rm="srm -m" + rm="srm -m" else - rm="rm -f" + rm="rm -f" fi func "Rm binary: $rm" # which mutt binary to use if command -v mutt > /dev/null; then - # system-wite - mutt="mutt" - # TODO: check if this is also the location on Fedora - pgpewrap="/usr/lib/mutt/pgpewrap" - dotlock="mutt_dotlock" + # system-wite + mutt="mutt" + # TODO: check if this is also the location on Fedora + pgpewrap="/usr/lib/mutt/pgpewrap" + dotlock="mutt_dotlock" elif command -v mutt-jaro > /dev/null; then - # in-house compiled - mutt=mutt-jaro - pgpewrap=pgpewrap - dotlock=dotlock + # in-house compiled + mutt=mutt-jaro + pgpewrap=pgpewrap + dotlock=dotlock else - error "Cannot find Mutt. Please install it." - exit 1 + error "Cannot find Mutt. Please install it." + exit 1 fi func "Mutt binary: $mutt" # make sure there is always a muttpass file even if empty touch $MAILDIR/tmp/muttpass + + + pidof gnome-keyring-daemon > /dev/null && { + act "using gnome-keyring to store secrets" + GNOMEKEY=1 } + return 0 } @@ -513,17 +518,16 @@ EOF typeset -A subcommands_opts -main() - { +main() { ### Options configuration #Hi, dear developer! Are you trying to add a new subcommand, or to add some options? #Well, keep in mind that: # 1. An option CAN'T have differente meanings/behaviour in different subcommands. # For example, "-s" means "size" and accept an argument. If you are tempted to add # an option "-s" (that means, for example "silent", and doesn't accept an argument) - # DON'T DO IT! - # There are two reasons for that: - # I. usability; user expect that "-s" is "size +# DON'T DO IT! + # There are two reasons for that: + # I. usability; user expect that "-s" is "size # II. Option parsing WILL EXPLODE if you do this kind of bad things # (it will say "option defined more than once, and he's right) main_opts=(a: -account=a l: -list=l q -quiet=q D -debug=D h -help=h v -version=v n -dry-run=n f -force=f) @@ -592,9 +596,9 @@ main() ### Detect subcommand local -aU every_opts #every_opts behave like a set; that is, an array with unique elements for optspec in $subcommands_opts$main_opts; do - for opt in ${=optspec}; do - every_opts+=${opt} - done + for opt in ${=optspec}; do + every_opts+=${opt} + done done local -a oldstar oldstar=($argv) @@ -602,9 +606,9 @@ main() unset discardme subcommand=$1 if [[ -z $subcommand ]]; then - subcommand="__default" + subcommand="__default" fi - + if [[ -z ${(k)subcommands_opts[$subcommand]} ]]; then # unknown command, pass it to autostart func "unknown command, autostart: $@" @@ -619,7 +623,7 @@ main() argv=(${oldstar}) unset oldstar - + ### Parsing global + command-specific options # zsh magic: ${=string} will split to multiple arguments when spaces occur set -A cmd_opts ${main_opts} ${=subcommands_opts[$subcommand]} @@ -685,8 +689,15 @@ main() PARAM=(${PARAM// -? /}) case "$subcommand" in - compose) compose ${PARAM} ;; - queue) queue ${PARAM} ;; + + compose) compose ${PARAM} + exitcode=$? + ;; + + queue) queue ${PARAM} + exitcode=$? + ;; + fetch) if [[ "$account" = "" ]]; then fetchall @@ -695,10 +706,19 @@ main() fi filter_maildir incoming ;; - send) send ${PARAM} ;; # was checking is_online - peek) peek ${PARAM} ;; # was checking is_online - later|remember) cat | deliver remember ;; + send) send ${PARAM} + exitcode=$? + ;; + + peek) peek ${PARAM} + exitcode=$? + ;; + + later|remember) + cat | deliver remember + exitcode=$? + ;; update|init) init_inbox @@ -709,21 +729,37 @@ main() help) usage ;; - index) nm_index ;; + index) nm_index + exitcode=$? + ;; + search) search ${PARAM} ;; + notmuch) notice "Command: notmuch ${PARAM}" nm ${PARAM} + exitcode=$? ;; stat) stats ${PARAM} ;; - complete) complete ${PARAM} ;; - isknown) sender_isknown ${PARAM} ;; - learn) learn ${PARAM} ;; - forget) forget ${PARAM} ;; - import) import ${PARAM} ;; + complete) complete ${PARAM} + exitcode=$? + ;; + + isknown) sender_isknown ${PARAM} + exitcode=$? + ;; + + learn) learn ${PARAM} + exitcode=$? + ;; + + import) import ${PARAM} + exitcode=$? + ;; + "export") case "$PARAM" in abook) @@ -736,22 +772,35 @@ main() esac ;; - abook) edit_abook ${PARAM} ;; + abook) edit_abook ${PARAM} + exitcode=$? + ;; edit|vim) edit_file ${PARAM} ;; open) open_folder ${PARAM} ;; preview) preview_file ${PARAM} ;; - backup) backup ${PARAM} ;; + backup) backup ${PARAM} + exitcode=$? + ;; + rmdupes) rmdupes ${PARAM} ;; merge) merge ${PARAM} ;; filter) update_filters ${PARAM} + [[ $? = 0 ]] || { + error "error updating filters, operation aborted." + break + } filter_maildir ${PARAM} + exitcode=$? ;; - deliver) deliver ${PARAM} ;; + deliver) + deliver ${PARAM} + exitcode=$? + ;; passwd) new_password ;; @@ -781,21 +830,19 @@ main() read_account $account ask_password bytes_total=`imap_get_size "$2"` + exitcode=$? notice "Size of account $login on $imap" act "$bytes_total bytes" mib_total=$(( $bytes_total / 1048576 )) act "$mib_total MB (MiB)" - exitcode=$? ;; listfolders) read_account $account ask_password folders=(`imap_list_folders`) - notice "List of folders for $login on $imap" - for f in $folders; do - print "$f" - done exitcode=$? + notice "List of folders for $login on $imap" + for f in $folders; do print "$f"; done ;; # interactive) # read_account @@ -821,7 +868,7 @@ main() smtp) smtp_send ${PARAM} - exitcode=$res + exitcode=$? ;; *) # unknown command, pass it to autostart @@ -834,10 +881,12 @@ main() } ;; esac + + return $exitcode } check_bin main $@ -_endgame EXIT +endgame NOERRORS return $exitcode diff --git a/src/zlibs/addressbook b/src/zlibs/addressbook @@ -198,16 +198,6 @@ learn() { } -forget() { - error "forget() TODO in abook branch" - return 0 - - # func "forget sender from mail in stdin" - # act "Expecting mail from stdin pipe" - # head="`${WORKDIR}/bin/fetchaddr -x From -a`" - # # forget the email part of the parsed head - # remove_address "${head[(ws:,:)1]}" -} # extract all addresses found in a list of email files from stdin extract_mails() { diff --git a/src/zlibs/email b/src/zlibs/email @@ -138,8 +138,8 @@ fetch() { } # updates the notmuch configuration - # setup the unread default tag nm_setup unread + # setup :unread: as default tag notice "Fetching email for account ${account}" diff --git a/src/zlibs/filters b/src/zlibs/filters @@ -190,7 +190,8 @@ filter_maildir() { ownfilters=1 } - [[ "$1" = "" ]] && { mdinput=incoming } + # default maildir to filter is incoming + mdinput=${1:-incoming} maildircheck "$MAILDIRS/$mdinput" [[ $? = 0 ]] || { @@ -220,6 +221,19 @@ filter_maildir() { match=0 c=$(($c + 1)) + # check if its an empty file + _fsize=`stat -c '%s' "$m"` + [[ $_fsize = 0 ]] && { + act "$c\t/ $numm\t(empty)" + rm "$m" + continue + } + + # parse if its a mailinglist + _md=0 + hdr "$m" | ismailinglist + [[ $? = 0 ]] && _md=1 + list="blacklist" hdr "$m" | sender_isknown [[ $? = 0 ]] && { @@ -248,28 +262,42 @@ filter_maildir() { [[ "$ownfilters" = "1" ]] && { func "processing through own filters" - ffrom=`hdr "$m" | ${WORKDIR}/bin/fetchaddr -x From -a` # run all filter regexps on the from: field - [[ "$ffrom" = "" ]] || { - femail="${ffrom[(ws:,:)1]:l}" + _dest="" + e_addr=() + hdr "$m" | e_parse From + [[ $? = 0 ]] && { + femail="${(k)e_addr}" # e_parse From hit is always one for exp in ${(k)filter_from}; do - + + # fuzzy match on a string (PCRE) if [[ "$femail" =~ "$exp" ]]; then + + # retrieve the filter destination maildir + _dest="${filter_from[$exp]}" + # if destination maildir is same as input, skip - [[ "${filter_from[$exp]}" = "$mdinput" ]] && { + [[ "$_dest" = "$mdinput" ]] && { act "$c\t/ $numm" match=1 break } - cat "$m" | deliver ${filter_from[$exp]} + + # tag mailinglists + if [[ $_md = 1 ]]; then + cat "$m" | deliver "$_dest" "+filtered +mailinglist" + else + cat "$m" | deliver "$_dest" "+filtered" + fi + if [[ $? = 0 ]]; then - act "$c\t/ $numm\t-> ${filter_from[$exp]}" + act "$c\t/ $numm\t-> $_dest\t(from $femail)" match=1 rm "$m" break else - error "Error filtering to maildir ${filter_from[$exp]}" + error "Error filtering to maildir $_dest" error "File: $m" continue fi @@ -281,31 +309,43 @@ filter_maildir() { } } - typeset -alU ftos + _dest="" # recompile the array of destination addresses - ftos=(`hdr "$m" | ${WORKDIR}/bin/fetchaddr -x cc -e`) - ftos+=(`hdr "$m" | ${WORKDIR}/bin/fetchaddr -x to -e`) - + e_addr=() + hdr "$m" | e_parse To + hdr "$m" | e_parse Cc # run all filter regexps on the to: and cc: fields - { test "$ftos" = "" } || { - for ft in ${(f)ftos}; do + [[ $? = 0 ]] && { + for ft in ${(k)e_addr}; do for exp in ${(k)filter_to}; do - # special zsh parsing in PCRE (=~) + + # fuzzy match on a string (PCRE) if [[ "$ft" =~ "$exp" ]]; then + + # retrieve the filter destination maildir + _dest="${filter_to[$exp]}" + # if destination maildir is same as input, skip - [[ "${filter_to[$exp]}" = "$mdinput" ]] && { + [[ "$_dest" = "$mdinput" ]] && { act "$c\t/ $numm" match=1 break } - cat "$m" | deliver ${filter_to[$exp]} + + # tag mailinglists + if [[ $_md = 1 ]]; then + cat "$m" | deliver "$_dest" "+filtered +mailinglist" + else + cat "$m" | deliver "$_dest" "+filtered" + fi + if [[ $? = 0 ]]; then - act "$c\t/ $numm\t-> ${filter_to[$exp]}" + act "$c\t/ $numm\t-> $_dest\t(to $ft)" match=1 rm "$m" break else - error "Error filtering to maildir ${filter_to[$exp]}" + error "Error filtering to maildir $_dest" error "File: $m" continue fi @@ -329,7 +369,7 @@ filter_maildir() { act "$c\t/ $numm" continue } - cat "$m" | deliver known + cat "$m" | deliver known "+inbox +priv" [[ $? = 0 ]] && { rm "$m" } act "$c\t/ $numm\t-> known" continue @@ -357,7 +397,7 @@ filter_maildir() { match=1 break } - cat "$m" | deliver priv + cat "$m" | deliver priv "+priv" [[ $? = 0 ]] && { rm "$m"; act "$c\t/ $numm\t-> priv" @@ -368,14 +408,13 @@ filter_maildir() { done [[ $match = 1 ]] && continue - # parse if its an unknown mailinglist - hdr "$m" | ismailinglist - [[ $? = 0 ]] && { + # its an unkown mailinglist + [[ $_ml = 1 ]] && { [[ "$mdinput" = "unsorted.ml" ]] && { act "$c\t/ $numm" continue } - cat "$m" | deliver unsorted.ml + cat "$m" | deliver unsorted.ml "+unsorted +mailinglist" [[ $? = 0 ]] && { rm "$m" } continue } @@ -385,7 +424,7 @@ filter_maildir() { act "$c\t/ $numm" else act "$c\t/ $numm\t-> unsorted" - cat "$m" | deliver unsorted + cat "$m" | deliver unsorted "+unsorted" [[ $? = 0 ]] && { rm "$m" } fi diff --git a/src/zlibs/maildirs b/src/zlibs/maildirs @@ -196,11 +196,9 @@ merge() { # important to return 1 on all errors # so that fetchmail does not deletes mail from server deliver() { - if [ "$1" = "" ]; then - dest="incoming" - else - dest="$1" - fi + + # default destination is incoming + dest=${1:-incoming} # create destination maildir if not existing [[ -r "$MAILDIRS/$dest" ]] || { @@ -217,52 +215,33 @@ deliver() { func "deliver to $dest" - # destinations excluded from notmuch indexing - [[ "$dest" = "outbox" ]] \ - || [[ "$dest" =~ "^zz." ]] \ - || [[ "$dest" = "incoming" ]] && { - - base="`hostname`_jaro_`date +%Y-%m-%d_%H-%M-%S`_$RANDOM" - + [[ "$2" = "" ]] && { + # no tag specified, plain delivery without indexing + ## destinations excluded from notmuch indexing + # for indexing rules see filter_maildir() + # [[ "$dest" = "outbox" ]] \ + # || [[ "$dest" =~ "^zz." ]] \ + # || [[ "$dest" = "incoming" ]] && { + base="`hostname`_jaro_`date +%Y-%m-%d_%H-%M-%S`_$RANDOM" cat > "$MAILDIRS/$dest/new/$base" [[ $? = 0 ]] || { - error "Could not write email file into maildir $dest." + error "Could not deliver email file into maildir $dest" func "Returning error to caller." return 1 } return 0 } - + ######### # notmuch indexing from here + tags="$2" + func "indexing tags: $tags" - # tag +inbox - [[ "$dest" = "known" ]] \ - || [[ "$dest" = "sent" ]] && { - cat | nm insert --folder="$dest" +inbox +priv - return $? - } - - [[ "$dest" = "priv" ]] && { - cat | nm insert --folder="$dest" +priv - return $? - } + cat | nm insert --folder="$dest" ${=tags} + res=$? + [[ $res = 0 ]] || \ + error "Could not deliver email file into maildir $dest with tags $tags" + + return $res - # tag +unsorted - [[ "$dest" = "unsorted" ]] \ - || [[ "$dest" =~ "^lists." ]] && { - cat | nm insert --folder="$dest" +unsorted - return $? - } - - _email=`cat` - print - "$_email" | ismailinglist - [[ $? = 0 ]] && { - print - "$_email" | nm insert --folder="$dest" +filters +mailinglist - return $? - } - - # anything else +filters - print - "$_email" | nm insert --folder="$dest" +filters - return $? }