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
+}