commit 255f6ed7c0bb74a939aa78ccbc072efc50769e8d
Author: Jaromil <jaromil@dyne.org>
Date:   Wed, 28 Sep 2011 19:20:58 +0200
initial commit
whitepaper and skeleton script
Diffstat:
3 files changed, 488 insertions(+), 0 deletions(-)
diff --git a/doc/postino-diagram.dia b/doc/postino-diagram.dia
Binary files differ.
diff --git a/doc/postino-whitepaper.org b/doc/postino-whitepaper.org
@@ -0,0 +1,117 @@
+#+TITLE: Postino Suite - whitepaper
+#+AUTHOR: Jaromil
+#+DATE: September 2010 - 2011
+
+#+LaTeX_CLASS: article
+#+LaTeX_CLASS_OPTIONS: [a4,onecolumn,portrait]
+#+LATEX_HEADER: \usepackage[utf8x]{inputenc}
+#+LATEX_HEADER: \usepackage[T1]{fontenc}
+#+LATEX_HEADER: \usepackage{hyperref}
+#+LATEX_HEADER: \usepackage[pdftex]{graphicx}
+#+LATEX_HEADER: \usepackage{fullpage}
+#+LATEX_HEADER: \usepackage{lmodern}
+#+LATEX_HEADER: \usepackage[hang,small]{caption}
+#+LATEX_HEADER: \usepackage{float}
+
+
+* Introduction
+
+Postino is an integrated suite of interoperable tools to manage e-mail
+communication in a private and efficient way, without relying on third
+party services. Rather than reinventing the wheel, this suite reuses
+existing free and open source tools and protocols and is mainly
+targeted for GNU/Linux/BSD desktop usage.
+
+* Diagram
+
+
+#+LATEX_BEGIN
+\begin{figure}[htb!]
+  \caption{Suite diagram}
+  \centering
+    \includegraphics{postino-diagram.png}
+\end{figure}
+#+LATEX_END
+
+
+* Components
+
+** Client side
+
++ Mail User Agent[fn:mua]
++ Fetchmail
++ Procmail & Procmail-lib
++ Mini SMTP (msmtp)
++ Little Brother DB
++ Secure shell client
+
+[fn:mua] Can be any application supporting local maildir folders, our favourite is Mutt
+
+** Server side
+
++ Postfix
++ Dovecot
++ Sieve
++ Secure shell server
+
+* Workflow
+
+** Configuration
+
+*** Configure to receive mail
+
+*** Configure to send mail
+
+*** Configure to filter mail
+
+#+BEGIN_EXAMPLE
+unset POSTRULE
+POSTRULE+="from: list@kernel.org save: ml.kernel %%"
+POSTRULE+="to: jaromil@nimk.nl save: job.nimk %%"
+#+END_EXAMPLE
+
+
+
+*** Example configuration
+
+**** main.conf
+
+#+BEGIN_EXAMPLE
+# ACCEPTED EMAIL ADDRESSES
+EMAIL="jaromil.rojo@gmail.com, jaromil@dyne.org, jaromil@kyuzz.org"
+
+# LOCAL FILES
+MAIL_USER_AGENT=mutt
+MAIL_TRANSPORT_AGENT=msmtp
+MAILDIRS=$HOME/mail
+WORKDIR=$HOME/.postino
+CERTIFICATES=$HOME/.ssl/certs
+REMOTE_FILTER=/var/mail/...
+#+END_EXAMPLE
+
+**** send.conf
+
+#+BEGIN_EXAMPLE
+# name    host             port   login
+gmail     smtp.gmail.com   25     jaromil.rojo@gmail.com
+dyne      assata.dyne.org  25     username@dyne.im
+#+END_EXAMPLE
+
+**** receive.conf
+#+BEGIN_EXAMPLE
+# name    host             port   login
+gmail     imap.gmail.com   443    jaromil.rojo@gmail.com
+#+END_EXAMPLE
+
+
+
+** Operation
+
+*** Fetch mail
+
+*** Read and reply mail
+
+*** Send mail
+
+*** Sync backup and filters
+
diff --git a/src/postino b/src/postino
@@ -0,0 +1,371 @@
+#!/bin/zsh
+#
+# Postino, an humble and faithful postman
+#
+# a tool to easily and privately handle your e-mail communication
+#
+# Copyleft (C) 2010-2011 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.
+
+VERSION=0.5
+DATE=Sept/2011
+POSTINOEXEC=$0
+typeset -a OLDARGS
+for arg in ${argv}; do OLDARGS+=($arg); done
+
+#declare global variables
+QUIET=0
+DEBUG=0
+typeset -A global_opts
+typeset -A opts
+
+# parse configuration
+source $HOME/.postinorc
+mkdir -p $WORKDIR/tmp
+
+autoload colors; colors
+
+# standard output message routines
+# it's always useful to wrap them, in case we change behaviour later
+notice() { if [[ $QUIET == 0 ]]; then print "$fg_bold[green][*]$fg_no_bold[white] $1" >&2; fi }
+error()  { if [[ $QUIET == 0 ]]; then print "$fg[red][!]$fg[white] $1" >&2; fi }
+func()   { if [[ $DEBUG == 1 ]]; then print "$fg[blue][D]$fg[white] $1" >&2; fi }
+act()    {
+    if [[ $QUIET == 0 ]]; then
+	if [ "$1" = "-n" ]; then
+	    print -n "$fg_bold[white] . $fg_no_bold[white] $2" >&2;
+	else
+	    print "$fg_bold[white] . $fg_no_bold[white] $1" >&2;
+	fi
+    fi
+}
+
+# we use pinentry
+# comes from gpg project and is secure
+# it also conveniently uses the right toolkit
+ask_password() {
+
+    # pinentry has no custom icon setting
+    # so we need to temporary modify the gtk theme
+    if [ -r /usr/local/share/themes/tomb/gtk-2.0-key/gtkrc ]; then
+	GTK2_RC=/usr/local/share/themes/tomb/gtk-2.0-key/gtkrc
+    elif [ -r /usr/share/themes/tomb/gtk-2.0-key/gtkrc ]; then
+	GTK2_RC=/usr/share/themes/tomb/gtk-2.0-key/gtkrc
+    fi
+
+    cat <<EOF | GTK2_RC_FILES=${GTK2_RC} pinentry 2>/dev/null | awk '/^D / { sub(/^D /, ""); print }'
+OPTION ttyname=$TTY
+OPTION lc-ctype=$LANG
+SETTITLE Insert tomb password
+SETDESC Open tomb: $1
+SETPROMPT Password:
+GETPIN
+EOF
+
+}
+
+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]}
+}
+
+######
+# SMTP
+send() {
+    # this function should send all mails in queue
+ 
+    local -aU smtp_set #behave like a set; that is, an array with unique elements
+    smtp_set=`awk '!/^#/ { print $1 ";" $2 ";" $3 ";" $4 }' $WORKDIR/send.conf`
+    for s in ${(f)smtp_set}; do
+	sname="${s[(ws:;:)1]}"
+	shost="${s[(ws:;:)2]}"
+	sport="${s[(ws:;:)3]}"
+	slogin="${s[(ws:;:)4]}"
+	func "SMTP: $sname $slogin $shost:$sport"
+    done
+    error "TODO"
+    return 0
+}
+
+######
+# IMAP
+peek() {
+    # this function will open the MTA to the imap server without fetching mails locally
+    local -aU imap_set #behave like a set; that is, an array with unique elements
+    imap_set=`awk '!/^#/ { print $1 ";" $2 ";" $3 ";" $4 }' $WORKDIR/receive.conf`
+    for i in ${(f)imap_set}; do
+	iname="${i[(ws:;:)1]}"
+	# look for selection, not default
+	if [ $1 ] && [ "$1" != "$iname" ]; then continue; fi
+	ihost="${i[(ws:;:)2]}"
+	iport="${i[(ws:;:)3]}"
+	ilogin="${i[(ws:;:)4]}"
+	func "IMAP: $iname $ilogin $ihost:$iport"
+	if ! [ $1 ]; then break; fi # take first as default
+    done
+    # escape at sign in login
+    escilogin=`echo $ilogin | sed 's/@/\\@/'`
+    print "mutt -f imaps://${escilogin}@${ihost}:$iport"
+    mutt -f imaps://`echo $ilogin | sed 's/@/\\@/'`@${ihost}:$iport
+    return 0
+}
+
+sync() {
+    # this function should:
+    # parse all filters
+    # generate procmailrc
+    # generate muttrc
+    # backup what's too old in the maildirs
+    # ...
+    
+    # debug configuration
+    func "WORKDIR: $WORKDIR"
+    func "MAILDIRS: $MAILDIRS"
+
+    ######
+    # MUTT
+    mkdir -p $WORKDIR/mutt
+    touch $WORKDIR/mutt/rc
+    cat<<EOF >> $WORKDIR/mutt/rc
+# mutt config generated by postino
+unset use_domain
+set hostname = "dyne.org"
+set realname = "jaromil"
+set folder = ~/$MAILDIRS
+set spoolfile = ~/$MAILDIRS/known/
+set record = ~/$MAILDIRS/sent/
+set postponed=~/$MAILDIRS/postponed/
+set tmpdir = $WORKDIR/tmp
+set query_command = "postino query '%s'"
+set header_cache=~/$WORKDIR/mutt/cache
+set maildir_header_cache_verify=no
+# mailboxes in order of priority
+source $WORKDIR/mutt/mboxes
+## end of postino muttrc
+EOF
+
+    # just the header, will be completed later in procmail loop
+    rm -f $WORKDIR/mutt/mboxes
+    echo -n "mailboxes +priv" > $WORKDIR/mutt/mboxes
+
+    ##########
+    # PROCMAIL
+    mkdir -p $WORKDIR/procmail
+    rm -f $WORKDIR/procmail/rc
+    touch $WORKDIR/procmail/rc
+    cat<<EOF >> $WORKDIR/procmail/rc
+# procmail configuration file generated by postino
+MAILDIR=$MAILDIRS
+DEFAULT=unsorted/
+VERBOSE=off
+LOGFILE=$WORKDIR/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  = /usr/share/procmail-lib
+#  Load the central initial startup code.
+INCLUDERC = $PMSRC/pm-javar.rc
+PF_DEST = ""			# clear these vars
+PF_FROM = ""
+# don't save multiple copies
+PF_RECURSE = yes
+:0
+* ? test pf-chkto.rc
+{
+# filters generated from postino filters.conf
+EOF
+    for f in `cat $WORKDIR/filters.conf | 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" \
+		    >> $WORKDIR/procmail/rc
+		;;
+	    from)
+		print "ADDR=${address}\tDEST=${destination}\tINCLUDERC=$PMSRC/pf-check.rc" \
+		    >> $WORKDIR/procmail/rc
+		;;
+	    *)
+		error "unsupported in filters.conf: $header (skipped)"
+		;;
+	esac
+	# MUTT (generate mailboxes priority this parser)
+	echo  " \\" >> $WORKDIR/mutt/mboxes
+	echo -n " +${destination} " >> $WORKDIR/mutt/mboxes
+    done
+
+    uniq $WORKDIR/mutt/mboxes > $WORKDIR/tmp/mboxes
+    mv $WORKDIR/tmp/mboxes $WORKDIR/mutt/mboxes
+    echo " \\" >> $WORKDIR/mutt/mboxes
+    echo " +unsorted" >> $WORKDIR/mutt/mboxes
+
+    cat <<EOF >> $WORKDIR/procmail/rc
+}
+
+# save the mails
+:0
+* PF_DEST ?? .
+* ? test $PMSRC/pf-save.rc
+{ INCLUDERC=$PMSRC/pf-save.rc }
+
+# if the sender is known (ldbd recognizes it) then put mail in high priority 'known'
+:0 w:
+* ? formail -x"From:" | head -n1 | tr 'A-Z' 'a-z' | sed 's/.*\W\([0-9a-z_.-]\+@[0-9a-z_.-]\+\).*/\1/' | xargs lbdbq
+known/
+
+# if the destination is known, put it in private folder
+:0
+* ? test $PMSRC/pf-chkto.rc
+{
+  ADDR="(jaromil@dyne.org)"	DEST=priv/	INCLUDERC=$PMSRC/pf-chkto.rc
+  ADDR="(jaromil@nimk.nl)"	DEST=priv/	INCLUDERC=$PMSRC/pf-chkto.rc
+  ADDR="(jaromil@kyuzz.org)"	DEST=priv/	INCLUDERC=$PMSRC/pf-chkto.rc
+  ADDR="(jaromil@enemy.org)"	DEST=priv/	INCLUDERC=$PMSRC/pf-chkto.rc
+  ADDR="(j@rastasoft.org)"	DEST=priv/	INCLUDERC=$PMSRC/pf-chkto.rc
+  ADDR="(denis@roio.net)"	DEST=priv/	INCLUDERC=$PMSRC/pf-chkto.rc
+}
+
+# if got here, go to unsorted
+
+# save the mails
+:0
+* PF_DEST ?? .
+* ? test $PMSRC/pf-save.rc
+{ INCLUDERC=$PMSRC/pf-save.rc }
+
+EOF
+    return 0
+}
+
+
+main()
+    {
+    local -A subcommands_opts
+    ### Options configuration
+    #Hi, dear developer! Are you trying to add a new subcommand, or to add some options?
+    #Well, keep in mind that:
+    # 1. An option CAN'T have differente meanings/behaviour in different subcommands.
+    #    For example, "-s" means "size" and accept an argument. If you are tempted to add
+    #    an option "-s" (that means, for example "silent", and doesn't accept an argument)
+    #              DON'T DO IT!
+    #     There are two reasons for that:
+    #       I. usability; user expect that "-s" is "size
+    #       II. Option parsing WILL EXPLODE if you do this kind of bad things
+    #               (it will say "option defined more than once, and he's right)
+    main_opts=(q -quiet=q D -debug=D h -help=h v -version=v n -dry-run=n)
+    subcommands_opts[__default]=""
+    subcommands_opts[fetch]="k -keep=k"
+    subcommands_opts[send]=""
+    subcommands_opts[peek]=""    
+    subcommands_opts[sync]=""
+    subcommands_opts[conf]=""
+#    subcommands_opts[mount]=${subcommands_opts[open]}
+#    subcommands_opts[create]="s: -size=s -ignore-swap k: -key=k"
+    ### Detect subcommand
+    local -aU every_opts #every_opts behave like a set; that is, an array with unique elements
+    for optspec in $subcommands_opts$main_opts; do
+        for opt in ${=optspec}; do
+            every_opts+=${opt}
+        done
+    done
+    local -a oldstar
+    oldstar=($argv)
+    zparseopts -M -E -D -Adiscardme ${every_opts}
+    unset discardme
+    subcommand=$1
+    if [[ -z $subcommand ]]; then
+        subcommand="__default"
+    fi
+    if [[ -z ${(k)subcommands_opts[$subcommand]} ]]; then #there's no such subcommand
+        error "Subcommand '$subcommand' doesn't exist"
+        exit 127
+    fi
+    argv=(${oldstar})
+    unset oldstar
+
+    ### Parsing global + command-specific options
+    # zsh magic: ${=string} will split to multiple arguments when spaces occur
+    set -A cmd_opts ${main_opts} ${=subcommands_opts[$subcommand]}
+    if [[ -n $cmd_opts ]]; then #if there is no option, we don't need parsing
+        zparseopts -M -E -D -Aopts ${cmd_opts}
+	if [[ $? != 0 ]]; then
+	    error "Some error occurred during option processing."
+	    exit 127
+	fi
+    fi
+  #build PARAM (array of arguments) and check if there are unrecognized options
+    ok=0
+    PARAM=()
+    for arg in $*; do
+	if [[ $arg == '--' || $arg == '-' ]]; then
+	    ok=1
+	    continue #it shouldnt be appended to PARAM
+	elif [[ $arg[1] == '-'  ]]; then
+	    if [[ $ok == 0 ]]; then
+		error "unrecognized option $arg"
+		exit 127
+	    fi
+	fi
+	PARAM+=$arg
+    done
+  #first parameter actually is the subcommand: delete it and shift
+    if [[ $subcommand != '__default' ]]; then
+	PARAM[1]=()
+	shift
+    fi
+  ### End parsing command-specific options
+
+    if option_is_set -v; then act "Postino - $VERSION"; fi
+    if option_is_set -h; then error "TODO usage here"; fi
+    if option_is_set -q; then QUIET=1; fi
+    if option_is_set -D; then func "Debug messages ON"; DEBUG=1; fi
+        
+    case "$subcommand" in
+	fetch) ;;
+	send) ;;
+	peek) peek ;;
+	sync) sync ;;
+	conf) ;;
+	__default) ;;
+	*) error "command \"$subcommand\" not recognized"
+	    act "try -h for help"
+	    return 1
+	    ;;
+    esac
+    return 0
+}
+
+main $@