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 22cb9f38bbd02be39310c63ec847b5a1ece9da7e
parent 82ede1c06ce4d3c0666832de2b1f48cab58fbc90
Author: Jaromil <jaromil@dyne.org>
Date:   Fri,  2 May 2014 14:39:30 +0200

fixes to keyring handling and configuration checks for gnu systems

Diffstat:
Msrc/jaro | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Msrc/zlibs/accounts | 227+------------------------------------------------------------------------------
Msrc/zlibs/addressbook | 10+++++-----
Msrc/zlibs/filters | 6+++++-
Asrc/zlibs/keyring | 250+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 319 insertions(+), 261 deletions(-)

diff --git a/src/jaro b/src/jaro @@ -180,6 +180,10 @@ else exit 1 fi +ACCOUNTS="$MAILDIRS/Accounts" +KEYRING="$MAILDIRS/Keyring" +addressbook="$MAILDIRS/Addressbook" + # temporary directory TMPDIR="$MAILDIRS/tmp/jaromil.$USER" case $OS in @@ -190,6 +194,20 @@ case $OS in TMPDIR=/dev/shm/tmp.jaromail.$USER TMPRAM=1 } + + # backward compatibility tests for old paths in JaroMail <1.3 + { test -d $WORKDIR/Accounts } && { test ! -d $ACCOUNTS } && { + act "Updating accounts location: $ACCOUNTS" + cp -ra $WORKDIR/Accounts $ACCOUNTS } + + { test -r "$WORKDIR/keyring" } && { test ! -r "$KEYRING" } && { + act "Updating keyring location: $KEYRING" + cp $WORKDIR/keyring "$KEYRING" } + + { test -r $WORKDIR/addressbook } && { test ! -r $addressbook } && { + act "Updating addressbook location: $addressbook" + cp $WORKDIR/addressbook $addressbook } + ;; MAC) mount | grep 'JaroTmp' > /dev/null @@ -201,38 +219,25 @@ case $OS in ;; esac +# use the TMP in RAM if possible, for acceleration { test $TMPRAM = 1 } && { act "Using temporary directory in volatile RAM" } - # make sure we have a temp dir ${=mkdir} "$TMPDIR" { test $? != 0 } && { error "Cannot create temporary directory: $TMPDIR" return 1 } -# make sure we have an addressbook -# use the one in RAM if present, for acceleration hostname=$(hostname) # gather the current hostname +# make sure we have a directory for account configurations +{ test -d $ACCOUNTS } || { mkdir -p $ACCOUNTS } -ACCOUNTS="$MAILDIRS/Accounts" -KEYRING="$MAILDIRS/Keyring" -addressbook="$MAILDIRS/Addressbook" - -# backward compatibility tests for old paths in JaroMail <1.3 -{ test -r $WORKDIR/Accounts } && { test ! -r $ACCOUNTS } && { - act "Updating accounts location: $ACCOUNTS" - cp -r $WORKDIR/Accounts $ACCOUNTS } - -{ test -r $WORKDIR/keyring } && { test ! -r $KEYRING } && { - act "Updating keyring location: $KEYRING" - cp $WORKDIR/keyring $KEYRING } - -{ test -r $WORKDIR/addressbook } && { test ! -r $addressbook } && { - act "Updating addressbook location: $addressbook" - cp $WORKDIR/addressbook $addressbook } +# make sure we have a local keyring in case system-wide not found +{ test -r "$KEYRING" } || { create_keyring "$KEYRING" } +# make sure we have an addressbook addressbook_tmp=$TMPDIR/${USER}.${hostname}.addressbook { test -r "$addressbook" } || { create_addressbook } { test -r "$addressbook_tmp" } && { addressbook="$addressbook_tmp" } @@ -244,6 +249,30 @@ PROCMAILDIR=$MAILDIRS/.procmail MUTTDIR=$MAILDIRS/.mutt +# make sure we have Filters.txt Applications.txt Mutt.txt +if ! [ -r $MAILDIRS/Filters.txt ]; then + cp -v doc/Filters.txt $MAILDIRS/Filters.txt + act "Default filters created" +else + error "Existing configuration $MAILDIRS/Filters.txt skipped" +fi + +if ! [ -r $MAILDIRS/Applications.txt ]; then + cp -v doc/Applications.txt $MAILDIRS/Applications.txt + act "Default helper applications settings created" +else + error "Existing configuration $MAILDIRS/Applications.txt skipped" +fi + + +if ! [ -r $MAILDIRS/Mutt.txt ]; then + cp -v doc/Mutt.txt $MAILDIRS/Mutt.txt + act "Default Mutt configuration template created" +else + error "Existing configuration $MAILDIRS/Mutt.txt skipped" +fi + + # use gnome-keyring for passwords on GNU systems GNOMEKEY=0 { test $GNOME_KEYRING_CONTROL } && { @@ -326,10 +355,10 @@ check_bin() { done # make sure a gnupg dir exists - { test -r $HOME/.gnupg/pubring.gpg } || { - mkdir -p $HOME/.gnupg - touch $HOME/.gnupg/pubring.gpg - touch $HOME/.gnupg/secring.gpg + { test -r $HOME/.gnupg/pubring.gpg } || { + mkdir -p $HOME/.gnupg + touch $HOME/.gnupg/pubring.gpg + touch $HOME/.gnupg/secring.gpg } # which find command to use @@ -338,7 +367,7 @@ check_bin() { 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" @@ -442,15 +471,15 @@ a pipe | in front indicate they take an email body from stdin account names correspond to the filenames, i.e. imap.default fetch downloads emails locally from an $account on-line - option "keep" (default) to avoid deleting from servers + option "keep" (default) to avoid deleting from servers send send all emails queued in outbox/ to an smtp.$account peek connect to an imap.$account with ncurses terminal mutt - remote folders supported. use to delete without download + remote folders supported. use to delete without download passwd set account passwords in the OS native keyring - or into a simple, file based, gpg encrypted database. + or into a simple, file based, gpg encrypted database. |queue queue a mail in outbox/ for send @@ -462,7 +491,7 @@ maildirs are directories of mails downloaded in Mail/ open open a maildir folder (can use -R for read-only) backup (mairix) move mails from a maildir to another one - match mails to move based on date ranges or strings + match mails to move based on date ranges or strings rmdupes remove all duplicated e-mails into a maildir @@ -572,7 +601,7 @@ main() act "try -h for help" CLEANEXIT=0 } - return $exitcode + return $exitcode fi argv=(${oldstar}) diff --git a/src/zlibs/accounts b/src/zlibs/accounts @@ -4,7 +4,7 @@ # # a tool to easily and privately handle your e-mail communication # -# Copyleft (C) 2010-2012 Denis Roio <jaromil@dyne.org> +# Copyleft (C) 2010-2014 Denis Roio <jaromil@dyne.org> # # This source code is free software; you can redistribute it and/or # modify it under the terms of the GNU Public License as published by @@ -156,228 +156,3 @@ read_account() { return 0 } - -# we use pinentry -# comes from gpg project and is secure -# it also conveniently uses the right toolkit -pin_entry() { - cat <<EOF | pinentry 2>/dev/null | awk '/^D / { sub(/^D /, ""); print }' -OPTION ttyname=$TTY -OPTION lc-ctype=$LANG -SETTITLE Type your password -SETDESC Type the password for $1 @ $2 -SETPROMPT Password: -GETPIN -EOF -} - - -# retrieve a password for user @ domain -# put it in variable password -# up to the caller to unset it after use -ask_password() { - case $OS in - MAC) - func "Looking for password in Mac/OSX keyring for $email on $host over $type" - security find-internet-password \ - -c JARO -a $email -s $host \ - -p $transport -P $port > /dev/null - if [ $? != 0 ]; then # its a new password - new_password - { test $? != 0 } && { - error "Password input aborted." - return 1 } - else - password=`security find-internet-password -c JARO -a $email -s $host -p $transport -P $port -g 2>&1| awk '/^password:/ { print $2 }' | sed -e 's/"//g'` - fi - return 0 - ;; - ##################################### - GNU) - ################### - # USE GNOME KEYRING - if [ "$GNOMEKEY" = "1" ]; then - func "Looking for password in Gnome keyring for $email on $host over $type" - func "path: jaromail/${type}/${email}" - - print "protocol=${type}\npath=jaromail/${type}/${email}\nusername=${login}\nhost=${host}\n\n" \ - | $WORKDIR/bin/jaro-gnome-keyring check - if [ $? != 0 ]; then # its a new password - new_password - { test $? != 0 } && { - error "Password input aborted." - return 1 } - else # password found into gnome keyring - act "Using saved password for $1 @ $2" - password=`print "protocol=${type}\npath=jaromail/${type}/${email}\nusername=${login}\nhost=${host}\n\n" | $WORKDIR/bin/jaro-gnome-keyring get` - fi - return 0 - elif [ -r $KEYRING ]; then - func "Looking for password in local keyring for $type account $account on $host" - func "new pass hash for: $type:$login:$host" - _hash=`print "$type:$login:$host" | shasum | awk '{print $1}'` - lookup="`lookup_secret ${_hash}`" - { test "$lookup" = "" } || { - act "Saved password found for $email ($transport on $host)" - notice "Type the password to unlock this keyring entry:" - password="`print $lookup | base64 -d | gpg -d --cipher-algo AES256 --openpgp --no-options`" - { test "$?" = 0 } || { error "Incorrect password to unlock local keyring entry, operation aborted."; return 1 } - return 0 - } - fi - #################### - # USE PINENTRY ALONE - new_password - { test $? != 0 } && { - error "Password input aborted." - return 1 } - return 0 - ;; - *) - error "Unknown system, can't figure out how to handle passwords" - return 1 - esac -} - -lookup_secret() { - _hash=$1 - if [ "$2" = "" ]; then key=password - else key="$2"; fi - cat <<EOF | ${SQL} -column -batch $KEYRING -SELECT ${key} FROM secrets -WHERE hash IS "${_hash}"; -EOF -} - -new_password() { - notice "Setting a new password for $type account $account on $host" - act "please enter password for username '$login'" - password=`pin_entry $login $host` - res=0 - case $OS in - MAC) - if [ "$password" != "" ]; then - - security delete-internet-password \ - -c JARO -a $email -s $host \ - -p $transport -P $port > /dev/null - - security add-internet-password \ - -c JARO -a $email -s $host \ - -p $transport -P $port -w "${password}" - - if [ $? != 0 ]; then - error "Error adding password to keyring." - else - act "New password saved in keyring" - fi - return 0 - - else - error "No password given, operation aborted" - return 1 - - # we are not deleting passwords anymore - security delete-internet-password \ - -c JARO -a $email -s $host \ - -p $transport -P $port > /dev/null - res=$?; unset password - { test $res != 0 } && { - echo - error "Error deleting password from keyring." - return 1 } - act "No new password given, old password erased." - return 0 - ######### - - fi - ;; - GNU) - if [ "$password" != "" ]; then # password was written - - # USE GNOME KEYRING - if [ "$GNOMEKEY" = "1" ]; then - act "using gnome-keyring password storage" - func "path: jaromail/${type}/${email}" - cat <<EOF | $WORKDIR/bin/jaro-gnome-keyring store -protocol=${type} -path=jaromail/${type}/${email} -username=${login} -host=${host} -password=${password} -EOF - { test $? != 0 } && { error "Error saving password in Gnome keyring" } - - else # save it into local keyring - - { test -r $KEYRING } || { - # make sure the local keyring exists - touch $KEYRING - chmod 600 $KEYRING - chown $_uid:$_gid $KEYRING - cat <<EOF | ${SQL} -batch $KEYRING -CREATE TABLE secrets -( - hash text unique, - password text -); -EOF - } - # calculate the hash for this entry - _hash=`print "$type:$login:$host" | shasum | awk '{print $1}'` - # check if the entry is already present - func "new pass hash for: $type:$login:$host" - lookup="`lookup_secret ${_hash} rowid`" - notice "Select the password to lock this keyring entry:" - _password="`print $password | gpg -c --cipher-algo AES256 --openpgp --no-options | base64`" - if [ "$lookup" = "" ]; then # new entry - cat <<EOF | ${SQL} -batch $KEYRING -INSERT INTO secrets (hash, password) -VALUES ("${_hash}", "${_password}"); -EOF - act "saved new password in local keyring" - else # update entry - cat <<EOF | ${SQL} -batch $KEYRING -UPDATE secrets SET password="${_password}" WHERE hash LIKE "${_hash}"; -EOF - act "updated local keyring with new password" - fi - fi - - return 0 - - else # password is blank or aborted - - # save it into gnome keyring - if [ $GNOMEKEY = 1 ]; then - - cat <<EOF | $WORKDIR/bin/jaro-gnome-keyring erase -protocol=${type} -path=jaromail/${email} -username=${login} -host=${host} -EOF - { test $? != 0 } && { - error "Error accessing password in Gnome keyring" - return 1 } - act "No new password given, old password erased." - return 0 - fi - # TODO: delete from local keyring - - fi - ;; - *) - error "Unknown system, can't figure out how to handle passwords" - return 1 - esac -} - -change_password() { - - read_account ${=PARAM} - - { test $? = 0 } && { test $DRYRUN != 1 } && { - new_password } - -} diff --git a/src/zlibs/addressbook b/src/zlibs/addressbook @@ -27,11 +27,11 @@ ADDRESSBOOK=$MAILDIRS/Addressbook # Jaro Brother DB create_addressbook() { func "create addressbook" - { test -r "$ADDRESSBOOK" } && { - error "Addressbook already exists: $ADDRESSBOOK" + { test -r "$1" } && { + error "Addressbook already exists: $1" return 1 } - cat <<EOF | ${SQL} -batch $ADDRESSBOOK + cat <<EOF | ${SQL} -batch "$1" CREATE TABLE whitelist ( email text collate nocase unique, @@ -47,8 +47,8 @@ EOF error "Error creating addressbook database." return 1 } # make sure is private - chmod 600 $ADDRESSBOOK - chown $_uid:$_gid $ADDRESSBOOK + chmod 600 "$1" + chown $_uid:$_gid "$1" return 0 } diff --git a/src/zlibs/filters b/src/zlibs/filters @@ -126,6 +126,8 @@ text/html; lynx -dump -assume_charset=%{charset} %s; nametemplate=%s.html; copio EOF fi + { test -r ${MAILDIRS}/Applications.txt } && { + apptypes=`cat ${MAILDIRS}/Applications.txt` for t in ${(f)apptypes}; do eval `print $t | awk ' @@ -137,6 +139,8 @@ EOF cat <<EOF >> $MUTTDIR/mailcap application/*; a="${TMPDIR}" && f=\`basename %s\` && rm -f "\$a"/"\$f" && cp %s "\$a"/"\$f" && jaro preview "\$a"/"\$f" EOF + } # Applications.txt + # this one is empty and sources files in temp when necessary # touch $TMPDIR/muttpass @@ -197,7 +201,7 @@ EOF # continue later on while we parse filters - +touch $MAILDIRS/Filters.txt ########## # PROCMAIL diff --git a/src/zlibs/keyring b/src/zlibs/keyring @@ -0,0 +1,250 @@ +#!/usr/bin/env zsh +# +# Jaro Mail, your humble and faithful electronic postman +# +# a tool to easily and privately handle your e-mail communication +# +# Copyleft (C) 2014 Denis Roio <jaromil@dyne.org> +# +# This source code is free software; you can redistribute it and/or +# modify it under the terms of the GNU Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This source code is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# Please refer to the GNU Public License for more details. +# +# You should have received a copy of the GNU Public License along with +# this source code; if not, write to: +# Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +create_keyring() { + # make sure the local keyring exists + touch "$1" + chmod 600 "$1" + chown $_uid:$_gid "$1" + cat <<EOF | ${SQL} -batch "$1" +CREATE TABLE secrets +( + hash text unique, + password text +); +EOF +} + + +# we use pinentry +# comes from gpg project and is secure +# it also conveniently uses the right toolkit +pin_entry() { + cat <<EOF | pinentry 2>/dev/null | awk '/^D / { sub(/^D /, ""); print }' +OPTION ttyname=$TTY +OPTION lc-ctype=$LANG +SETTITLE Type your password +SETDESC Type the password for $1 @ $2 +SETPROMPT Password: +GETPIN +EOF +} + + +# retrieve a password for user @ domain +# put it in variable password +# up to the caller to unset it after use +ask_password() { + case $OS in + MAC) + func "Looking for password in Mac/OSX keyring for $email on $host over $type" + security find-internet-password \ + -c JARO -a $email -s $host \ + -p $transport -P $port > /dev/null + if [ $? != 0 ]; then # its a new password + new_password + { test $? != 0 } && { + error "Password input aborted." + return 1 } + else + password=`security find-internet-password -c JARO -a $email -s $host -p $transport -P $port -g 2>&1| awk '/^password:/ { print $2 }' | sed -e 's/"//g'` + fi + return 0 + ;; + ##################################### + GNU) + ################### + # USE GNOME KEYRING + if [ "$GNOMEKEY" = "1" ]; then + func "Looking for password in Gnome keyring for $email on $host over $type" + func "path: jaromail/${type}/${email}" + + print "protocol=${type}\npath=jaromail/${type}/${email}\nusername=${login}\nhost=${host}\n\n" \ + | $WORKDIR/bin/jaro-gnome-keyring check + if [ $? != 0 ]; then # its a new password + new_password + { test $? != 0 } && { + error "Password input aborted." + return 1 } + else # password found into gnome keyring + act "Using saved password for $1 @ $2" + password=`print "protocol=${type}\npath=jaromail/${type}/${email}\nusername=${login}\nhost=${host}\n\n" | $WORKDIR/bin/jaro-gnome-keyring get` + fi + return 0 + elif [ -r $KEYRING ]; then + func "Looking for password in local keyring for $type account $account on $host" + func "new pass hash for: $type:$login:$host" + _hash=`print "$type:$login:$host" | shasum | awk '{print $1}'` + lookup="`lookup_secret ${_hash}`" + { test "$lookup" = "" } || { + act "Saved password found for $email ($transport on $host)" + notice "Type the password to unlock this keyring entry:" + password="`print $lookup | base64 -d | gpg -d --cipher-algo AES256 --openpgp --no-options`" + { test "$?" = 0 } || { error "Incorrect password to unlock local keyring entry, operation aborted."; return 1 } + return 0 + } + fi + #################### + # USE PINENTRY ALONE + new_password + { test $? != 0 } && { + error "Password input aborted." + return 1 } + return 0 + ;; + *) + error "Unknown system, can't figure out how to handle passwords" + return 1 + esac +} + +lookup_secret() { + _hash=$1 + if [ "$2" = "" ]; then key=password + else key="$2"; fi + cat <<EOF | ${SQL} -column -batch $KEYRING +SELECT ${key} FROM secrets +WHERE hash IS "${_hash}"; +EOF +} + +new_password() { + notice "Setting a new password for $type account $account on $host" + act "please enter password for username '$login'" + password=`pin_entry $login $host` + res=0 + case $OS in + MAC) + if [ "$password" != "" ]; then + + security delete-internet-password \ + -c JARO -a $email -s $host \ + -p $transport -P $port > /dev/null + + security add-internet-password \ + -c JARO -a $email -s $host \ + -p $transport -P $port -w "${password}" + + if [ $? != 0 ]; then + error "Error adding password to keyring." + else + act "New password saved in keyring" + fi + return 0 + + else + error "No password given, operation aborted" + return 1 + + # we are not deleting passwords anymore + security delete-internet-password \ + -c JARO -a $email -s $host \ + -p $transport -P $port > /dev/null + res=$?; unset password + { test $res != 0 } && { + echo + error "Error deleting password from keyring." + return 1 } + act "No new password given, old password erased." + return 0 + ######### + + fi + ;; + GNU) + if [ "$password" != "" ]; then # password was written + + # USE GNOME KEYRING + if [ "$GNOMEKEY" = "1" ]; then + act "using gnome-keyring password storage" + func "path: jaromail/${type}/${email}" + cat <<EOF | $WORKDIR/bin/jaro-gnome-keyring store +protocol=${type} +path=jaromail/${type}/${email} +username=${login} +host=${host} +password=${password} +EOF + { test $? != 0 } && { error "Error saving password in Gnome keyring" } + + else # save it into local keyring + + { test -r "$KEYRING" } || { create_keyring "$KEYRING" } + + # calculate the hash for this entry + _hash=`print "$type:$login:$host" | shasum | awk '{print $1}'` + # check if the entry is already present + func "new pass hash for: $type:$login:$host" + lookup="`lookup_secret ${_hash} rowid`" + notice "Select the password to lock this keyring entry:" + _password="`print $password | gpg -c --cipher-algo AES256 --openpgp --no-options | base64`" + if [ "$lookup" = "" ]; then # new entry + cat <<EOF | ${SQL} -batch $KEYRING +INSERT INTO secrets (hash, password) +VALUES ("${_hash}", "${_password}"); +EOF + act "saved new password in local keyring" + else # update entry + cat <<EOF | ${SQL} -batch $KEYRING +UPDATE secrets SET password="${_password}" WHERE hash LIKE "${_hash}"; +EOF + act "updated local keyring with new password" + fi + fi + + return 0 + + else # password is blank or aborted + + # save it into gnome keyring + if [ $GNOMEKEY = 1 ]; then + + cat <<EOF | $WORKDIR/bin/jaro-gnome-keyring erase +protocol=${type} +path=jaromail/${email} +username=${login} +host=${host} +EOF + { test $? != 0 } && { + error "Error accessing password in Gnome keyring" + return 1 } + act "No new password given, old password erased." + return 0 + fi + # TODO: delete from local keyring + + fi + ;; + *) + error "Unknown system, can't figure out how to handle passwords" + return 1 + esac +} + +change_password() { + + read_account ${=PARAM} + + { test $? = 0 } && { test $DRYRUN != 1 } && { + new_password } + +}