commit c545395aac97837ce33d96710aff08fb519e93c6
parent b37c1ea65b76962077502faf1aa4a34da1b70ef3
Author: Jaromil <jaromil@dyne.org>
Date:   Mon, 18 Jun 2012 01:52:55 +0200
main script broken up into zsh libraries compiled at install time
Diffstat:
| M | src/fetchdate.c |  |  | 4 | +--- | 
| M | src/jaro |  |  | 1675 | +------------------------------------------------------------------------------ | 
| A | src/zlibs/accounts |  |  | 356 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 
| A | src/zlibs/addressbook |  |  | 153 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 
| A | src/zlibs/cmdline |  |  | 111 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 
| A | src/zlibs/email |  |  | 298 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 
| A | src/zlibs/helpers |  |  | 213 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 
| A | src/zlibs/locking |  |  | 81 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 
| A | src/zlibs/maildirs |  |  | 147 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 
| A | src/zlibs/password |  |  | 166 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 
| A | src/zlibs/search |  |  | 259 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 
| A | src/zlibs/stats |  |  | 208 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 
12 files changed, 2007 insertions(+), 1664 deletions(-)
diff --git a/src/fetchdate.c b/src/fetchdate.c
@@ -52,10 +52,8 @@ int main (int argc, char **argv) {
     
 #endif
     free_rfc822(parsed);
-  } else {
-    printf("Error parsing %s\n",argv[1]);
-    exit(1);
   }
+
   exit(0);
 }
 
diff --git a/src/jaro b/src/jaro
@@ -123,6 +123,18 @@ chmod 700 $WORKDIR
 
 PATH=$WORKDIR/bin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/opt/local/bin
 
+# load our ZLibs
+. $WORKDIR/zlibs/accounts
+. $WORKDIR/zlibs/addressbook
+. $WORKDIR/zlibs/cmdline
+. $WORKDIR/zlibs/email
+. $WORKDIR/zlibs/helpers
+. $WORKDIR/zlibs/locking
+. $WORKDIR/zlibs/maildirs
+. $WORKDIR/zlibs/password
+. $WORKDIR/zlibs/search
+. $WORKDIR/zlibs/stats
+
 # temporary directory
 TMPDIR=$WORKDIR/tmp
 case $OS in
@@ -197,1663 +209,6 @@ TRAPINT() {
 	else exit 1; fi
 }
 
-lock() {
-    func "lock: $1"
-    $WORKDIR/bin/dotlock ${1}
-    case $? in
-	1) error "Cannot lock non existing file: $1"
-	    return 1 ;;
-	3) error "Locked file in use: $1"
-	    return 3 ;;
-	5) error "Impossible to lock: $1"
-	    return 5 ;;
-	# success
-	0) echo "$$" > ${1}.pid
-	    return 0 ;;
-    esac
-}
-newlock() { # create locked
-    func "creating locked file: $1"
-    touch $1
-    chmod 600 $1
-    lock $1
-}
-unlock() {
-    func "unlock: $1"
-    lockpid="`cat ${1}.pid`"
-    { test -r ${1}.pid } && {
-	{ test "$$" != "$lockpid" } && {
-	    error "Unlock attempt by different PID: $1"
-	    error "Created by $lockpid now $$ is trying to unlock"
-	    return 1 }
-    }
-
-    $WORKDIR/bin/dotlock -u ${=@}
-    { test $? != 0 } && { error "Unable to unlock: $1"; return 1 }
-    { test -r ${1}.pid } && { rm -f ${1}.pid }
-    return 0
-}
-unlink() { # delete a file that we are locking
-    func "unlink: $1"
-    # use with care! this can permanently erase currently locked files
-    # only the locking PID should use it on its own locks
-
-    { test -r ${1}.pid } && {
-	lockpid="`cat ${1}.pid`"
-	{ test "$$" != "$lockpid" } && {
-	    error "Unlock attempt by different PID: $1"
-	    error "Created by $lockpid now $$ is trying to unlock"
-	    return 1 }
-    }
-
-    (
-	${=rm} ${1}
-	touch ${1}
-	$WORKDIR/bin/dotlock -d -f ${1}
-	{ test $? != 0 } && { error "Unable to unlink: $1"; return 1 }
-	{ test -r ${1}.pid } && { rm -f ${1}.pid }
-    ) &!
-    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
-}
-
-
-check_bin() {
-    # check for required programs
-    for req in pinentry fetchmail procmail mutt; do
-	which $req >/dev/null
-	{ test $? != 0 } && {
-	    error "Cannot find $req. Please install it."
-	    return 1
-	}
-    done
-
-    # which wipe command to use
-    which wipe > /dev/null
-    { test $? = 0 } && {
-	rm="wipe -f -s -q -R /dev/urandom"; return 0 }
-    which srm > /dev/null
-    { test $? = 0 } && {
-	rm="srm -m"; return 0 }
-    rm="rm -f"
-    return 0
-}
-
-# retrieve a password for user @ domain
-# put it in variable password
-# up to the caller to unset it after use
-ask_password() {
-    func "Looking for password in keyring: $name"
-    case $OS in
-	MAC)
-	    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)
-	    func "Looking for password in keyring: $name"
-	    ###################
-	    # USE GNOME KEYRING
-	    { test $GNOMEKEY = 1 } && {
-		echo "protocol=${type}\npath=jaromail/${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=`echo "protocol=${type}\npath=jaromail/${email}\nusername=${login}\nhost=${host}\n\n" | $WORKDIR/bin/jaro-gnome-keyring get`
-		fi
-		return 0
-	    }
-	    ####################
-	    # 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
-}
-
-new_password() {
-    notice "Setting a new password for $name"
-    password=`pin_entry $login $host`
-    res=0
-    case $OS in
-	MAC)
-	    if [ "$password" != "" ]; then
-		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
-		security delete-internet-password \
-		    -c JARO -a $email -s $host \
-		    -p $transport -P $port > /dev/null
-		res=$?; unset password
-		{ test $res != 0 } && {
-		    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
-		{ test $GNOMEKEY = 1 } && {
-
-		    cat <<EOF | $WORKDIR/bin/jaro-gnome-keyring store
-protocol=${type}
-path=jaromail/${email}
-username=${login}
-host=${host}
-password=${password}
-EOF
-		    { test $? != 0 } && { error "Error saving password in Gnome keyring" }
-		    return 0
-		}
-
-		return 0
-
-	    else # password is blank or aborted
-
-		# USE GNOME KEYRING
-		{ test $GNOMEKEY = 1 } && {
-
-		    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
-		}
-
-		return 1
-
-	    fi
-	    ;;
-	*)
-	    error "Unknown system, can't figure out how to handle passwords"
-	    return 1
-    esac
-}
-switch_identity() {
-    if [ "$name" != "" ]; then
-	act "switch to identity: $name <$login>"
-	rm -f $MUTTDIR/identity
-	cat <<EOF > $MUTTDIR/identity
-set hostname = $host
-set realname = "$name"
-set from = "$name <$login>"
-EOF
-    else
-	error "No identity found, left blank."
-	touch $MUTTDIR/identity
-    fi
-}
-
-
-option_is_set() {
-    #First argument, the option (something like "-s")
-    #Second (optional) argument: if it's "out", command will print it out 'set'/'unset'
-    #                       This is useful for if conditions
-    #Return 0 if is set, 1 otherwise
-    [[ -n ${(k)opts[$1]} ]];
-    r=$?
-    if [[ $2 == out ]]; then
-	if [[ $r == 0 ]]; then
-	    echo 'set'
-	else
-	    echo 'unset'
-	fi
-    fi
-    return $r;
-}
-option_value() {
-    #First argument, the option (something like "-s")
-    <<< ${opts[$1]}
-}
-
-# checks if its a maildir
-# returns 0 (success) if yes
-# no in all other cases
-maildircheck() {
-    { test -r $1 } || {	
-	error "Maildir not existing: $1"
-	return 1 }
-    { test -w $1 } || {
-	error "Directory not writable: $1"
-	return 1 }
-    { test -r $1/cur } \
-	&& { return 0 } # Yes is a maildir
-# shortened test to speedup
-#	&& { test -r $1/new } \
-#	&& { test -r $1/tmp } \
-    func "Not a maildir: $1"
-    return 1
-}
-
-maildirmake() {
-
-    if [ -z $1 ]; then
-	error "internal error: missing argument for maildirmake"
-	return
-    fi
-
-    if [ -r $1 ]; then
-	func "maildir exists: $1"
-	return
-    fi
-
-    ${=mkdir} ${1}/cur
-    ${=mkdir} ${1}/new
-    ${=mkdir} ${1}/tmp
-
-}
-# arg 1 : account type, es: imap or smtp
-# -a defines which account name other than 'default'
-read_account() {
-    typeset -al all
-    type=$1
-    # find the file
-    func "read_account looking for \"$account\""
-    for a in `find $WORKDIR/Accounts -name "$type.$account*"`; do
-	func "found account: $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):"
-	for a in `find $WORKDIR/Accounts -type f | grep -v README | sed 's/.txt//'`; 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
-	    echo " (`basename $a | cut -d. -f1`)"
-	done
-	return 1
-    elif [ ${#all} != 1 ]; then
-	error "Too many $type accounts named $account"
-	act -n ""
-	for i in ${=all}; do echo -n "`basename ${i}` "; done
-	echo; 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)
-
-	    lock $acct
-	    ttmp=`cat $acct | 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 }
-    /^login/ { printf "login=\"%s\";", $2 }
-    /^transport/ { printf "transport=\"%s\";", $2 }
-    /^port/ { printf "port=\"%s\";", $2 }
-    /^password/ { printf "password=\"%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
-
-	    eval "$ttmp"
-	    # check required fields
-	    { test -z $host }  && { error "Field missing in account $acct: host"; return 1 }
-
-	    # 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
-
-	    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
-}
-
-# start without options: auto
-# read or compose depending if there is an argument that is an email
-# or a folder to start with
-
-autostart() {
-# no argument passed. open known folder
-    { test -z ${1} } && {
-
-	{ test ! -r $MUTTDIR/rc } \
-	    && { error "Jaro Mail is not yet configured."; return 1 }
-
-	mutt -F $MUTTDIR/rc
-	return $?
-    }
-
-    # argument passed: determine if an email
-    echo "${1}" \
-	| tr 'A-Z' 'a-z' \
-	| grep '^[a-zA-Z0-9._%+-]*@[a-zA-Z0-9]*[\.[a-zA-Z0-9]*]*[a-zA-Z0-9]$' \
-	> /dev/null
-    { test $? = 0 } && {
-	notice "Composing message to: ${1}"
-	# its an email, TODO see if we have it in our addressbook
-	mutt -F $MUTTDIR/rc "${1}"
-	return 0
-    }
-    # or a path to folder
-    { test -r ${1} } && {
-	mutt -F $MUTTDIR/rc -f ${1}
-	return 0
-    }
-
-    # or the name of a folder in Jaro Mail
-    { test -r $MAILDIRS/${1} } && {
-	notice "Opening folder ${1}"
-	mutt -F $MUTTDIR/rc -f "$MAILDIRS/${1}"
-	return 0
-    }
-
-    return $?
-}
-
-queue() {
-    local base;
-    local tmp;
-    local mailfile;
-    local msmtpfile;
-
-    # add mails to the sendout queue
-    ${=mkdir} $MAILDIRS/outbox
-    cd $MAILDIRS/outbox || return 1
-    notice "Adding mail to the outbox queue"
-    # 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`"
-    func "[$base] queue called with params: ${PARAM[@]}"
-
-    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"
-    lock $msmtpfile
-    # Write the mail to $MAILFILE
-    cat > "$mailfile"
-    unlock $msmtpfile
-    cd -
-    return 0
-}
-
-######
-# CERT
-# downloads and/or installs certificates
-cert() {
-    if [ -z $1 ]; then
-	error "Certificate handler called without an argument"
-	return 1;
-    fi
-
-    certificate="$1"
-
-    case $certificate in
-	gmail)
-	    cc=Equifax_Secure_Certificate_Authority
-	    if ! [ -r $WORKDIR/certs/${cc}.pem ]; then
-
-		curl -o $WORKDIR/certs/${cc}.pem \
-		    "https://www.geotrust.com/resources/root_certificates/certificates/${cc}.cer"
-		openssl x509 -in \
-		    $WORKDIR/certs/${cc}.pem -fingerprint \
-		    -subject -issuer -serial -hash -noout
-	    fi
-	    ;;
-	dyne|autistici|freaknet)
-	    cc=Autistici_Certificate_Authority
-	    if ! [ -r $WORKDIR/certs/${cc}.pem ]; then
-		curl -o $WORKDIR/certs/${cc}.pem \
-		    "http://ca.autistici.org/ca.pem"
-		openssl x509 -in \
-		    $WORKDIR/certs/${cc}.pem \
-		    -fingerprint -subject -issuer -serial -hash -noout
-	    fi
-	    ;;
-	riseup)
-	    cc=RiseupCA
-	    if ! [ -r $WORKDIR/certs/${cc}.pem ]; then
-		curl -o $WORKDIR/certs/${cc}.pem "https://help.riseup.net/assets/43052/RiseupCA.pem"
-		openssl x509 -in \
-		    $WORKDIR/certs/${cc}.pem \
-		    -fingerprint -subject -issuer -serial -hash -noout
-	    fi
-	    ;;
-	*)
-	    cc="`basename $certificate`"
-	    curl -o "$WORKDIR/certs/${cc}" "$certificate"
-	    if [ $? != 0 ]; then
-		error "Error downloading certificate: $certificate"
-		return 1
-	    fi
-	    openssl x509 -in \
-		"$WORKDIR/certs/${cc}" \
-		-fingerprint -subject -issuer -serial -hash -noout
-	    ;;
-    esac
-    act "refreshing certificates"
-    c_rehash $WORKDIR/certs > /dev/null
-    if [ $? != 0 ]; then
-	error "Error refreshing certificates in $WORKDIR/certs"
-	c_rehash $WORKDIR/certs
-    fi
-    return 0
-}
-
-###########
-# FETCHMAIL
-fetchall() {
-    notice "Fetching all accounts in $MAILDIRS"
-    res=0
-    for i in `find $WORKDIR/Accounts -type f | grep -v README`; do
-	account=`basename $i`
-	type=`echo $account | cut -d. -f1`
-	func "fetchall: $account type $type"
-	fetch
-	if [ $? != 0 ]; then res=1; fi
-	# returns an error if just one of the accounts did
-    done
-    return $res
-}
-fetch() {
-    adir=$WORKDIR/Accounts
-
-    typeset -al all
-
-    # recursion here
-    { test $account = default } && { fetchall; return $? }
-
-    read_account "$account"
-    if [ $? != 0 ]; then
-	error "Account configuration not found, or broken. Aborting operation."
-	return 1
-    fi
-
-    if [ "$type" = "smtp" ]; then # we skip smtp in fetch
-	return 0; fi
-
-    if ! [ -r $PROCMAILDIR/rc ]; then
-	act "updating procmail configuration"
-	update
-    fi
-
-    notice "Fetching mails for $name"
-    ask_password $login $host
-    { test $? != 0 } && {
-	error "Error retrieving password for $login on $host"
-	unset password all; return 1
-    }
-
-    tmp=$TMPDIR/$host.fetch.$RANDOM
-    newlock $tmp
-    cat <<EOF > $tmp
-poll $host with proto IMAP user "$login" there with password "$password"
-EOF
-    unset password
-
-    if ! [ -z $accountopt ]; then # add option configuration
-	echo "${accountopt}" >> $tmp; fi
-
-    if ! [ -z $folders ]; then # add folder configuration
-	echo "folder ${folders}" >> $tmp; fi
-
-    cat <<EOF >> $tmp
-ssl warnings 3600 and wants mda "procmail -m $PROCMAILDIR/rc"
-EOF
-    if [ "$cert" = "check" ]; then
-	cat <<EOF >> $tmp
-sslcertck sslcertpath '$WORKDIR/certs'
-EOF
-    fi
-    cat <<EOF >> $tmp
-antispam 571 550 501 554
-EOF
-
-	# try login without doing anything
-    fetchmail -c -f $tmp
-	# examine result
-    case $? in
-	1)
-	    notice "No mails for $name"
-	    unlink $tmp
-	    return 1
-		;;
-	2)
-	    error "Invalid or unknown certificate for $host"
-	    unlink $tmp
-	    return 1
-	    ;;
-	3)
-	    error "Invalid password for user $login at $host"
-	    unlink $tmp
-	    return 1
-	    ;;
-	*) ;;
-    esac
-
-	# archive old procmail log
-    if [ -r $WORKDIR/log/procmail.log ]; then
-	newlock $WORKDIR/log/procmail-${datestamp}.log
-	cat $WORKDIR/log/procmail.log \
-	    >> $WORKDIR/log/procmail-${datestamp}.log
-	rm -f $WORKDIR/log/procmail.log
-	unlock $WORKDIR/log/procmail-${datestamp}.log
-    fi
-    act "please wait while downloading mails..."
-
-    cat $tmp | fetchmail -f $tmp
-	# TODO: substitute this with cat conf | fetchmail -f -
-	# to avoid writing the password in clear on filesystem
-
-    unlink $tmp
-
-    total=`mailstat -k $WORKDIR/log/procmail.log | tail -n1 | awk '{print $2}'`
-    briefing=`mailstat -kt $WORKDIR/log/procmail.log |awk '!/procmail/ { print "    " $2 "\t" $3 }'|sort -nr`
-    notice "$total emails fetched"
-    echo "${briefing}"
-
-    return 0
-}
-
-list_maildirs() {
-    maildirs=()
-    for m in `find $MAILDIRS -type d`; do
-	maildircheck $m
-	{ test $? = 0 } && { maildirs+=(`basename $m`) }
-    done
-    return ${#maildirs}
-}
-
-stats() {
-    # make index of all maildirs
-    notice "Maildirs status"
-    typeset -alU ml
-    typeset -al empty
-    for i in `ls $MAILDIRS`; do
-	{ maildircheck $MAILDIRS/${i} } || { continue }
-	cur=`ls $MAILDIRS/$i/cur | wc -l`
-	new=`ls $MAILDIRS/$i/cur | wc -l`
-	tot=$(( $cur + $new ))
-	ml+=("$tot\t:: $i\t ($cur/$new)")
-    done
-    for m in ${ml}; do
-	{ test ${m[1]} = "0" } && { 
-	    empty+=${m}
-	    continue }
-	act "$m"
-    done
-    { test ${#empty} != 0 } && { emptystr="(${#empty} are empty)" }
-    notice "Total maildirs: ${#ml} $emptystr"
-    return 0
-}
-
-timecloud() {
-    id=$RANDOM
-    list_maildirs
-    notice "Computing timecloud statistics on ${#maildirs} maildirs"
-    func "Creating temporary database"
-    cat <<EOF | ${SQL} -batch $TMPDIR/timecloud.db.$id
-CREATE TABLE stats
-(
-  date    text collate nocase,
-  tag     text collate nocase,
-  hits    int
-);
-EOF
-    { test $? != 0 } && {
-	error "Error creating temporary timecloud database."
-	return 1 }
-    
-    for m in ${maildirs}; do
-	for f in `find $MAILDIRS/${m} -type f`; do
-	    timestamp=`fetchdate "%Y-%m-%d" ${f}`
-	    cat <<EOF | ${SQL} -batch $TMPDIR/timecloud.db.$id > $TMPDIR/timecloud.select.$id
-SELECT * FROM stats
-WHERE tag IS "${m}" AND date IS "${timestamp}";
-EOF
-	    res=`cat $TMPDIR/timecloud.select.$id`
-	    if [ "$res" = "" ]; then
-		# new tag
-		cat <<EOF | ${SQL} -batch $TMPDIR/timecloud.db.$id
-INSERT INTO stats (date, tag, hits)
-VALUES ("${timestamp}", "${m}", 1);
-EOF
-	    else
-		cat <<EOF | ${SQL} -batch $TMPDIR/timecloud.db.$id
-UPDATE stats SET hits = hits + 1
-WHERE tag IS "${m}" AND date IS "${timestamp}";
-EOF
-	    fi
-	done
-    done
-    # gather results from the database
-    cat <<EOF | ${SQL} -batch $TMPDIR/timecloud.db.$id > $TMPDIR/timecloud.results.$id
-.separator \t
-SELECT * FROM stats ORDER BY date;
-EOF
-    # format the results into a JSON string
-    awk 'BEGIN { date=0; printf "[" }
-{ if(date != $1) {
-    if( date != 0 ) printf "]],"
-    date=$1
-    printf "[\"" date "\",["
-    printf "[\"" $2 "\",\"" $3 "\"]"
-  } else {
-    printf ",[\"" $2 "\",\"" $3 "\"]"
-  }
-}
-END { print "]]];" }
-'  $TMPDIR/timecloud.results.$id > $TMPDIR/timecloud.json.$id
-    ${=mkdir} $WORKDIR/timecloud
-    cat <<EOF > $WORKDIR/timecloud/jaromail.js
-var jaromail=`cat $TMPDIR/timecloud.json.$id`
-EOF
-    rm $TMPDIR/timecloud.*.$id
-}
-
-######
-# SEND
-# this function should send all mails in outbox
-send() {
-    adir=$WORKDIR/Accounts
-    typeset -al all
-
-    # list mails to send
-    mailnum=`ls ${MAILDIRS}/outbox | grep 'mail$' | wc -l`
-    mailnum=${mailnum// /} # trim whitespace
-    if [ "$mailnum" = "0" ]; then
-	act "Outbox is empty, no mails to send."
-	return 0
-    fi
-
-    read_account smtp
-    if [ $? != 0 ]; then
-	error "Account configuration not found, or broken. Aborting operation."
-	return 1
-    fi
-
-    notice "Sending out ${mailnum} mails via $name"
-
-    # defaults
-    [ -z $auth ] && auth=plain
-    [ -z $port ] && port=25
-
-
-    ask_password $login $host
-    { test $? != 0 } && {
-	error "Error retrieving password for $login on $host"
-	unset password all; return 1
-    }
-
-    tmp=$TMPDIR/$host.send.$RANDOM
-    newlock $tmp
-    cat <<EOF > $tmp
-account default
-from ${email}
-user ${login}
-host ${host}
-port ${port}
-tls on
-tls_starttls on
-tls_certcheck off
-logfile ${WORKDIR}/log/msmtp.log
-auth ${auth}
-password ${password}
-EOF
-    unset password
-
-    for mail in `find $MAILDIRS/outbox -name "*.mail"`; do
-       smtp=`echo ${mail} | sed -e 's/mail/msmtp/'`
-       lock ${smtp}
-       recipients="`cat ${smtp}`"
-       act "To: ${recipients}"
-       msmtp -C $tmp -- ${=recipients} < "${mail}"
-       if [ $? != 0 ]; then
-	   error "Error sending mail, skipped"
-	   unlock ${smtp}
-       else
-	   act "Mail sent succesfully"
-	   # whitelist those to whom we send mails
-	   cat ${mail} | $WORKDIR/bin/jaro -l whitelist -q learn to:
-	   ${=rm} ${mail} &
-	   unlink ${smtp} &
-       fi
-    done
-    unlink $tmp
-    return 0
-}
-
-######
-# PEEK
-# this function will open the MTA to the imap server without fetching mails locally
-peek() {
-    read_account imap
-    if [ $? != 0 ]; then
-	error "Account configuration not found, or broken. Aborting operation."
-	return 1
-    fi
-
-    notice "Peek into remote imap account $name"
-
-    folder=""
-    if ! [ -z ${1} ]; then
-	folder="/${1}"
-	act "opening folder ${folder}"
-    fi
-
-    switch_identity
-    case $transport in
-	ssl) act "using secure connection (SSL)"
-	    iproto="imaps" ;;
-	plain) act "using clear text connection"
-	    iproto="imap"  ;;
-    esac
-    # escape at sign in login
-    ilogin=`echo $login | sed 's/@/\\@/'`
-
-    ask_password $login $host
-    { test $? != 0 } && {
-	error "Error retrieving password for $login on $host"
-	unset password all; return 1
-    }
-    tmp=$TMPDIR/$host.peek.$RANDOM
-    newlock $tmp
-    cat <<EOF >> $tmp
-set imap_pass = "${password}"
-# set imap_peek = yes
-EOF
-    unset password
-    echo "source $tmp" >> $MUTTDIR/password
-    (sleep 1;
-	cp /dev/null $MUTTDIR/password
-	cp /dev/null $tmp
-	unlink $tmp
-    ) &
-    mutt -F $MUTTDIR/rc	-f ${iproto}://${ilogin}@${host}${folder}
-    return $?
-}
-
-update() {
-    notice "Updating all configurations and filters"
-    # this function should:
-    # parse all filters
-    # generate procmailrc
-    # generate muttrc
-    # backup what's too old in the maildirs
-    # ...
-
-    # debug configuration
-    func "MAILDIRS:    $MAILDIRS"
-    func "WORKDIR:     $WORKDIR"
-    func "MUTTDIR:     $MUTTDIR"
-    func "PROCMAILDIR: $PROCMAILDIR"
-
-    # make sure maildirs where to put mails exist
-    ${=mkdir} $MAILDIRS
-    maildirmake $MAILDIRS/known
-    maildirmake $MAILDIRS/sent
-    maildirmake $MAILDIRS/priv
-    maildirmake $MAILDIRS/postponed
-    maildirmake $MAILDIRS/unsorted
-    maildirmake $MAILDIRS/ml.unsorted
-    ${=mkdir} $MAILDIRS/outbox
-
-    ######
-    # MUTT
-    ${=mkdir} $MUTTDIR
-    rm -f $MUTTDIR/rc
-    cat<<EOF > $MUTTDIR/rc
-# mutt config generated by Jaro Mail
-unset use_domain
-set folder = $MAILDIRS
-set spoolfile = $MAILDIRS/known/
-set record = $MAILDIRS/sent/
-set postponed= $MAILDIRS/postponed/
-set tmpdir = $WORKDIR/cache
-set sendmail = "$WORKDIR/bin/jaro queue"
-set header_cache= $WORKDIR/cache
-set maildir_header_cache_verify=no
-set editor = "$WORKDIR/bin/jaro edit"
-set mailcap_path = "$WORKDIR/.mutt/mailcap:$WORKDIR/mailcap:$HOME/.mailcap:/etc/mailcap:/etc/mailcap:/usr/etc/mailcap:/usr/local/etc/mailcap"
-
-# Little Brother Database
-set query_command = "$WORKDIR/bin/jaro -q addr '%s'"
-macro index,pager a "<pipe-message>$WORKDIR/bin/jaro -l whitelist -q learn from:<enter>" "add to whitelist everyone in the message"
-macro index,pager A "<pipe-message>$WORKDIR/bin/jaro -l whitelist -q forget from:<enter>" "remove sender from whitelist
-macro index,pager z "<pipe-message>$WORKDIR/bin/jaro -l blacklist -q learn from:<enter>" "add sender to blacklist"
-macro index,pager Z "<pipe-message>$WORKDIR/bin/jaro -l blacklist -q forget from:<enter>" "remove sender from blacklist
-
-# mailboxes in order of priority
-source $MUTTDIR/mboxes
-
-# specific configuration files
-source $MUTTDIR/crypto
-source $MUTTDIR/general
-source $MUTTDIR/formats
-source $MUTTDIR/keybindings
-source $MUTTDIR/identity
-source $MUTTDIR/password
-source $MUTTDIR/colors
-source $WORKDIR/Mutt.txt
-## end of Jaro Mail generated muttrc
-####################################
-
-EOF
-
-   # making sure we have the minimum mailcap necessary
-wwwtext=w3m
-{ which lynx > /dev/null } && { wwwtext=lynx }
-{ which elinks > /dev/null } && { wwwtext=elinks }
-cat <<EOF > $MUTTDIR/mailcap
-text/html; ${wwwtext} -dump %s; nametemplate=%s.html; copiousoutput
-application/*; a=$WORKDIR/tmp/attach-$RANDOM$RANDOM && cp %s \$a && jaro open \$a
-text/plain; iconv -f iso-8859-1 -t utf-8; test=charset=%{charset} \
-  && test x`echo \"$charset\" | tr a-z A-Z` = xISO-8859-1; copiousoutput
-text/plain; cat %s
-EOF
-
-    # this one is empty and sources files in temp when necessary
-    touch $MUTTDIR/password
-
-    # just the header, will be completed later in procmail loop
-    rm -f $MUTTDIR/mboxes
-    echo -n "mailboxes +priv" > $MUTTDIR/mboxes
-
-    # update identity with default
-    read_account imap
-
-    switch_identity
-
-    ##########
-    # PROCMAIL
-    act "generating procmail filters"
-    ${=mkdir} $PROCMAILDIR
-    rm -f $PROCMAILDIR/rc
-    touch $PROCMAILDIR/rc
-    cat<<EOF >> $PROCMAILDIR/rc
-# procmail configuration file generated by Jaro Mail
-MAILDIR=$MAILDIRS
-JARO=$WORKDIR/bin/jaro
-DEFAULT=unsorted/
-VERBOSE=off
-LOGFILE=$WORKDIR/log/procmail.log
-SHELL       = /bin/sh       # VERY IMPORTANT
-UMASK       = 007           # James Bond :-)
-LINEBUF     = 8192          # avoid procmail choke
-
-#  Using Procmail Module Library http://sf.net/projects/pm-lib
-PMSRC  = $PROCMAILDIR
-#  Load the central initial startup code.
-INCLUDERC = \$PMSRC/pm-javar.rc
-PF_DEST = ""			# clear these vars
-PF_FROM = ""
-PF_RECURSE = yes
-
-# blacklist filters
-:0 w:
-* ? \$JARO -l blacklist -q query from:
-zz.blacklist/
-
-# filters generated from Filters.txt
-:0
-* ? test \$PMSRC/pf-chkto.rc
-{
-EOF
-
-    #######
-    echo "# filters generated from Filters.txt" >> $PROCMAILDIR/rc
-
-    for f in `cat $WORKDIR/Filters.txt | awk '/^#/ {next} /^./ { print $1 ";" $2 ";" $3 ";" $4 }'`; do
-	header="${f[(ws:;:)1]}"
-	address="${f[(ws:;:)2]}"
-	action="${f[(ws:;:)3]}"
-	destination="${f[(ws:;:)4]}"
-	case $header in
-	    to)
-		print "ADDR=${address}\tDEST=${destination}/\tINCLUDERC=\$PMSRC/pf-chkto.rc" \
-		    >> $PROCMAILDIR/rc
-		func "messages to <${address}> in folder: ${destination}"
-		maildirmake $MAILDIRS/$destination
-		;;
-	    from)
-		print "ADDR=${address}\tDEST=${destination}/\tINCLUDERC=\$PMSRC/pf-check.rc" \
-		    >> $PROCMAILDIR/rc
-		func "messages from <${address}> in folder: {$destination}"
-		maildirmake $MAILDIRS/$destination
-		;;
-	    *)
-		error "unsupported filter: $header (skipped)"
-		;;
-	esac
-	# MUTT (generate mailboxes priority this parser)
-	echo  " \\" >> $MUTTDIR/mboxes
-	echo -n " +${destination} " >> $MUTTDIR/mboxes
-    done
-
-    # if the sender is known (ldbd recognizes it) then put mail in high priority 'known'
-    cat <<EOF >> $PROCMAILDIR/rc
-}
-
-:0
-* PF_DEST ?? .
-* ? test \$PMSRC/pf-save.rc
-{ INCLUDERC=\$PMSRC/pf-save.rc }
-
-
-# whitelisting filters
-:0 w:
-* ? \$JARO -l whitelist -q query from:
-known/
-
-
-EOF
-
-    #######
-cat <<EOF >> $PROCMAILDIR/rc
-# filters generated from Accounts
-:0
-* ? test \$PMSRC/pf-chkto.rc
-{
-EOF
-    for f in `cat $WORKDIR/Accounts/* | awk '/^email/ { print $2 }'`; do
-	echo "ADDR=${f}\tDEST=priv/\tINCLUDERC=\$PMSRC/pf-chkto.rc"  >> $PROCMAILDIR/rc
-	func "private account: <${f}>"
-    done
-
-    cat <<EOF >> $PROCMAILDIR/rc
-}
-
-:0
-* PF_DEST ?? .
-* ? test \$PMSRC/pf-save.rc
-{ INCLUDERC=\$PMSRC/pf-save.rc }
-
-# if its an unknown mailinglist, save it into ml.unsorted
-:0
-* ^(List-Id|X-(Mailing-)?List):
-ml.unsorted/
-
-EOF
-
-    # MUTT (generate mailboxes priority this parser)
-    echo " \\" >> $MUTTDIR/mboxes
-    echo " +ml.unsorted +unsorted" >> $MUTTDIR/mboxes
-
-    uniq $MUTTDIR/mboxes > $TMPDIR/mboxes
-    mv $TMPDIR/mboxes $MUTTDIR/mboxes
-    rm -f $TMPDIR/mboxes
-
-    # conclude
-    cat <<EOF >> $PROCMAILDIR/rc
-
-# if got here, go to unsorted
-:0:
-\$DEFAULT
-
-#
-# End of generated procmail rc
-#
-EOF
-    return 0
-} # end of update()
-
-###################
-# Jaro Brother DB
-create_addressbook() {
-    { test -r $WORKDIR/addressbook } && {
-	error "Addressbook already exists: $WORKDIR/addressbook"
-	return 1
-    }
-    cat <<EOF | ${SQL} -batch $WORKDIR/addressbook
-CREATE TABLE whitelist
-(
-  email   text collate nocase,
-  name    text collate nocase unique,
-  hits    int
-);
-CREATE TABLE blacklist
-(
-  email   text collate nocase,
-  name    text collate nocase unique,
-  hits    int
-);
-EOF
-    { test $? != 0 } && {
-	error "Error creating addressbook database."
-	return 1 }
-    return 0
-}
-insert_address() {
-    func "insert address: $1, $2"
-    cat <<EOF | ${SQL} -batch $WORKDIR/addressbook 2> /dev/null
-INSERT INTO $list (email, name)
-VALUES ("${1}", "${2}");
-EOF
-    { test $? != 0 } && {
-	func "address already present in $list" }
-    return $0
-}
-remove_address() {
-    func "remove address <$1> from $list"
-    cat <<EOF | ${SQL} -batch $WORKDIR/addressbook
-DELETE FROM $list
-WHERE email LIKE "${1}";
-EOF
-    { test $? != 0 } && {
-	func "address not found or error occurred" }
-}
-search_name() {
-	cat <<EOF | ${SQL} -column -batch $WORKDIR/addressbook
-.width 64 128
-SELECT * FROM $list
-WHERE name LIKE "%${1}%";
-EOF
-}
-search_email() {
-	cat <<EOF | ${SQL} -column -batch $WORKDIR/addressbook
-SELECT rowid FROM $list
-WHERE email IS "${1}";
-EOF
-return $?
-}
-address() {
-    { test ! -r ${WORKDIR}/addressbook } && { create_addressbook }
-    act "Searching for \"${PARAM[1]}\" in $list"
-    { test "$OS" = "MAC" } && {
-	matches=`$WORKDIR/bin/ABQuery ${PARAM[1]}`
-    }
-    matches="${matches}\n`search_name ${PARAM[1]}`"
-
-    # mutt query requires something like this
-    echo "jaro: `echo $matches | wc -l` matches"
-    echo "$matches" | awk '
-{ printf "%s\t", $1
-  for(i=2;i<=NF;i++) {
-    sub("<","",$i)
-    sub(">","",$i)
-    if($i!=$1) printf "%s ", $i
-  }
-  printf "\n" }'
-}
-query() {
-    { test ! -r ${WORKDIR}/addressbook } && { create_addressbook }
-    if [ -z ${PARAM[1]} ]; then
-	email="`${WORKDIR}/bin/fetchaddr -a| awk '{print $1}'`"
-    else
-	email="`${WORKDIR}/bin/fetchaddr -x ${PARAM[1]} -a| awk '{print $1}'`"
-    fi
-    exitcode=1
-    lookup="`search_email ${email}`"
-    { test "$lookup" != "" } && { exitcode=0 }
-    act "Email <$email> found in $list with id $lookup"
-}
-
-learn() {
-    { test ! -r ${WORKDIR}/addressbook } && { create_addressbook }
-    tmp=$TMPDIR/learn.$datestamp.$RANDOM
-    from="`tee $tmp | formail -xFrom: | sed -e 's/\"//g'`"
-    if [ -z ${PARAM[1]} ]; then
-	email="`cat $tmp | ${WORKDIR}/bin/fetchaddr| awk '{print $1}'`"
-    else
-	email="`cat $tmp | ${WORKDIR}/bin/fetchaddr -x ${PARAM[1]}| awk '{print $1}'`"
-    fi
-    ${=rm} $tmp &
-    insert_address "$email" "$from"
-}
-
-forget() {
-    { test ! -r ${WORKDIR}/addressbook } && { create_addressbook }
-    act "Expecting mail from stdin pipe"
-    if [ -z ${PARAM[1]} ]; then
-	email="`${WORKDIR}/bin/fetchaddr| awk '{print $1}'`"
-    else
-	email="`${WORKDIR}/bin/fetchaddr -x ${PARAM[1]}| awk '{print $1}'`"
-    fi
-    remove_address "${email}"
-}
-list_addresses() {
-    { test ${PARAM[1]} } && { list=${PARAM[1]} }
-    act "Listing all contents for $list"
-    cat <<EOF | ${SQL} -column -header -batch $WORKDIR/addressbook
-.width 32 40
-SELECT * FROM $list;
-EOF
-
-    echo ".dump" | ${SQL} -batch $WORKDIR/addressbook \
-	| bzip2 > $WORKDIR/addressbook.bz2
-    notice "Backup of all addressbook created"
-    act -n ""; ls -lh $WORKDIR/addressbook.bz2
-}
-
-###################
-
-
-#########
-## Editor
-# this part guesses what is the best editor already present on the system
-# of course we have a preference for AutOrg, the editor from our suite
-# however the default is nano if nothing else is choosen.
-editor() {
-    { test ${EDITOR} } && {
-	${=EDITOR} ${PARAM[1]}
-	return $? }
-    case $OS in
-	MAC) open -t ${PARAM[1]} ;;
-	GNU)
-	    { which nano > /dev/null } && {
-		nano -m -S -Q ">" -I -E -D -T 4 -U -W -c -i -k -r 72 ${PARAM[1]} }
-	    error "No editor found, please configure the EDITOR environment variable."
-	    sleep 3
-	    ;;
-    esac
-    return $?
-}
-
-#######################
-## Search into maildirs
-# using mairix
-search() {
-    { which mairix > /dev/null } || { return 1 }
-    id=$RANDOM
-    rc=$TMPDIR/search.conf.$id
-    typeset -al expr
-    typeset -al fold
-    # intelligent parse of args, position independent
-    # check if its a folder, if not is an expression
-    mlstr="all folders"; ml=""; c=0
-    basedir=$MAILDIRS
-    # check if the name of a maildir is among params
-    for p in ${PARAM}; do
-	c=$(( $c + 1 ))
-	func "checking param: ${p}"
-	if [ -r ${p} ]; then
-
-	    { maildircheck ${p} } && { 
-		fold+=(${p})
-		{ test ${#fold} = 1 } && {
-		    # base path is the dir of the first folder
-		    pushd `dirname ${p}`
-		    basedir=`pwd`
-		    popd }
-	    }
-	    
-        elif [ -r ${MAILDIRS}/${p} ]; then
-
-	    { maildircheck ${MAILDIRS}/${p} } && { fold+=(${MAILDIRS}/${p}) }
-
-	else # not a folder, add it to expressions array
-	    expr+=(${p})
-	fi
-    done
-
-
-    # now fold is an array of specified folders
-    # expr is an array of specified search expressions
-
-
-    # to search only one maildir then we need to index it
-    # separate from the rest of the maildirs
-    if [ ${#fold} != 0 ]; then
-	{ test ${#expr} = 0 } && {
-	    error "no search expression given for folders ${fold[@]}"
-	    return 1 }
-	# forge the folder string for mairix conf
-	folders=""; for f in ${fold}; do folders="$folders`basename $f`:"; done
-	cat <<EOF > $rc
-base=$basedir
-database=$TMPDIR/search.db.$id
-maildir=${folders}
-mfolder=$TMPDIR/search.result.$id
-mformat=maildir
-EOF
-
-	exitcode=1
-	act "Indexing ${folders}"
-	mairix -F -f $rc 2> /dev/null
-	{ test $? = 0 } && { 
-	    act "Searching for: ${expr}"
-	    found=`mairix -F -f $rc ${=expr} 2> /dev/null | awk '{ print $2}'`
-	    if [ $found != 0 ]; then
-		mutt -F $MUTTDIR/rc -R -f $TMPDIR/search.result.$id
-		notice "Found $found matches looking for '$expr' in $folders"
-		find $TMPDIR/search.result.$id
-		cat $rc
-		exitcode=0
-	    else error "No matches found."; fi
-	}
-
-
-	rm -f $rc
-	rm -f $TMPDIR/search.db.$id
-#	rm -rf $TMPDIR/search.result.$id
-	return $exitcode
-
-
-	####################################################
-    else    # no folder specified on commandline, search all
-    # make index if no params given
-	c=0
-	for i in `ls $MAILDIRS`; do
-	# is it a maildir?
-	    { maildircheck $MAILDIRS/${i} } && {
-		c=`expr $c + 1`; ml="$ml:$i" }
-	done
-    fi
-
-    cat <<EOF > $rc
-base=$MAILDIRS
-database=$WORKDIR/search.db
-maildir=${ml}
-mfolder=$TMPDIR/search.result.$id
-mformat=maildir
-EOF
-    # just index
-    { test ${#PARAM} = 0 } && {
-	act "Indexing $c maildirs for search"
-	act "please be patient..."
-	mairix -F -f $rc
-	rm -f $rc
-	exitcode=$?
-	if [ $exitcode = 0 ]; then notice "Done."
-	else error "Error, indexing aborted."; fi
-	rm -f $rc
-	return $exitcode
-    }
-    act "Searching $mlstr for: ${expr}"
-    exitcode=1
-    found=`mairix -F -f $rc ${=expr} 2> /dev/null | awk '{print $2}'`
-    if [ $CALLMUTT = 1 ]; then
-
-	if [ $found != 0 ]; then
-	    mutt -F $MUTTDIR/rc -R -f $TMPDIR/search.result.$id
-	    notice "Found $found matches looking for '$expr' in all mail folders"
-	    exitcode=0
-	else error "Nothing found matching '$expr'"; fi
-
-	rm -rf $TMPDIR/search.*.$id
-	rm -f $rc
-	return $exitcode
-
-    else ### if not calling mutt, internal use mode:
-	# print out the full path to the results maildir
-	# return number of found hits
-	echo $TMPDIR/search.result.$id
-	return $found
-    fi
-}
-##############
-## Open a File
-open_file() {
-    a=$WORKDIR/cache/attach-$RANDOM$RANDOM
-    cp ${PARAM[1]} $a
-    case $OS in
-	GNU)
-	    # TODO
-	    ;;
-	MAC)
-	    open -g $a
-	    ;;
-    esac
-}
-
-######################
-# Maildir manipulation
-backup() {
-    id=$RANDOM
-    rc=$TMPDIR/backup.conf.$id
-    typeset -al expr
-    typeset -al fold
-
-    src=""; dst=""
-    basedir=$MAILDIRS
-    # check if the name of a maildir is among params
-    # we need at least 2 maildirs, the second is the destination
-    for p in ${PARAM}; do
-	c=$(( $c + 1 ))
-
-	if [ $c = ${#PARAM} ]; then
-	    # last one is always the destination
-	    func "destination is ${p}"
-	    fold+=(${p})
-
-	elif [ -r ${p} ]; then
-
-	    { maildircheck ${p} } && {
-		func "param ${p} is a maildir"
-		fold+=(${p})
-		{ test ${#fold} = 1 } && {
-		    # base path is the dir of the first folder
-		    pushd `dirname ${p}`
-		    basedir=`pwd`
-		    popd }
-	    }
-	    
-        elif [ -r ${MAILDIRS}/${p} ]; then
-
-	    { maildircheck ${MAILDIRS}/${p} } && {
-		func "param ${p} is a jaro maildir"
-		fold+=(${MAILDIRS}/${p})
-	    }
-
-	else # not a folder, add it to expressions array
-	    func "param ${p} is an expression"
-	    expr+=(${p})
-	fi
-    done
-
-    { test ${#fold} -lt 2 } && {
-	error "Not enough folders specified for backup: minimum is 2"
-	act "When specifying more than 2, the last one is the destination"
-	return 1
-    }
-
-    dst=${fold[${#fold}]}
-    { test -r $dst } && {
-	error "Backup destination already exists: $dst"
-	return 1 }
-
-    maildirmake "${dst}"
-
-    { test ${#expr} = 0 } && {
-	error "No expression set for backup, please indicate what you want to backup"
-	act "For example: d:10y-2y (all mails older than 1 year up to 10 years ago"
-	act "Or a simple search string, all expressions can be verified using search."
-	return 1
-    }
-
-    # forge the folder string for mairix conf
-    folders=""
-    for f in ${fold}; do
-	{ test $f = $dst } || { 
-	    folders="$folders`basename $f`:" }
-    done
-
-    notice "Backup of all mails in '$folders' matching expression '$expr'"
-
-    act "Indexing folders"
-    cat <<EOF > $rc
-base=$basedir
-database=$TMPDIR/backup.db.$id
-maildir=${folders}
-mfolder=$dst
-mformat=maildir
-EOF
-    mairix -F -f $rc 2> /dev/null
-    act "Moving matches to $dst"
-    pushd `dirname $dst`; basedir=`pwd`; popd
-    rm -f $rc; cat <<EOF > $rc
-base=$basedir
-database=$TMPDIR/backup.db.$id
-maildir=${folders}
-mfolder=$dst
-mformat=maildir
-EOF
-    found=`mairix -F -f $rc -H ${expr} 2> /dev/null | awk '{print $2}'`
-    notice "$found matches found, destination folder size is `du -hs $basedir/$dst | awk '{print $1}'`"
-    # invert the order of folders to start with destination in rmdupes
-    typeset -al PARAM
-    c=$(( ${#fold} ))
-    while [ $c -gt 0 ]; do
-	func "${fold[$c]}"
-	PARAM+=(${fold[$c]})
-	c=$(( $c - 1 ))
-    done
-    QUIET=1
-    rmdupes
-}
-
-rmdupes() {
-    tmp=$TMPDIR/$datestamp.rmdupes.$RANDOM
-    newlock $tmp
-    for folder in ${=PARAM}; do
-	{ test ! -r $folder } && { folder=$MAILDIRS/$folder }
-	{ test ! -r $folder } && { error "Maildir not found: $folder"; continue }
-	notice "Removing duplicates in $folder"
-	c=0
-	for i in `find ${folder} -type f`; do
-	    # 5MB should be enough ehre?
-	    formail -D 5000000  $tmp <$i \
-		&& rm $i && c=`expr $c + 1`
-	done
-    done
-    unlink $tmp
-    notice "$c duplicates found and deleted"
-}
-
-merge() {
-    src=${PARAM[1]}
-    dst=${PARAM[2]}
-    if ! [ -r ${src}/cur ]; then
-	error "No source maildir found in $src"
-	return 1
-    fi
-    if ! [ -r ${dst}/cur ]; then
-	error "No destination maildir found in $dst"
-	return 1
-    fi
-    notice "Merging maildir ${src} into ${dst}"
-    c=0
-    for i in `find ${src}/cur -type f`; do mv $i ${dst}/cur/; c=`expr $c + 1`; done
-    for i in `find ${src}/new -type f`; do mv $i ${dst}/new/; c=`expr $c + 1`; done
-    for i in `find ${src}/tmp -type f`; do mv $i ${src}/tmp/; c=`expr $c + 1`; done
-    act "$c mails succesfully moved."
-    notice "Operation completed, you can now safely remove ${src}"
-}
-
-# re-sort all maildir through the procmail filters
-# it can create duplicates, up to the user to rmdupes
-filter() {
-
-    update # update all filters
-
-	# archive old procmail log
-    if [ -r $WORKDIR/log/procmail.log ]; then
-	newlock $WORKDIR/log/procmail-${datestamp}.log
-	cat $WORKDIR/log/procmail.log \
-	    >> $WORKDIR/log/procmail-${datestamp}.log
-	rm -f $WORKDIR/log/procmail.log
-	unlock $WORKDIR/log/procmail-${datestamp}.log
-    fi
-
-    for folder in ${=PARAM}; do
-	typeset -al fall
-	{ test ! -r $folder } && { folder=$MAILDIRS/$folder }
-	{ test ! -r $folder } && { error "Maildir not found: $folder"; return 1 }
-	notice "Filtering folder $folder"
-	# first index current state
-	for m in `find $folder -type f`; do fall+=($m); done
-	# then process it, this way ignoring new mails send to same folder
-	for n in ${=fall}; do
-	    cat $n | procmail -m $PROCMAILDIR/rc
-	done
-	unset fall
-    done
-
-    total=`mailstat -k $WORKDIR/log/procmail.log | tail -n1 | awk '{print $2}'`
-    briefing=`mailstat -kt $WORKDIR/log/procmail.log |awk '!/procmail/ { print "    " $2 "\t" $3 }'|sort -nr`
-    echo "${briefing}"
-}
-
-# opens and closes a ramdisk for temporary files
-# users can do this explicitly between session to speed up operations
-ramdisk() {
-    case $OS in
-	GNU)
-	    # TODO
-	    # not so urgent, since usually /dev/shm is mounted and writable
-	    ;;
-	MAC)
-	    case ${PARAM[1]} in
-		open)
-		    mount | grep 'JaroTmp' > /dev/null
-		    { test $? = 0 } && {
-			error "A Jaro Mail ramdisk is already open"
-			return 1 }
-		    { test -z ${PARAM[2]} } && { size=10 } || { size=${PARAM[2]} }
-		    act "Creating ramdisk of ${size}MB"
-
-		    # 2048 is a megabyte here
-		    devsize=$((1024*2*$size))
-		    devname=`hdid -nomount ram://${devsize}`
-		    act "Mounting ramdisk on $devname"
-		    diskutil eraseVolume HFS+ JaroTmp `basename $devname` > /dev/null
-		    { test $? != 0 } && {
-			error "Error initializing ramdisk"
-			hdiutil detach `basename $devname`
-			return 1 }
-		    notice "Operation successful, ramdisk ready on /Volume/JaroTmp"
-		    TMPRAM=1
-		    ;;
-		close)
-		    devname=`mount | awk '/JaroTmp/ {print $1}'`
-		    { test "$devname" = "" } && {
-			error "No ramdisk seems to be open"
-			return 1 }
-		    act "Unmounting ramdisk: $devname"
-		    diskutil unmount /Volumes/JaroTmp > /dev/null
-		    hdiutil detach `basename $devname` > /dev/null
-		    notice "Ramdisk succesfully detached"
-		    TMPRAM=0
-		    ;;
-	    esac
-	    ;;
-    esac
-}
-######################
-
-usage() {
-    cat <<EOF
-Jaro Mail $VERSION - your humble and faithful electronic postman
-
- Copyright (C) 2010-2012 Dyne.org Foundation, License GNU GPL v3+
- This is free software: you are free to change and redistribute it
- The latest Jaro Mail sourcecode is on <http://jaromail.dyne.org>
-
-Synopsis: jaro [options] [command] [command-options]
-
-Main commands:
-
- fetch  download unread emails from [account]
- send   send all mails queued in the outbox
- peek   look into the [account] mailbox without downloading
-
-Options:
-
- -a     use a particular account instead of default (keyword)
- -l     whitelist or blacklist to use with learn/query/forget
- -h     print this help
- -v     version information for this tool
- -q     run quietly without printing informations
- -n     dry run, show operations without executing them
- -D     print debugging information at runtime
-
-Maintenance commands:
-
- passwd   reset password for the account in use
-
- update   refresh configurations
- queue    add a mail into outbox
-
- addr     look for a matching address in list
- query    read mail from stdin, return 0 if known to list
- learn    learn addresses from mails piped in stdin
- forget   remove addresses found in mails piped in stdin
-
- backup   move all mails older than N days from a maildir to another
- rmdupes  remove all duplicate mails into a maildir
- merge    merge a maildir into another, removing all duplicates
-
-Please report bugs on <http://bugs.dyne.org>.
-EOF
-}
 
 # TODO: For more informations on Jaro Mail read the manual: man jaro
 
@@ -1879,8 +234,7 @@ main()
     subcommands_opts[peek]=""
     subcommands_opts[update]=""
 
-    subcommands_opts[stats]=""
-    subcommands_opts[timecloud]=""
+    subcommands_opts[stat]=""
 
     subcommands_opts[search]=""
     subcommands_opts[addr]=""
@@ -1994,8 +348,7 @@ main()
 
 	search)  CLEANEXIT=0; search ${PARAM} ;;
 
-	stats)   CLEANEXIT=0; stats ;;
-	timecloud)   CLEANEXIT=0; timecloud ;;
+	stat)   CLEANEXIT=0; stats ${PARAM} ;;
 
 	addr)    CLEANEXIT=0; address ${PARAM} ;;
 	query)   CLEANEXIT=0; query ${PARAM} ;;
diff --git a/src/zlibs/accounts b/src/zlibs/accounts
@@ -0,0 +1,356 @@
+#!/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) 2010-2012 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.
+
+switch_identity() {
+    if [ "$name" != "" ]; then
+	act "switch to identity: $name <$login>"
+	rm -f $MUTTDIR/identity
+	cat <<EOF > $MUTTDIR/identity
+set hostname = $host
+set realname = "$name"
+set from = "$name <$login>"
+EOF
+    else
+	error "No identity found, left blank."
+	touch $MUTTDIR/identity
+    fi
+}
+
+
+
+# arg 1 : account type, es: imap or smtp
+# -a defines which account name other than 'default'
+read_account() {
+    typeset -al all
+    type=$1
+    # find the file
+    func "read_account looking for \"$account\""
+    for a in `find $WORKDIR/Accounts -name "$type.$account*"`; do
+	func "found account: $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):"
+	for a in `find $WORKDIR/Accounts -type f | grep -v README | sed 's/.txt//'`; 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
+	    echo " (`basename $a | cut -d. -f1`)"
+	done
+	return 1
+    elif [ ${#all} != 1 ]; then
+	error "Too many $type accounts named $account"
+	act -n ""
+	for i in ${=all}; do echo -n "`basename ${i}` "; done
+	echo; 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)
+
+	    lock $acct
+	    ttmp=`cat $acct | 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 }
+    /^login/ { printf "login=\"%s\";", $2 }
+    /^transport/ { printf "transport=\"%s\";", $2 }
+    /^port/ { printf "port=\"%s\";", $2 }
+    /^password/ { printf "password=\"%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
+
+	    eval "$ttmp"
+	    # check required fields
+	    { test -z $host }  && { error "Field missing in account $acct: host"; return 1 }
+
+	    # 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
+
+	    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
+}
+
+
+update() {
+    notice "Updating all configurations and filters"
+    # this function should:
+    # parse all filters
+    # generate procmailrc
+    # generate muttrc
+    # backup what's too old in the maildirs
+    # ...
+
+    # debug configuration
+    func "MAILDIRS:    $MAILDIRS"
+    func "WORKDIR:     $WORKDIR"
+    func "MUTTDIR:     $MUTTDIR"
+    func "PROCMAILDIR: $PROCMAILDIR"
+
+    # make sure maildirs where to put mails exist
+    ${=mkdir} $MAILDIRS
+    maildirmake $MAILDIRS/known
+    maildirmake $MAILDIRS/sent
+    maildirmake $MAILDIRS/priv
+    maildirmake $MAILDIRS/postponed
+    maildirmake $MAILDIRS/unsorted
+    maildirmake $MAILDIRS/ml.unsorted
+    ${=mkdir} $MAILDIRS/outbox
+
+    ######
+    # MUTT
+    ${=mkdir} $MUTTDIR
+    rm -f $MUTTDIR/rc
+    cat<<EOF > $MUTTDIR/rc
+# mutt config generated by Jaro Mail
+unset use_domain
+set folder = $MAILDIRS
+set spoolfile = $MAILDIRS/known/
+set record = $MAILDIRS/sent/
+set postponed= $MAILDIRS/postponed/
+set tmpdir = $WORKDIR/cache
+set sendmail = "$WORKDIR/bin/jaro queue"
+set header_cache= $WORKDIR/cache
+set maildir_header_cache_verify=no
+set editor = "$WORKDIR/bin/jaro edit"
+set mailcap_path = "$WORKDIR/.mutt/mailcap:$WORKDIR/mailcap:$HOME/.mailcap:/etc/mailcap:/etc/mailcap:/usr/etc/mailcap:/usr/local/etc/mailcap"
+
+# Little Brother Database
+set query_command = "$WORKDIR/bin/jaro -q addr '%s'"
+macro index,pager a "<pipe-message>$WORKDIR/bin/jaro -l whitelist -q learn from:<enter>" "add to whitelist everyone in the message"
+macro index,pager A "<pipe-message>$WORKDIR/bin/jaro -l whitelist -q forget from:<enter>" "remove sender from whitelist
+macro index,pager z "<pipe-message>$WORKDIR/bin/jaro -l blacklist -q learn from:<enter>" "add sender to blacklist"
+macro index,pager Z "<pipe-message>$WORKDIR/bin/jaro -l blacklist -q forget from:<enter>" "remove sender from blacklist
+
+# mailboxes in order of priority
+source $MUTTDIR/mboxes
+
+# specific configuration files
+source $MUTTDIR/crypto
+source $MUTTDIR/general
+source $MUTTDIR/formats
+source $MUTTDIR/keybindings
+source $MUTTDIR/identity
+source $MUTTDIR/password
+source $MUTTDIR/colors
+source $WORKDIR/Mutt.txt
+## end of Jaro Mail generated muttrc
+####################################
+
+EOF
+
+   # making sure we have the minimum mailcap necessary
+wwwtext=w3m
+{ which lynx > /dev/null } && { wwwtext=lynx }
+{ which elinks > /dev/null } && { wwwtext=elinks }
+cat <<EOF > $MUTTDIR/mailcap
+text/html; ${wwwtext} -dump %s; nametemplate=%s.html; copiousoutput
+application/*; a=$WORKDIR/tmp/attach-$RANDOM$RANDOM && cp %s \$a && jaro open \$a
+text/plain; iconv -f iso-8859-1 -t utf-8; test=charset=%{charset} \
+  && test x`echo \"$charset\" | tr a-z A-Z` = xISO-8859-1; copiousoutput
+text/plain; cat %s
+EOF
+
+    # this one is empty and sources files in temp when necessary
+    touch $MUTTDIR/password
+
+    # just the header, will be completed later in procmail loop
+    rm -f $MUTTDIR/mboxes
+    echo -n "mailboxes +priv" > $MUTTDIR/mboxes
+
+    # update identity with default
+    read_account imap
+
+    switch_identity
+
+    ##########
+    # PROCMAIL
+    act "generating procmail filters"
+    ${=mkdir} $PROCMAILDIR
+    rm -f $PROCMAILDIR/rc
+    touch $PROCMAILDIR/rc
+    cat<<EOF >> $PROCMAILDIR/rc
+# procmail configuration file generated by Jaro Mail
+MAILDIR=$MAILDIRS
+JARO=$WORKDIR/bin/jaro
+DEFAULT=unsorted/
+VERBOSE=off
+LOGFILE=$WORKDIR/log/procmail.log
+SHELL       = /bin/sh       # VERY IMPORTANT
+UMASK       = 007           # James Bond :-)
+LINEBUF     = 8192          # avoid procmail choke
+
+#  Using Procmail Module Library http://sf.net/projects/pm-lib
+PMSRC  = $PROCMAILDIR
+#  Load the central initial startup code.
+INCLUDERC = \$PMSRC/pm-javar.rc
+PF_DEST = ""			# clear these vars
+PF_FROM = ""
+PF_RECURSE = yes
+
+# blacklist filters
+:0 w:
+* ? \$JARO -l blacklist -q query from:
+zz.blacklist/
+
+# filters generated from Filters.txt
+:0
+* ? test \$PMSRC/pf-chkto.rc
+{
+EOF
+
+    #######
+    echo "# filters generated from Filters.txt" >> $PROCMAILDIR/rc
+
+    for f in `cat $WORKDIR/Filters.txt | awk '/^#/ {next} /^./ { print $1 ";" $2 ";" $3 ";" $4 }'`; do
+	header="${f[(ws:;:)1]}"
+	address="${f[(ws:;:)2]}"
+	action="${f[(ws:;:)3]}"
+	destination="${f[(ws:;:)4]}"
+	case $header in
+	    to)
+		print "ADDR=${address}\tDEST=${destination}/\tINCLUDERC=\$PMSRC/pf-chkto.rc" \
+		    >> $PROCMAILDIR/rc
+		func "messages to <${address}> in folder: ${destination}"
+		maildirmake $MAILDIRS/$destination
+		;;
+	    from)
+		print "ADDR=${address}\tDEST=${destination}/\tINCLUDERC=\$PMSRC/pf-check.rc" \
+		    >> $PROCMAILDIR/rc
+		func "messages from <${address}> in folder: {$destination}"
+		maildirmake $MAILDIRS/$destination
+		;;
+	    *)
+		error "unsupported filter: $header (skipped)"
+		;;
+	esac
+	# MUTT (generate mailboxes priority this parser)
+	echo  " \\" >> $MUTTDIR/mboxes
+	echo -n " +${destination} " >> $MUTTDIR/mboxes
+    done
+
+    # if the sender is known (ldbd recognizes it) then put mail in high priority 'known'
+    cat <<EOF >> $PROCMAILDIR/rc
+}
+
+:0
+* PF_DEST ?? .
+* ? test \$PMSRC/pf-save.rc
+{ INCLUDERC=\$PMSRC/pf-save.rc }
+
+
+# whitelisting filters
+:0 w:
+* ? \$JARO -l whitelist -q query from:
+known/
+
+
+EOF
+
+    #######
+cat <<EOF >> $PROCMAILDIR/rc
+# filters generated from Accounts
+:0
+* ? test \$PMSRC/pf-chkto.rc
+{
+EOF
+    for f in `cat $WORKDIR/Accounts/* | awk '/^email/ { print $2 }'`; do
+	echo "ADDR=${f}\tDEST=priv/\tINCLUDERC=\$PMSRC/pf-chkto.rc"  >> $PROCMAILDIR/rc
+	func "private account: <${f}>"
+    done
+
+    cat <<EOF >> $PROCMAILDIR/rc
+}
+
+:0
+* PF_DEST ?? .
+* ? test \$PMSRC/pf-save.rc
+{ INCLUDERC=\$PMSRC/pf-save.rc }
+
+# if its an unknown mailinglist, save it into ml.unsorted
+:0
+* ^(List-Id|X-(Mailing-)?List):
+ml.unsorted/
+
+EOF
+
+    # MUTT (generate mailboxes priority this parser)
+    echo " \\" >> $MUTTDIR/mboxes
+    echo " +ml.unsorted +unsorted" >> $MUTTDIR/mboxes
+
+    uniq $MUTTDIR/mboxes > $TMPDIR/mboxes
+    mv $TMPDIR/mboxes $MUTTDIR/mboxes
+    rm -f $TMPDIR/mboxes
+
+    # conclude
+    cat <<EOF >> $PROCMAILDIR/rc
+
+# if got here, go to unsorted
+:0:
+\$DEFAULT
+
+#
+# End of generated procmail rc
+#
+EOF
+    return 0
+} # end of update()
+
diff --git a/src/zlibs/addressbook b/src/zlibs/addressbook
@@ -0,0 +1,153 @@
+#!/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) 2010-2012 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.
+
+
+
+###################
+# Jaro Brother DB
+create_addressbook() {
+    { test -r $WORKDIR/addressbook } && {
+	error "Addressbook already exists: $WORKDIR/addressbook"
+	return 1
+    }
+    cat <<EOF | ${SQL} -batch $WORKDIR/addressbook
+CREATE TABLE whitelist
+(
+  email   text collate nocase,
+  name    text collate nocase unique,
+  hits    int
+);
+CREATE TABLE blacklist
+(
+  email   text collate nocase,
+  name    text collate nocase unique,
+  hits    int
+);
+EOF
+    { test $? != 0 } && {
+	error "Error creating addressbook database."
+	return 1 }
+    return 0
+}
+insert_address() {
+    func "insert address: $1, $2"
+    cat <<EOF | ${SQL} -batch $WORKDIR/addressbook 2> /dev/null
+INSERT INTO $list (email, name)
+VALUES ("${1}", "${2}");
+EOF
+    { test $? != 0 } && {
+	func "address already present in $list" }
+    return $0
+}
+remove_address() {
+    func "remove address <$1> from $list"
+    cat <<EOF | ${SQL} -batch $WORKDIR/addressbook
+DELETE FROM $list
+WHERE email LIKE "${1}";
+EOF
+    { test $? != 0 } && {
+	func "address not found or error occurred" }
+}
+search_name() {
+	cat <<EOF | ${SQL} -column -batch $WORKDIR/addressbook
+.width 64 128
+SELECT * FROM $list
+WHERE name LIKE "%${1}%";
+EOF
+}
+search_email() {
+	cat <<EOF | ${SQL} -column -batch $WORKDIR/addressbook
+SELECT rowid FROM $list
+WHERE email IS "${1}";
+EOF
+return $?
+}
+address() {
+    { test ! -r ${WORKDIR}/addressbook } && { create_addressbook }
+    act "Searching for \"${PARAM[1]}\" in $list"
+    { test "$OS" = "MAC" } && {
+	matches=`$WORKDIR/bin/ABQuery ${PARAM[1]}`
+    }
+    matches="${matches}\n`search_name ${PARAM[1]}`"
+
+    # mutt query requires something like this
+    echo "jaro: `echo $matches | wc -l` matches"
+    echo "$matches" | awk '
+{ printf "%s\t", $1
+  for(i=2;i<=NF;i++) {
+    sub("<","",$i)
+    sub(">","",$i)
+    if($i!=$1) printf "%s ", $i
+  }
+  printf "\n" }'
+}
+query() {
+    { test ! -r ${WORKDIR}/addressbook } && { create_addressbook }
+    if [ -z ${PARAM[1]} ]; then
+	email="`${WORKDIR}/bin/fetchaddr -a| awk '{print $1}'`"
+    else
+	email="`${WORKDIR}/bin/fetchaddr -x ${PARAM[1]} -a| awk '{print $1}'`"
+    fi
+    exitcode=1
+    lookup="`search_email ${email}`"
+    { test "$lookup" != "" } && { exitcode=0 }
+    act "Email <$email> found in $list with id $lookup"
+}
+
+learn() {
+    { test ! -r ${WORKDIR}/addressbook } && { create_addressbook }
+    tmp=$TMPDIR/learn.$datestamp.$RANDOM
+    from="`tee $tmp | formail -xFrom: | sed -e 's/\"//g'`"
+    if [ -z ${PARAM[1]} ]; then
+	email="`cat $tmp | ${WORKDIR}/bin/fetchaddr| awk '{print $1}'`"
+    else
+	email="`cat $tmp | ${WORKDIR}/bin/fetchaddr -x ${PARAM[1]}| awk '{print $1}'`"
+    fi
+    ${=rm} $tmp &
+    insert_address "$email" "$from"
+}
+
+forget() {
+    { test ! -r ${WORKDIR}/addressbook } && { create_addressbook }
+    act "Expecting mail from stdin pipe"
+    if [ -z ${PARAM[1]} ]; then
+	email="`${WORKDIR}/bin/fetchaddr| awk '{print $1}'`"
+    else
+	email="`${WORKDIR}/bin/fetchaddr -x ${PARAM[1]}| awk '{print $1}'`"
+    fi
+    remove_address "${email}"
+}
+list_addresses() {
+    { test ${PARAM[1]} } && { list=${PARAM[1]} }
+    act "Listing all contents for $list"
+    cat <<EOF | ${SQL} -column -header -batch $WORKDIR/addressbook
+.width 32 40
+SELECT * FROM $list;
+EOF
+
+    echo ".dump" | ${SQL} -batch $WORKDIR/addressbook \
+	| bzip2 > $WORKDIR/addressbook.bz2
+    notice "Backup of all addressbook created"
+    act -n ""; ls -lh $WORKDIR/addressbook.bz2
+}
+
+###################
diff --git a/src/zlibs/cmdline b/src/zlibs/cmdline
@@ -0,0 +1,111 @@
+#!/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) 2010-2012 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.
+
+check_bin() {
+    # check for required programs
+    for req in pinentry fetchmail procmail mutt; do
+	which $req >/dev/null
+	{ test $? != 0 } && {
+	    error "Cannot find $req. Please install it."
+	    return 1
+	}
+    done
+
+    # which wipe command to use
+    which wipe > /dev/null
+    { test $? = 0 } && {
+	rm="wipe -f -s -q -R /dev/urandom"; return 0 }
+    which srm > /dev/null
+    { test $? = 0 } && {
+	rm="srm -m"; return 0 }
+    rm="rm -f"
+    return 0
+}
+
+
+option_is_set() {
+    #First argument, the option (something like "-s")
+    #Second (optional) argument: if it's "out", command will print it out 'set'/'unset'
+    #                       This is useful for if conditions
+    #Return 0 if is set, 1 otherwise
+    [[ -n ${(k)opts[$1]} ]];
+    r=$?
+    if [[ $2 == out ]]; then
+	if [[ $r == 0 ]]; then
+	    echo 'set'
+	else
+	    echo 'unset'
+	fi
+    fi
+    return $r;
+}
+option_value() {
+    #First argument, the option (something like "-s")
+    <<< ${opts[$1]}
+}
+
+
+usage() {
+    cat <<EOF
+Jaro Mail $VERSION - your humble and faithful electronic postman
+
+ Copyright (C) 2010-2012 Dyne.org Foundation, License GNU GPL v3+
+ This is free software: you are free to change and redistribute it
+ The latest Jaro Mail sourcecode is on <http://jaromail.dyne.org>
+
+Synopsis: jaro [options] [command] [command-options]
+
+Main commands:
+
+ fetch  download unread emails from [account]
+ send   send all mails queued in the outbox
+ peek   look into the [account] mailbox without downloading
+
+Options:
+
+ -a     use a particular account instead of default (keyword)
+ -l     whitelist or blacklist to use with learn/query/forget
+ -h     print this help
+ -v     version information for this tool
+ -q     run quietly without printing informations
+ -n     dry run, show operations without executing them
+ -D     print debugging information at runtime
+
+Maintenance commands:
+
+ passwd   reset password for the account in use
+
+ update   refresh configurations
+ queue    add a mail into outbox
+
+ addr     look for a matching address in list
+ query    read mail from stdin, return 0 if known to list
+ learn    learn addresses from mails piped in stdin
+ forget   remove addresses found in mails piped in stdin
+
+ backup   move all mails older than N days from a maildir to another
+ rmdupes  remove all duplicate mails into a maildir
+ merge    merge a maildir into another, removing all duplicates
+
+Please report bugs on <http://bugs.dyne.org>.
+EOF
+}
diff --git a/src/zlibs/email b/src/zlibs/email
@@ -0,0 +1,298 @@
+#!/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) 2010-2012 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.
+
+queue() {
+    local base;
+    local tmp;
+    local mailfile;
+    local msmtpfile;
+
+    # add mails to the sendout queue
+    ${=mkdir} $MAILDIRS/outbox
+    cd $MAILDIRS/outbox || return 1
+    notice "Adding mail to the outbox queue"
+    # 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`"
+    func "[$base] queue called with params: ${PARAM[@]}"
+
+    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"
+    lock $msmtpfile
+    # Write the mail to $MAILFILE
+    cat > "$mailfile"
+    unlock $msmtpfile
+    cd -
+    return 0
+}
+
+###########
+# FETCHMAIL
+fetchall() {
+    notice "Fetching all accounts in $MAILDIRS"
+    res=0
+    for i in `find $WORKDIR/Accounts -type f | grep -v README`; do
+	account=`basename $i`
+	type=`echo $account | cut -d. -f1`
+	func "fetchall: $account type $type"
+	fetch
+	if [ $? != 0 ]; then res=1; fi
+	# returns an error if just one of the accounts did
+    done
+    return $res
+}
+fetch() {
+    adir=$WORKDIR/Accounts
+
+    typeset -al all
+
+    # recursion here
+    { test $account = default } && { fetchall; return $? }
+
+    read_account "$account"
+    if [ $? != 0 ]; then
+	error "Account configuration not found, or broken. Aborting operation."
+	return 1
+    fi
+
+    if [ "$type" = "smtp" ]; then # we skip smtp in fetch
+	return 0; fi
+
+    if ! [ -r $PROCMAILDIR/rc ]; then
+	act "updating procmail configuration"
+	update
+    fi
+
+    notice "Fetching mails for $name"
+    ask_password $login $host
+    { test $? != 0 } && {
+	error "Error retrieving password for $login on $host"
+	unset password all; return 1
+    }
+
+    tmp=$TMPDIR/$host.fetch.$RANDOM
+    newlock $tmp
+    cat <<EOF > $tmp
+poll $host with proto IMAP user "$login" there with password "$password"
+EOF
+    unset password
+
+    if ! [ -z $accountopt ]; then # add option configuration
+	echo "${accountopt}" >> $tmp; fi
+
+    if ! [ -z $folders ]; then # add folder configuration
+	echo "folder ${folders}" >> $tmp; fi
+
+    cat <<EOF >> $tmp
+ssl warnings 3600 and wants mda "procmail -m $PROCMAILDIR/rc"
+EOF
+    if [ "$cert" = "check" ]; then
+	cat <<EOF >> $tmp
+sslcertck sslcertpath '$WORKDIR/certs'
+EOF
+    fi
+    cat <<EOF >> $tmp
+antispam 571 550 501 554
+EOF
+
+	# try login without doing anything
+    fetchmail -c -f $tmp
+	# examine result
+    case $? in
+	1)
+	    notice "No mails for $name"
+	    unlink $tmp
+	    return 1
+		;;
+	2)
+	    error "Invalid or unknown certificate for $host"
+	    unlink $tmp
+	    return 1
+	    ;;
+	3)
+	    error "Invalid password for user $login at $host"
+	    unlink $tmp
+	    return 1
+	    ;;
+	*) ;;
+    esac
+
+	# archive old procmail log
+    if [ -r $WORKDIR/log/procmail.log ]; then
+	newlock $WORKDIR/log/procmail-${datestamp}.log
+	cat $WORKDIR/log/procmail.log \
+	    >> $WORKDIR/log/procmail-${datestamp}.log
+	rm -f $WORKDIR/log/procmail.log
+	unlock $WORKDIR/log/procmail-${datestamp}.log
+    fi
+    act "please wait while downloading mails..."
+
+    cat $tmp | fetchmail -f $tmp
+	# TODO: substitute this with cat conf | fetchmail -f -
+	# to avoid writing the password in clear on filesystem
+
+    unlink $tmp
+
+    total=`mailstat -k $WORKDIR/log/procmail.log | tail -n1 | awk '{print $2}'`
+    briefing=`mailstat -kt $WORKDIR/log/procmail.log |awk '!/procmail/ { print "    " $2 "\t" $3 }'|sort -nr`
+    notice "$total emails fetched"
+    echo "${briefing}"
+
+    return 0
+}
+
+
+######
+# SEND
+# this function should send all mails in outbox
+send() {
+    adir=$WORKDIR/Accounts
+    typeset -al all
+
+    # list mails to send
+    mailnum=`ls ${MAILDIRS}/outbox | grep 'mail$' | wc -l`
+    mailnum=${mailnum// /} # trim whitespace
+    if [ "$mailnum" = "0" ]; then
+	act "Outbox is empty, no mails to send."
+	return 0
+    fi
+
+    read_account smtp
+    if [ $? != 0 ]; then
+	error "Account configuration not found, or broken. Aborting operation."
+	return 1
+    fi
+
+    notice "Sending out ${mailnum} mails via $name"
+
+    # defaults
+    [ -z $auth ] && auth=plain
+    [ -z $port ] && port=25
+
+
+    ask_password $login $host
+    { test $? != 0 } && {
+	error "Error retrieving password for $login on $host"
+	unset password all; return 1
+    }
+
+    tmp=$TMPDIR/$host.send.$RANDOM
+    newlock $tmp
+    cat <<EOF > $tmp
+account default
+from ${email}
+user ${login}
+host ${host}
+port ${port}
+tls on
+tls_starttls on
+tls_certcheck off
+logfile ${WORKDIR}/log/msmtp.log
+auth ${auth}
+password ${password}
+EOF
+    unset password
+
+    for mail in `find $MAILDIRS/outbox -name "*.mail"`; do
+       smtp=`echo ${mail} | sed -e 's/mail/msmtp/'`
+       lock ${smtp}
+       recipients="`cat ${smtp}`"
+       act "To: ${recipients}"
+       msmtp -C $tmp -- ${=recipients} < "${mail}"
+       if [ $? != 0 ]; then
+	   error "Error sending mail, skipped"
+	   unlock ${smtp}
+       else
+	   act "Mail sent succesfully"
+	   # whitelist those to whom we send mails
+	   cat ${mail} | $WORKDIR/bin/jaro -l whitelist -q learn to:
+	   ${=rm} ${mail} &
+	   unlink ${smtp} &
+       fi
+    done
+    unlink $tmp
+    return 0
+}
+
+######
+# PEEK
+# this function will open the MTA to the imap server without fetching mails locally
+peek() {
+    read_account imap
+    if [ $? != 0 ]; then
+	error "Account configuration not found, or broken. Aborting operation."
+	return 1
+    fi
+
+    notice "Peek into remote imap account $name"
+
+    folder=""
+    if ! [ -z ${1} ]; then
+	folder="/${1}"
+	act "opening folder ${folder}"
+    fi
+
+    switch_identity
+    case $transport in
+	ssl) act "using secure connection (SSL)"
+	    iproto="imaps" ;;
+	plain) act "using clear text connection"
+	    iproto="imap"  ;;
+    esac
+    # escape at sign in login
+    ilogin=`echo $login | sed 's/@/\\@/'`
+
+    ask_password $login $host
+    { test $? != 0 } && {
+	error "Error retrieving password for $login on $host"
+	unset password all; return 1
+    }
+    tmp=$TMPDIR/$host.peek.$RANDOM
+    newlock $tmp
+    cat <<EOF >> $tmp
+set imap_pass = "${password}"
+# set imap_peek = yes
+EOF
+    unset password
+    echo "source $tmp" >> $MUTTDIR/password
+    (sleep 1;
+	cp /dev/null $MUTTDIR/password
+	cp /dev/null $tmp
+	unlink $tmp
+    ) &
+    mutt -F $MUTTDIR/rc	-f ${iproto}://${ilogin}@${host}${folder}
+    return $?
+}
+
diff --git a/src/zlibs/helpers b/src/zlibs/helpers
@@ -0,0 +1,213 @@
+#!/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) 2010-2012 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.
+
+# start without options: auto
+# read or compose depending if there is an argument that is an email
+# or a folder to start with
+
+autostart() {
+# no argument passed. open known folder
+    { test -z ${1} } && {
+
+	{ test ! -r $MUTTDIR/rc } \
+	    && { error "Jaro Mail is not yet configured."; return 1 }
+
+	mutt -F $MUTTDIR/rc
+	return $?
+    }
+
+    # argument passed: determine if an email
+    echo "${1}" \
+	| tr 'A-Z' 'a-z' \
+	| grep '^[a-zA-Z0-9._%+-]*@[a-zA-Z0-9]*[\.[a-zA-Z0-9]*]*[a-zA-Z0-9]$' \
+	> /dev/null
+    { test $? = 0 } && {
+	notice "Composing message to: ${1}"
+	# its an email, TODO see if we have it in our addressbook
+	mutt -F $MUTTDIR/rc "${1}"
+	return 0
+    }
+    # or a path to folder
+    { test -r ${1} } && {
+	mutt -F $MUTTDIR/rc -f ${1}
+	return 0
+    }
+
+    # or the name of a folder in Jaro Mail
+    { test -r $MAILDIRS/${1} } && {
+	notice "Opening folder ${1}"
+	mutt -F $MUTTDIR/rc -f "$MAILDIRS/${1}"
+	return 0
+    }
+
+    return $?
+}
+
+
+#########
+## Editor
+# this part guesses what is the best editor already present on the system
+# of course we have a preference for AutOrg, the editor from our suite
+# however the default is nano if nothing else is choosen.
+editor() {
+    { test ${EDITOR} } && {
+	${=EDITOR} ${PARAM[1]}
+	return $? }
+    case $OS in
+	MAC) open -t ${PARAM[1]} ;;
+	GNU)
+	    { which nano > /dev/null } && {
+		nano -m -S -Q ">" -I -E -D -T 4 -U -W -c -i -k -r 72 ${PARAM[1]} }
+	    error "No editor found, please configure the EDITOR environment variable."
+	    sleep 3
+	    ;;
+    esac
+    return $?
+}
+
+##############
+## Open a File
+open_file() {
+    a=$WORKDIR/cache/attach-$RANDOM$RANDOM
+    cp ${PARAM[1]} $a
+    case $OS in
+	GNU)
+	    # TODO
+	    ;;
+	MAC)
+	    open -g $a
+	    ;;
+    esac
+}
+
+
+# opens and closes a ramdisk for temporary files
+# users can do this explicitly between session to speed up operations
+ramdisk() {
+    case $OS in
+	GNU)
+	    # TODO
+	    # not so urgent, since usually /dev/shm is mounted and writable
+	    ;;
+	MAC)
+	    case ${PARAM[1]} in
+		open)
+		    mount | grep 'JaroTmp' > /dev/null
+		    { test $? = 0 } && {
+			error "A Jaro Mail ramdisk is already open"
+			return 1 }
+		    { test -z ${PARAM[2]} } && { size=10 } || { size=${PARAM[2]} }
+		    act "Creating ramdisk of ${size}MB"
+
+		    # 2048 is a megabyte here
+		    devsize=$((1024*2*$size))
+		    devname=`hdid -nomount ram://${devsize}`
+		    act "Mounting ramdisk on $devname"
+		    diskutil eraseVolume HFS+ JaroTmp `basename $devname` > /dev/null
+		    { test $? != 0 } && {
+			error "Error initializing ramdisk"
+			hdiutil detach `basename $devname`
+			return 1 }
+		    notice "Operation successful, ramdisk ready on /Volume/JaroTmp"
+		    TMPRAM=1
+		    ;;
+		close)
+		    devname=`mount | awk '/JaroTmp/ {print $1}'`
+		    { test "$devname" = "" } && {
+			error "No ramdisk seems to be open"
+			return 1 }
+		    act "Unmounting ramdisk: $devname"
+		    diskutil unmount /Volumes/JaroTmp > /dev/null
+		    hdiutil detach `basename $devname` > /dev/null
+		    notice "Ramdisk succesfully detached"
+		    TMPRAM=0
+		    ;;
+	    esac
+	    ;;
+    esac
+}
+
+
+######
+# CERT
+# downloads and/or installs certificates
+cert() {
+    if [ -z $1 ]; then
+	error "Certificate handler called without an argument"
+	return 1;
+    fi
+
+    certificate="$1"
+
+    case $certificate in
+	gmail)
+	    cc=Equifax_Secure_Certificate_Authority
+	    if ! [ -r $WORKDIR/certs/${cc}.pem ]; then
+
+		curl -o $WORKDIR/certs/${cc}.pem \
+		    "https://www.geotrust.com/resources/root_certificates/certificates/${cc}.cer"
+		openssl x509 -in \
+		    $WORKDIR/certs/${cc}.pem -fingerprint \
+		    -subject -issuer -serial -hash -noout
+	    fi
+	    ;;
+	dyne|autistici|freaknet)
+	    cc=Autistici_Certificate_Authority
+	    if ! [ -r $WORKDIR/certs/${cc}.pem ]; then
+		curl -o $WORKDIR/certs/${cc}.pem \
+		    "http://ca.autistici.org/ca.pem"
+		openssl x509 -in \
+		    $WORKDIR/certs/${cc}.pem \
+		    -fingerprint -subject -issuer -serial -hash -noout
+	    fi
+	    ;;
+	riseup)
+	    cc=RiseupCA
+	    if ! [ -r $WORKDIR/certs/${cc}.pem ]; then
+		curl -o $WORKDIR/certs/${cc}.pem "https://help.riseup.net/assets/43052/RiseupCA.pem"
+		openssl x509 -in \
+		    $WORKDIR/certs/${cc}.pem \
+		    -fingerprint -subject -issuer -serial -hash -noout
+	    fi
+	    ;;
+	*)
+	    cc="`basename $certificate`"
+	    curl -o "$WORKDIR/certs/${cc}" "$certificate"
+	    if [ $? != 0 ]; then
+		error "Error downloading certificate: $certificate"
+		return 1
+	    fi
+	    openssl x509 -in \
+		"$WORKDIR/certs/${cc}" \
+		-fingerprint -subject -issuer -serial -hash -noout
+	    ;;
+    esac
+    act "refreshing certificates"
+    c_rehash $WORKDIR/certs > /dev/null
+    if [ $? != 0 ]; then
+	error "Error refreshing certificates in $WORKDIR/certs"
+	c_rehash $WORKDIR/certs
+    fi
+    return 0
+}
+
+######################
diff --git a/src/zlibs/locking b/src/zlibs/locking
@@ -0,0 +1,81 @@
+#!/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) 2010-2012 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.
+
+lock() {
+    func "lock: $1"
+    $WORKDIR/bin/dotlock ${1}
+    case $? in
+	1) error "Cannot lock non existing file: $1"
+	    return 1 ;;
+	3) error "Locked file in use: $1"
+	    return 3 ;;
+	5) error "Impossible to lock: $1"
+	    return 5 ;;
+	# success
+	0) echo "$$" > ${1}.pid
+	    return 0 ;;
+    esac
+}
+newlock() { # create locked
+    func "creating locked file: $1"
+    touch $1
+    chmod 600 $1
+    lock $1
+}
+unlock() {
+    func "unlock: $1"
+    lockpid="`cat ${1}.pid`"
+    { test -r ${1}.pid } && {
+	{ test "$$" != "$lockpid" } && {
+	    error "Unlock attempt by different PID: $1"
+	    error "Created by $lockpid now $$ is trying to unlock"
+	    return 1 }
+    }
+
+    $WORKDIR/bin/dotlock -u ${=@}
+    { test $? != 0 } && { error "Unable to unlock: $1"; return 1 }
+    { test -r ${1}.pid } && { rm -f ${1}.pid }
+    return 0
+}
+unlink() { # delete a file that we are locking
+    func "unlink: $1"
+    # use with care! this can permanently erase currently locked files
+    # only the locking PID should use it on its own locks
+
+    { test -r ${1}.pid } && {
+	lockpid="`cat ${1}.pid`"
+	{ test "$$" != "$lockpid" } && {
+	    error "Unlock attempt by different PID: $1"
+	    error "Created by $lockpid now $$ is trying to unlock"
+	    return 1 }
+    }
+
+    (
+	${=rm} ${1}
+	touch ${1}
+	$WORKDIR/bin/dotlock -d -f ${1}
+	{ test $? != 0 } && { error "Unable to unlink: $1"; return 1 }
+	{ test -r ${1}.pid } && { rm -f ${1}.pid }
+    ) &!
+    return 0
+}
+
diff --git a/src/zlibs/maildirs b/src/zlibs/maildirs
@@ -0,0 +1,147 @@
+#!/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) 2010-2012 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.
+
+
+# checks if its a maildir
+# returns 0 (success) if yes
+# no in all other cases
+maildircheck() {
+    { test -r $1 } || {	
+	error "Maildir not existing: $1"
+	return 1 }
+    { test -w $1 } || {
+	error "Directory not writable: $1"
+	return 1 }
+    { test -r $1/cur } \
+	&& { return 0 } # Yes is a maildir
+# shortened test to speedup
+#	&& { test -r $1/new } \
+#	&& { test -r $1/tmp } \
+    func "Not a maildir: $1"
+    return 1
+}
+
+maildirmake() {
+
+    if [ -z $1 ]; then
+	error "internal error: missing argument for maildirmake"
+	return
+    fi
+
+    if [ -r $1 ]; then
+	func "maildir exists: $1"
+	return
+    fi
+
+    ${=mkdir} ${1}/cur
+    ${=mkdir} ${1}/new
+    ${=mkdir} ${1}/tmp
+
+}
+
+
+list_maildirs() {
+    maildirs=()
+    for m in `find $MAILDIRS -type d`; do
+	maildircheck $m
+	{ test $? = 0 } && {
+	    # is a maildir
+	    { test "`find $m -type f`" != "" } && {
+		# and is not empty
+		maildirs+=(`basename $m`)
+	    }
+	}
+    done
+    return ${#maildirs}
+}
+
+rmdupes() {
+    tmp=$TMPDIR/$datestamp.rmdupes.$RANDOM
+    newlock $tmp
+    for folder in ${=PARAM}; do
+	{ test ! -r $folder } && { folder=$MAILDIRS/$folder }
+	{ test ! -r $folder } && { error "Maildir not found: $folder"; continue }
+	notice "Removing duplicates in $folder"
+	c=0
+	for i in `find ${folder} -type f`; do
+	    # 5MB should be enough ehre?
+	    formail -D 5000000  $tmp <$i \
+		&& rm $i && c=`expr $c + 1`
+	done
+    done
+    unlink $tmp
+    notice "$c duplicates found and deleted"
+}
+
+merge() {
+    src=${PARAM[1]}
+    dst=${PARAM[2]}
+    if ! [ -r ${src}/cur ]; then
+	error "No source maildir found in $src"
+	return 1
+    fi
+    if ! [ -r ${dst}/cur ]; then
+	error "No destination maildir found in $dst"
+	return 1
+    fi
+    notice "Merging maildir ${src} into ${dst}"
+    c=0
+    for i in `find ${src}/cur -type f`; do mv $i ${dst}/cur/; c=`expr $c + 1`; done
+    for i in `find ${src}/new -type f`; do mv $i ${dst}/new/; c=`expr $c + 1`; done
+    for i in `find ${src}/tmp -type f`; do mv $i ${src}/tmp/; c=`expr $c + 1`; done
+    act "$c mails succesfully moved."
+    notice "Operation completed, you can now safely remove ${src}"
+}
+
+# re-sort all maildir through the procmail filters
+# it can create duplicates, up to the user to rmdupes
+filter() {
+
+    update # update all filters
+
+	# archive old procmail log
+    if [ -r $WORKDIR/log/procmail.log ]; then
+	newlock $WORKDIR/log/procmail-${datestamp}.log
+	cat $WORKDIR/log/procmail.log \
+	    >> $WORKDIR/log/procmail-${datestamp}.log
+	rm -f $WORKDIR/log/procmail.log
+	unlock $WORKDIR/log/procmail-${datestamp}.log
+    fi
+
+    for folder in ${=PARAM}; do
+	typeset -al fall
+	{ test ! -r $folder } && { folder=$MAILDIRS/$folder }
+	{ test ! -r $folder } && { error "Maildir not found: $folder"; return 1 }
+	notice "Filtering folder $folder"
+	# first index current state
+	for m in `find $folder -type f`; do fall+=($m); done
+	# then process it, this way ignoring new mails send to same folder
+	for n in ${=fall}; do
+	    cat $n | procmail -m $PROCMAILDIR/rc
+	done
+	unset fall
+    done
+
+    total=`mailstat -k $WORKDIR/log/procmail.log | tail -n1 | awk '{print $2}'`
+    briefing=`mailstat -kt $WORKDIR/log/procmail.log |awk '!/procmail/ { print "    " $2 "\t" $3 }'|sort -nr`
+    echo "${briefing}"
+}
diff --git a/src/zlibs/password b/src/zlibs/password
@@ -0,0 +1,166 @@
+#!/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) 2010-2012 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.
+
+
+
+# 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() {
+    func "Looking for password in keyring: $name"
+    case $OS in
+	MAC)
+	    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)
+	    func "Looking for password in keyring: $name"
+	    ###################
+	    # USE GNOME KEYRING
+	    { test $GNOMEKEY = 1 } && {
+		echo "protocol=${type}\npath=jaromail/${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=`echo "protocol=${type}\npath=jaromail/${email}\nusername=${login}\nhost=${host}\n\n" | $WORKDIR/bin/jaro-gnome-keyring get`
+		fi
+		return 0
+	    }
+	    ####################
+	    # 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
+}
+
+new_password() {
+    notice "Setting a new password for $name"
+    password=`pin_entry $login $host`
+    res=0
+    case $OS in
+	MAC)
+	    if [ "$password" != "" ]; then
+		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
+		security delete-internet-password \
+		    -c JARO -a $email -s $host \
+		    -p $transport -P $port > /dev/null
+		res=$?; unset password
+		{ test $res != 0 } && {
+		    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
+		{ test $GNOMEKEY = 1 } && {
+
+		    cat <<EOF | $WORKDIR/bin/jaro-gnome-keyring store
+protocol=${type}
+path=jaromail/${email}
+username=${login}
+host=${host}
+password=${password}
+EOF
+		    { test $? != 0 } && { error "Error saving password in Gnome keyring" }
+		    return 0
+		}
+
+		return 0
+
+	    else # password is blank or aborted
+
+		# USE GNOME KEYRING
+		{ test $GNOMEKEY = 1 } && {
+
+		    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
+		}
+
+		return 1
+
+	    fi
+	    ;;
+	*)
+	    error "Unknown system, can't figure out how to handle passwords"
+	    return 1
+    esac
+}
diff --git a/src/zlibs/search b/src/zlibs/search
@@ -0,0 +1,259 @@
+#!/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) 2010-2012 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.
+
+#######################
+## Search into maildirs
+# using mairix
+search() {
+    { which mairix > /dev/null } || { return 1 }
+    id=$RANDOM
+    rc=$TMPDIR/search.conf.$id
+    typeset -al expr
+    typeset -al fold
+    # intelligent parse of args, position independent
+    # check if its a folder, if not is an expression
+    mlstr="all folders"; ml=""; c=0
+    basedir=$MAILDIRS
+    # check if the name of a maildir is among params
+    for p in ${PARAM}; do
+	c=$(( $c + 1 ))
+	func "checking param: ${p}"
+	if [ -r ${p} ]; then
+
+	    { maildircheck ${p} } && { 
+		fold+=(${p})
+		{ test ${#fold} = 1 } && {
+		    # base path is the dir of the first folder
+		    pushd `dirname ${p}`
+		    basedir=`pwd`
+		    popd }
+	    }
+	    
+        elif [ -r ${MAILDIRS}/${p} ]; then
+
+	    { maildircheck ${MAILDIRS}/${p} } && { fold+=(${MAILDIRS}/${p}) }
+
+	else # not a folder, add it to expressions array
+	    expr+=(${p})
+	fi
+    done
+
+
+    # now fold is an array of specified folders
+    # expr is an array of specified search expressions
+
+
+    # to search only one maildir then we need to index it
+    # separate from the rest of the maildirs
+    if [ ${#fold} != 0 ]; then
+	{ test ${#expr} = 0 } && {
+	    error "no search expression given for folders ${fold[@]}"
+	    return 1 }
+	# forge the folder string for mairix conf
+	folders=""; for f in ${fold}; do folders="$folders`basename $f`:"; done
+	cat <<EOF > $rc
+base=$basedir
+database=$TMPDIR/search.db.$id
+maildir=${folders}
+mfolder=$TMPDIR/search.result.$id
+mformat=maildir
+EOF
+
+	exitcode=1
+	act "Indexing ${folders}"
+	mairix -F -f $rc 2> /dev/null
+	{ test $? = 0 } && { 
+	    act "Searching for: ${expr}"
+	    found=`mairix -F -f $rc ${=expr} 2> /dev/null | awk '{ print $2}'`
+	    if [ $found != 0 ]; then
+		mutt -F $MUTTDIR/rc -R -f $TMPDIR/search.result.$id
+		notice "Found $found matches looking for '$expr' in $folders"
+		find $TMPDIR/search.result.$id
+		cat $rc
+		exitcode=0
+	    else error "No matches found."; fi
+	}
+
+
+	rm -f $rc
+	rm -f $TMPDIR/search.db.$id
+#	rm -rf $TMPDIR/search.result.$id
+	return $exitcode
+
+
+	####################################################
+    else    # no folder specified on commandline, search all
+    # make index if no params given
+	c=0
+	for i in `ls $MAILDIRS`; do
+	# is it a maildir?
+	    { maildircheck $MAILDIRS/${i} } && {
+		c=`expr $c + 1`; ml="$ml:$i" }
+	done
+    fi
+
+    cat <<EOF > $rc
+base=$MAILDIRS
+database=$WORKDIR/search.db
+maildir=${ml}
+mfolder=$TMPDIR/search.result.$id
+mformat=maildir
+EOF
+    # just index
+    { test ${#PARAM} = 0 } && {
+	act "Indexing $c maildirs for search"
+	act "please be patient..."
+	mairix -F -f $rc
+	rm -f $rc
+	exitcode=$?
+	if [ $exitcode = 0 ]; then notice "Done."
+	else error "Error, indexing aborted."; fi
+	rm -f $rc
+	return $exitcode
+    }
+    act "Searching $mlstr for: ${expr}"
+    exitcode=1
+    found=`mairix -F -f $rc ${=expr} 2> /dev/null | awk '{print $2}'`
+    if [ $CALLMUTT = 1 ]; then
+
+	if [ $found != 0 ]; then
+	    mutt -F $MUTTDIR/rc -R -f $TMPDIR/search.result.$id
+	    notice "Found $found matches looking for '$expr' in all mail folders"
+	    exitcode=0
+	else error "Nothing found matching '$expr'"; fi
+
+	rm -rf $TMPDIR/search.*.$id
+	rm -f $rc
+	return $exitcode
+
+    else ### if not calling mutt, internal use mode:
+	# print out the full path to the results maildir
+	# return number of found hits
+	echo $TMPDIR/search.result.$id
+	return $found
+    fi
+}
+
+
+backup() {
+    id=$RANDOM
+    rc=$TMPDIR/backup.conf.$id
+    typeset -al expr
+    typeset -al fold
+
+    src=""; dst=""
+    basedir=$MAILDIRS
+    # check if the name of a maildir is among params
+    # we need at least 2 maildirs, the second is the destination
+    for p in ${PARAM}; do
+	c=$(( $c + 1 ))
+
+	if [ $c = ${#PARAM} ]; then
+	    # last one is always the destination
+	    func "destination is ${p}"
+	    fold+=(${p})
+
+	elif [ -r ${p} ]; then
+
+	    { maildircheck ${p} } && {
+		func "param ${p} is a maildir"
+		fold+=(${p})
+		{ test ${#fold} = 1 } && {
+		    # base path is the dir of the first folder
+		    pushd `dirname ${p}`
+		    basedir=`pwd`
+		    popd }
+	    }
+	    
+        elif [ -r ${MAILDIRS}/${p} ]; then
+
+	    { maildircheck ${MAILDIRS}/${p} } && {
+		func "param ${p} is a jaro maildir"
+		fold+=(${MAILDIRS}/${p})
+	    }
+
+	else # not a folder, add it to expressions array
+	    func "param ${p} is an expression"
+	    expr+=(${p})
+	fi
+    done
+
+    { test ${#fold} -lt 2 } && {
+	error "Not enough folders specified for backup: minimum is 2"
+	act "When specifying more than 2, the last one is the destination"
+	return 1
+    }
+
+    dst=${fold[${#fold}]}
+    { test -r $dst } && {
+	error "Backup destination already exists: $dst"
+	return 1 }
+
+    maildirmake "${dst}"
+
+    { test ${#expr} = 0 } && {
+	error "No expression set for backup, please indicate what you want to backup"
+	act "For example: d:10y-2y (all mails older than 1 year up to 10 years ago"
+	act "Or a simple search string, all expressions can be verified using search."
+	return 1
+    }
+
+    # forge the folder string for mairix conf
+    folders=""
+    for f in ${fold}; do
+	{ test $f = $dst } || { 
+	    folders="$folders`basename $f`:" }
+    done
+
+    notice "Backup of all mails in '$folders' matching expression '$expr'"
+
+    act "Indexing folders"
+    cat <<EOF > $rc
+base=$basedir
+database=$TMPDIR/backup.db.$id
+maildir=${folders}
+mfolder=$dst
+mformat=maildir
+EOF
+    mairix -F -f $rc 2> /dev/null
+    act "Moving matches to $dst"
+    pushd `dirname $dst`; basedir=`pwd`; popd
+    rm -f $rc; cat <<EOF > $rc
+base=$basedir
+database=$TMPDIR/backup.db.$id
+maildir=${folders}
+mfolder=$dst
+mformat=maildir
+EOF
+    found=`mairix -F -f $rc -H ${expr} 2> /dev/null | awk '{print $2}'`
+    notice "$found matches found, destination folder size is `du -hs $basedir/$dst | awk '{print $1}'`"
+    # invert the order of folders to start with destination in rmdupes
+    typeset -al PARAM
+    c=$(( ${#fold} ))
+    while [ $c -gt 0 ]; do
+	func "${fold[$c]}"
+	PARAM+=(${fold[$c]})
+	c=$(( $c - 1 ))
+    done
+    QUIET=1
+    rmdupes
+}
diff --git a/src/zlibs/stats b/src/zlibs/stats
@@ -0,0 +1,208 @@
+#!/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) 2010-2012 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.
+
+
+
+
+stats() {
+    # make index of all maildirs
+    notice "Maildirs status"
+    case ${PARAM[1]} in
+
+	timecloud) timecloud ;;
+
+	weeks) weeks ;;
+
+	*) # simple stats
+	    typeset -alU ml
+	    typeset -al empty
+	    list_maildirs
+	    for i in ${maildirs}; do
+		cur=`ls $MAILDIRS/$i/cur | wc -l`
+		new=`ls $MAILDIRS/$i/cur | wc -l`
+		tot=$(( $cur + $new ))
+		ml+=("$tot\t:: $i\t ($cur/$new)")
+	    done
+	    for m in ${ml}; do
+		{ test ${m[1]} = "0" } && { 
+		    empty+=${m}
+		    continue }
+		print "    ${m}"
+	    done
+	    { test ${#empty} != 0 } && { emptystr="(${#empty} are empty)" }
+	    notice "Total maildirs: ${#ml} $emptystr"
+	    ;;
+    esac
+    return 0
+}
+
+weeks() {
+    id=$RANDOM
+    db=$TMPDIR/weekstats.db.$id
+    sql=$TMPDIR/weekstats.select.$id
+    list_maildirs
+    notice "Computing weekly statistics on ${#maildirs} maildirs"
+    func "Creating temporary database"
+    cat <<EOF | ${SQL} -batch $db
+CREATE TABLE stats
+(
+  date    text nocase,
+  ml      text collate nocase,
+  hits    int
+);
+EOF
+    { test $? != 0 } && {
+	error "Error creating temporary week stats database."
+	return 1 }
+
+    for m in ${maildirs}; do
+	for f in `find $MAILDIRS/${m} -type f`; do
+	    timestamp=`fetchdate "%Y-%U" ${f}`
+	    cat <<EOF | ${SQL} -batch $db > $sql
+SELECT * FROM stats
+WHERE ml IS "${m}" AND date IS "${timestamp}";
+EOF
+	    res=`cat $sql`
+	    if [ "$res" = "" ]; then
+		# new tag
+		cat <<EOF | ${SQL} -batch $db
+INSERT INTO stats (date, ml, hits)
+VALUES ("${timestamp}", "${m}", 1);
+EOF
+	    else
+		cat <<EOF | ${SQL} -batch $db
+UPDATE stats SET hits = hits + 1
+WHERE ml IS "${m}" AND date IS "${timestamp}";
+EOF
+	    fi
+	done
+    done
+    # gather results from the database
+    table=$WORKDIR/.stats/jaromail.html
+    ${=mkdir} $WORKDIR/.stats
+
+    cat <<EOF > $table
+<table >
+	<caption>Jaro Mail weekly statistics</caption>
+	<thead>
+		<tr>
+		<td></td>
+EOF
+    typeset -al week
+    cat <<EOF | ${SQL} -batch $db > $sql
+SELECT date FROM stats ORDER BY date;
+EOF
+    for w in `cat $sql | uniq`; do
+	week+=($w)
+	echo "<th scope=\"col\">${w}</th>" >> $table
+    done
+    cat <<EOF >> $table
+		</tr>
+	</thead>
+	<tbody>
+
+EOF
+    for m in ${maildirs}; do
+	echo -n "<tr><th scopre=\"row\">$m</th>" >> $table
+	for w in ${week}; do
+	    cat <<EOF | ${SQL} -batch $db > $sql
+SELECT hits FROM stats
+WHERE ml IS "${m}" AND date IS "${w}";
+EOF
+	echo -n "<td>`cat $sql`</td>" >> $table
+	done
+	echo "</tr>" >> $table
+    done
+
+    cat <<EOF >> $table
+	</tbody>
+</table>	
+EOF
+    cp $WORKDIR/.stats/visualize/header.html \
+	$WORKDIR/.stats/index.html
+    cat $table >> $WORKDIR/.stats/index.html
+    echo "</body>\n</html>\n" >> $WORKDIR/.stats/index.html
+}
+
+timecloud() {
+    id=$RANDOM
+    list_maildirs
+    notice "Computing timecloud statistics on ${#maildirs} maildirs"
+    func "Creating temporary database"
+    cat <<EOF | ${SQL} -batch $TMPDIR/timecloud.db.$id
+CREATE TABLE stats
+(
+  date    text collate nocase,
+  tag     text collate nocase,
+  hits    int
+);
+EOF
+    { test $? != 0 } && {
+	error "Error creating temporary timecloud database."
+	return 1 }
+    
+    for m in ${maildirs}; do
+	for f in `find $MAILDIRS/${m} -type f`; do
+	    timestamp=`fetchdate "%Y-%m-%d" ${f}`
+	    cat <<EOF | ${SQL} -batch $TMPDIR/timecloud.db.$id > $TMPDIR/timecloud.select.$id
+SELECT * FROM stats
+WHERE tag IS "${m}" AND date IS "${timestamp}";
+EOF
+	    res=`cat $TMPDIR/timecloud.select.$id`
+	    if [ "$res" = "" ]; then
+		# new tag
+		cat <<EOF | ${SQL} -batch $TMPDIR/timecloud.db.$id
+INSERT INTO stats (date, tag, hits)
+VALUES ("${timestamp}", "${m}", 1);
+EOF
+	    else
+		cat <<EOF | ${SQL} -batch $TMPDIR/timecloud.db.$id
+UPDATE stats SET hits = hits + 1
+WHERE tag IS "${m}" AND date IS "${timestamp}";
+EOF
+	    fi
+	done
+    done
+    # gather results from the database
+    cat <<EOF | ${SQL} -batch $TMPDIR/timecloud.db.$id > $TMPDIR/timecloud.results.$id
+.separator \t
+SELECT * FROM stats ORDER BY date;
+EOF
+    # format the results into a JSON string
+    awk 'BEGIN { date=0; printf "[" }
+{ if(date != $1) {
+    if( date != 0 ) printf "]],"
+    date=$1
+    printf "[\"" date "\",["
+    printf "[\"" $2 "\",\"" $3 "\"]"
+  } else {
+    printf ",[\"" $2 "\",\"" $3 "\"]"
+  }
+}
+END { print "]]];" }
+'  $TMPDIR/timecloud.results.$id > $TMPDIR/timecloud.json.$id
+    ${=mkdir} $WORKDIR/timecloud
+    cat <<EOF > $WORKDIR/timecloud/jaromail.js
+var jaromail=`cat $TMPDIR/timecloud.json.$id`
+EOF
+    rm $TMPDIR/timecloud.*.$id
+}