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 e60deef1ce5f55e8a98897997c3eaa7fc8f9d545
parent 68fe3a19d19b4796073191703e062a1f3a228a63
Author: Jaromil <jaromil@dyne.org>
Date:   Thu,  8 May 2014 02:40:15 +0200

Simplified account configuration

Now there is only one file for each account, which can contain details for both imap and smtp.
Removed some cruft in the old mechanism and fixed the .txt~ backup turd bug.

Diffstat:
Mdoc/Accounts/README.txt | 11++++++-----
Ddoc/Accounts/imap.default.txt | 46----------------------------------------------
Ddoc/Accounts/smtp.default.txt | 18------------------
Msrc/jaro | 9+++++----
Msrc/zlibs/accounts | 160+++++++++++++++++++++++++++++++++----------------------------------------------
Msrc/zlibs/email | 70+++++++++++++++++++++++++++++++++++++++-------------------------------
Msrc/zlibs/keyring | 10+++++-----
7 files changed, 121 insertions(+), 203 deletions(-)

diff --git a/doc/Accounts/README.txt b/doc/Accounts/README.txt @@ -1,8 +1,9 @@ -Directory containing account information +Directory containing account information. -Each file contains a different account: imap, pop or gmail -each account contains all information needed to connect it +Accounts are your login information at email providers. -Examples are: imap.default.txt and smtp.default.txt +Each file in this directory configures a different account. -One can have multiple accounts named otherwise than default +Accounts can be selected using the 'jaro -a accountname' option. + +The account named default is the one used if none is specified. diff --git a/doc/Accounts/imap.default.txt b/doc/Accounts/imap.default.txt @@ -1,46 +0,0 @@ -# Name and values are separated by spaces or tabs -# comments start the line with a hash - -# Give a name to this account -name To Be Configured -# configure Identity.txt to set your From: field - -# Email address (default is same as login) -email unknown@gmail.com - -# Internet address -host imap.gmail.com - -# Username -login USERNAME@gmail.com - -# Authentication type -auth plain # or kerberos, etc - -# Identity certificate: check or ignore -cert ignore - -# Transport protocol -transport ssl - -# Service port -port 993 - -# Options when fetching -# to empty your mailbox you can use: fetchall flush -# by default this is 'keep': don't delete mails from server -options keep - -# Imap folders -# uncomment to provide a list of folders to be fetched -# folders INBOX, known, priv, lists, ml.unsorted, unsorted - -# Remote sieve -# command to upload a sieve filter to the server -# %% will be filled in automatically with our file -# remote_sieve_cmd scp %% assata.dyne.org:/var/mail/sieve-scripts/email - - -# -# The password field will be filled in automatically -# diff --git a/doc/Accounts/smtp.default.txt b/doc/Accounts/smtp.default.txt @@ -1,18 +0,0 @@ -# Name and values are separated by spaces or tabs -# comments start the line with a hash - -# Give a name to this account -name To Be Configured - -# Internet address -host smtp.gmail.com - -# Username -login USERNAME@gmail.com - -# Transport protocol -transport ssl # or "tls" or "plain" - -# Service port -# port 465 -port 25 diff --git a/src/jaro b/src/jaro @@ -60,7 +60,8 @@ typeset -h list list=whitelist # global variables for accounts -typeset -h name login host protocol port password auth folders accountopt +typeset -h name login imap imap_port smtp smtp_port protocol password auth folders accountopt +typeset -h host port type # global variables for addressbook typeset -h hostname addressbook addressbook_tmp @@ -311,10 +312,10 @@ cleanexit() { pidfile="${TMPDIR}/$lname.pid" if [ -r ${pidfile} ]; then pid=`cat $pidfile` - error "forced removal of lock left by pid $pid: $lname" + func "forced removal of lock left by pid $pid: $lname" rm -f ${pidfile} else - error "forced removal of lock left by unknown pid: $lname" + func "forced removal of lock left by unknown pid: $lname" fi rm -f ${TMPDIR}/${lname}.lock @@ -322,7 +323,7 @@ cleanexit() { if [ -r ${TMPDIR}/$lname ]; then func "deleting temp file: ${TMPDIR}/$lname" ${=rm} "${TMPDIR}/$lname" - else act "empty lock: file was already removed"; fi + else func "empty lock: file was already removed"; fi done diff --git a/src/zlibs/accounts b/src/zlibs/accounts @@ -21,122 +21,94 @@ # Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -list_accounts() { - accts=`${=find} "$ACCOUNTS" -type f | grep -v README | sed 's/.txt//'` - for a in ${(f)accts}; do - act -n "`basename $a`\t :: " - awk ' -/^name/ { for(i=2;i<=NF;i++) printf "%s ", $i } -/^email/ { printf "<%s>", $2 } -/^host/ { printf " on %s", $2 } -{next}' "$a.txt" - print " (`basename $a | cut -d. -f1`)" - done -} - -# arg 1 : account type, es: imap or smtp +# account = type.name es: imap.default or smtp.gmail # -a defines which account name other than 'default' # results in the definition of global account variables: # name login host protocol port auth folders accountopt read_account() { typeset -al all - unset name email host login transport \ - port auth cert options folders + unset name email imap imap_port smtp smtp_port \ + host login transport auth cert options folders # parse arguments - { test "$1" != "" } && { account="$1" } - - account=".${account}" - type=`print ${account} | cut -d. -f2` - account=`print ${account} | cut -d. -f3` - - { test "$2" != "" } && { account="$2" } - { test "$type" = "default" } && { - error "No account type specified" - act "Available accounts (excluding symbolic links, omit final .txt):" - list_accounts - return 1 } - { test "$account" = "" } && { account=default } + { test "$account" = "" } && { account="default" } # find the account - func "read_account looking for ${type} ${account}" - accountlist=`${=find} "$ACCOUNTS" -name "$type.$account*" | grep -v 'lock$' | grep -v 'pid$'` - for a in ${(f)accountlist}; do - # check if it is locked - func "found account: $a" - { test -r "${a}.lock" } && { - pidcheck "${a}" } - all+=("$a") - done - if [ ${#all} = 0 ]; then - error "No $type account found: $account" - act "Refine your argument using '-a type.account'" - act "For instance adding to the commandline: -a imap.default" - act "Available accounts (excluding symbolic links, omit final .txt):" - list_accounts - return 1 - elif [ ${#all} != 1 ]; then - error "Too many $type accounts named $account" - act -n "" - for i in ${=all}; do print -n "`basename ${i}` "; done - print; error "Refine your account keyword using -a option" - return 1 - fi - - acct="${all[1]}" - type=`basename "$acct" | cut -d. -f1` - - case $type in - imap|smtp) + func "read_account looking for $account" + acct="$MAILDIRS/Accounts/$account"; + { test -r "$acct" } || { + acct="$MAILDIRS/Accounts/$account.txt" + { test -r "$acct" } || { + error "no account found: $acct" + act "Refine your argument using '-a accountname'" + act "Available accounts:" + ls "$MAILDIRS/Accounts/" + return 1 + } + } - lock $acct - ttmp=`cat "$acct" | awk ' + ttmp=`awk ' /^#/ { next } /^name/ { printf "name=\""; for(i=2;i<=NF;i++) printf "%s ", $i; printf "\";" } /^email/ { printf "email=\"%s\";", $2 } - /^host/ { printf "host=\"%s\";", $2 } + /^imap / { printf "imap=\"%s\";", $2 } + /^smtp / { printf "smtp=\"%s\";", $2 } + /^host / { printf "host=\"%s\";", $2 } + /^port / { printf "port=\"%s\";", $2 } /^login/ { printf "login=\"%s\";", $2 } /^transport/ { printf "transport=\"%s\";", $2 } - /^port/ { printf "port=\"%s\";", $2 } + /^imap_port/ { printf "imap_port=\"%s\";", $2 } + /^smtp_port/ { printf "smtp_port=\"%s\";", $2 } /^auth/ { printf "auth=\"%s\";", $2 } /^cert/ { printf "cert=\"%s\";", $2 } /^options/ { printf "accountopt=\""; for(i=2;i<=NF;i++) printf "%s ", $i; printf "\";" } /^folders/ { printf "folders=\""; for(i=2;i<=NF;i++) printf "%s ", $i; printf "\";" } - '` - unlock $acct + ' "$acct"` + { test $? = 0 } || { + error "Error parsing account: $acct" + return 1 } - eval "$ttmp" - # check required fields - { test -z $host } && { error "Field missing in account $acct: host"; return 1 } + eval "$ttmp" + # check required fields - # fill in defaults - { test -z $name } && { name="$type" } - { test -z $login } && { login="$email" } # usually email and login are the same - { test -z $email } && { email="$login" } # so if one is specified, deduce the other - { test -z $transport } && { transport=plain } - { test -z $port } && { port=143 } - { test -z $auth } && { auth=plain } - { test -z $cert } && { cert=ignore } - { test -z $accountopt } && { accountopt=keep } - # cert and password can be missing + # falling back to old host/port conf directives + + { test -z $imap } && { imap=$host } + { test -z $smtp } && { smtp=$imap } + { test -z $imap_port } && { imap_port=$port } + { test -z $smtp_port } && { smtp_port=$imap_port } + + # fill in defaults + { test -z $name } && { name="$type" } + { test -z $login } && { login="$email" } # usually email and login are the same + { test -z $email } && { email="$login" } # so if one is specified, deduce the other + { test -z $transport } && { transport=plain } + { test -z $imap_port } && { imap_port=143 } + { test -z $smtp_port } && { smtp_port=25 } + + { test -z $auth } && { auth=plain } + { test -z $cert } && { cert=ignore } + { test -z $accountopt } && { accountopt=keep } + # cert and password can be missing + + func "name: $name" + func "email: $email" + func "login: $login" + + func "host: $host" + func "port: $port" + + func "imap: $imap" + func "imap port: $imap_port" + func "smtp: $smtp" + func "smtp port: $smtp_port" + + func "trans: $transport" + func "cert: $cert" + func "auth: $auth" + func "options: $accountopt" + func "folders: $folders" - func "type: $type" - func "name: $name" - func "email: $email" - func "host: $host" - func "login: $login" - func "trans: $transport" - func "port: $port" - func "cert: $cert" - func "auth: $auth" - func "options: $accountopt" - func "folders: $folders" - ;; - *) - error "Account type \"$type\" not recognized." - return 1 - ;; - esac return 0 } diff --git a/src/zlibs/email b/src/zlibs/email @@ -172,24 +172,24 @@ fetch() { fetchall; return $? } # setup global account variables - read_account ${account_type} ${account} + read_account ${account} # name login host protocol port auth folders accountopt { test $? != 0 } && { - error "Error on account entry: $account_type $account" + error "Invalid account: $account" return 1 } - # fetch works with imap and pop, skip smtp - { test "$account_type" = "smtp" } && { return 0 } + notice "Fetching email for account ${account}" - notice "Connecting account ${account} via ${account_type}" - - is_online ${host} ${port} + is_online ${imap} ${imap_port} { test $? = 0 } || { return 1 } - ask_password $login $host - { test $? = 0 } || { error "Impossible to fetch ${account_type} account ${account}"; return 1 } + type=imap + host=$imap + port=$imap_port + ask_password + { test $? = 0 } || { error "Impossible to fetch email for account ${account}"; return 1 } # this puts total size in $imap_info # experimental only, commented out for now @@ -204,7 +204,7 @@ fetch() { { test $DRYRUN != 1 } && { - fmconf=("poll $host with proto IMAP user \"$login\" there with password \"$password\"") + fmconf=("poll $imap with proto IMAP user \"$login\" there with password \"$password\"") unset password @@ -239,12 +239,12 @@ fetch() { return 1 ;; 2) - error "Invalid or unknown certificate for $host" + error "Invalid or unknown certificate for $imap" unset $fmconf return 1 ;; 3) - error "Invalid password for user $login at $host" + error "Invalid password for user $login at $imap" unset $fmconf return 1 ;; @@ -279,30 +279,34 @@ send() { act "Outbox is empty, no mails to send." return 0 } - read_account smtp ${account} + read_account ${account} { test $? != 0 } && { - error "Account configuration not found, or broken. Aborting operation." + error "Invalid account: $account" return 1 } # defaults { test -z $auth } && { auth=plain } - { test -z $port } && { port=25 } - - is_online ${host} ${port} - { test $? = 0 } || { return 1 } + { test -z $smtp_port } && { smtp_port=25 } - notice "Sending out $queue_outbox_num mails via ${type}.${account}" + is_online ${smtp} ${smtp_port} + { test $? = 0 } || { + error "Smtp host not reachable: $smtp_host:$smtp_port" + return 1 } + notice "Sending out $queue_outbox_num mails via account ${account}" { test $DRYRUN = 1 } && { return 0 } # from here on we must unlock on error lock "${MAILDIRS}/outbox" - ask_password $login $host + type=smtp + host=$smtp + port=$smtp_port + ask_password { test $? = 0 } || { - error "Error retrieving password for $login on $host" + error "Error retrieving password for $login on $smtp" unset password all unlock "${MAILDIRS}/outbox" return 1 } @@ -313,8 +317,8 @@ send() { account default from ${email} user ${login} -host ${host} -port ${port} +host ${smtp} +port ${smtp_port} tls on tls_starttls on tls_certcheck off @@ -328,7 +332,7 @@ EOF # check if this is an anonymous mail hdr "$qbody" | grep -i '^from: anon' > /dev/null if [ $? = 0 ]; then - anoncfg="${TMPDIR}/${host}.anon.$RANDOM" + anoncfg="${TMPDIR}/${smtp}.anon.$RANDOM" cat <<EOF > "$anoncfg" REMAIL n POOLSIZE 0 @@ -419,13 +423,13 @@ BEGIN { head=1 } # PEEK # this function will open the MTA to the imap server without fetching mails locally peek() { - read_account imap ${account} + read_account ${account} { test $? != 0 } && { - error "Account configuration not found, or broken. Aborting operation." + error "Invalid account: $account" return 1 } - is_online ${host} ${port} + is_online ${imap} ${imap_port} { test $? = 0 } || { return 1 } notice "Peek into remote imap account $name" @@ -451,12 +455,16 @@ peek() { { test $DRYRUN != 1 } && { - ask_password $login $host + type=imap + host=$imap + port=$imap_port + ask_password + { test $? != 0 } && { - error "Error retrieving password for $login on $host" + error "Error retrieving password for $login on $imap" unset password all; return 1 } - tmp="$TMPDIR/$host.peek.$RANDOM" + tmp="$TMPDIR/$imap.peek.$RANDOM" newlock $tmp cat <<EOF >> $tmp set imap_pass = "${password}" @@ -474,7 +482,7 @@ EOF cp /dev/null "$TMPDIR/muttpass" unlink $tmp # secure delete in ram ) & - ${=mutt} -F $MUTTDIR/rc -f ${iproto}://${ilogin}@${host}${folder} + ${=mutt} -F $MUTTDIR/rc -f ${iproto}://${ilogin}@${imap}${folder} } # DRYRUN return $? diff --git a/src/zlibs/keyring b/src/zlibs/keyring @@ -56,7 +56,7 @@ EOF ask_password() { case $OS in MAC) - func "Looking for password in Mac/OSX keyring for $email on $host over $type" + func "Looking for password in Mac/OSX keyring for $email ($account)" security find-internet-password \ -c JARO -a $email -s $host \ -p $transport -P $port > /dev/null @@ -75,7 +75,7 @@ ask_password() { ################### # USE GNOME KEYRING if [ "$GNOMEKEY" = "1" ]; then - func "Looking for password in Gnome keyring for $email on $host over $type" + func "Looking for password in Gnome keyring for $email ($account)" func "path: jaromail/${type}/${email}" print "protocol=${type}\npath=jaromail/${type}/${email}\nusername=${login}\nhost=${host}\n\n" \ @@ -86,12 +86,12 @@ ask_password() { error "Password input aborted." return 1 } else # password found into gnome keyring - act "Using saved password for $1 @ $2" + act "Using saved password for $login @ $host" 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 "Looking for password in local keyring for $email ($account)" func "new pass hash for: $type:$login:$host" _hash=`print "$type:$login:$host" | shasum | awk '{print $1}'` lookup="`lookup_secret ${_hash}`" @@ -128,7 +128,7 @@ EOF } new_password() { - notice "Setting a new password for $type account $account on $host" + notice "Setting a new password for account $account on $host" act "please enter password for username '$login'" password=`pin_entry $login $host` res=0