diff options
-rwxr-xr-x | firma | 215 |
1 files changed, 163 insertions, 52 deletions
@@ -1,6 +1,6 @@ #!/bin/bash # -# firma v0.3: encrypted mailing list manager +# firma: encrypted mailing list manager # feedback: rhatto@riseup.net luis@riseup.net | GPL # # list configuration is passed thru the config file, @@ -15,7 +15,7 @@ # FIRMA_LIST_PATH="/usr/local/etc/lists" -VERSION="0.3-cvs" +VERSION="0.3" # todo: # errase all vars before quit the game @@ -24,23 +24,35 @@ VERSION="0.3-cvs" # umask .... function usage { - echo "usage: `basename $0` <option> <list-name>" - echo "-c: create a new list" - echo "-p: process a message" - echo "-r: admin and user requests (mail only)" - echo "-a: admin commands" + echo "usage: $(basename $0) OPTION [LIST-NAME]" + echo " -a: admin commands" + echo " -c: create a new list" + echo " -h: display this help and exit" + echo " -p: process a message" + echo " -r: admin and user requests (mail only)" + echo " -v: output version information and exit" } +function version { + echo "firma $VERSION" + echo "Copyright (C) 2005 A Firma, Inc." + echo "This program comes with ABSOLUTELY NO WARRANTY." + echo "This is free software, and you are welcome to redistribute it" + echo "under certain conditions. See the GNU General Public License" + echo "for more details." +} + + function check_config { # check configuration file parameters if [ ! -f $GPG -o ! -x $GPG ]; then - echo -e "\n$1: GPG binary ($GPG) could not be found.\n" + echo "$CONFIG_FILE: GPG binary ($GPG) could not be found." exit 1 elif [ ! -f $MAIL -o ! -x $MAIL ]; then - echo -e "\n$1: Mail program ($MAIL) could not be found.\n" + echo "$CONFIG_FILE: Mail program ($MAIL) could not be found." exit 1 elif [ ! -d $GPGDIR -o ! -f $GPGDIR/pubring.gpg -o ! -f $GPGDIR/secring.gpg ]; then - echo -e "\n$1: GPG home directory ($GPGDIR) or the GPG keyrings could not be found.\n" + echo "$CONFIG_FILE: GPG home directory ($GPGDIR) or the GPG keyrings could not be found." exit 1 elif [ -z "$(cat $CONFIG | grep -o ^PASSWD=\'[^\']*\'$)" -o \ -z "$(echo -n $PASSWD)" -o \ @@ -48,39 +60,39 @@ function check_config { -z "$(echo -n $PASSWD | grep -o [[:lower:][:upper:]])" -o \ -z "$(echo -n $PASSWD | grep -o [[:digit:]])" -o \ "$(echo -n $PASSWD | grep -o [[:punct:]] | wc -l)" -lt "5" ]; then - echo -e "\n$CONFIG: PASSWD is empty or does not meet the minimum complexity requirements." - echo "$1: Please set a new passphrase for the list's private key. Make it at least" - echo "$1: 25 characters long (using a combination of letters, numbers and at least" - echo "$1: 5 special characters) and enclose it in 'single quotes'. The passphrase" - echo -e "$CONFIG: itself, though, cannot contain any single quote.\n" + echo "$CONFIG_FILE: PASSWD is empty or does not meet the minimum complexity requirements." + echo "$CONFIG_FILE: Please set a new passphrase for the list's private key. Make it at least" + echo "$CONFIG_FILE: 25 characters long (using a combination of letters, numbers and at least" + echo "$CONFIG_FILE: 5 special characters) and enclose it in 'single quotes'. The passphrase" + echo "$CONFIG_FILE: itself, though, cannot contain any single quote." exit 1 elif [ -z "$($GPGLIST | grep ^pub | cut -d : -f 10 | grep -i \<$LISTNAME\>$)" ]; then - echo -e "\n$CONFIG: GPG key for list \"$LISTNAME\" could not be found." - echo -e "$CONFIG: Note that this parameter expects an email address.\n" + echo "$CONFIG_FILE: GPG key for list \"$LISTNAME\" could not be found." + echo "$CONFIG_FILE: Note that this parameter expects an email address." exit 1 else for ADMIN in $LISTADMIN; do { if [ -z "$($GPGLIST | grep ^pub | cut -d : -f 10 | grep -i \<$ADMIN\>$)" ]; then - echo -e "\n$CONFIG: GPG key for list administrator \"$ADMIN\" could not be found." - echo -e "$CONFIG: Note that this parameter expects one or more space separated email addresses.\n" + echo "$CONFIG_FILE: GPG key for list administrator \"$ADMIN\" could not be found." + echo "$CONFIG_FILE: Note that this parameter expects one or more space separated email addresses." exit 1 fi; } done fi } -function GPGSTDERR { +function get_gpg_stderr { # discard $GPGDECRYPT STDOUT and get its STDERR instead, for signature checking - echo -e "$PASSWD\n${GPG_MESSAGE[@]}" | sed -e 's/^ //' | ($GPGDECRYPT --status-fd 2 1> /dev/null) 2>&1 ; + echo -e "$PASSWD\n${GPG_MESSAGE[@]}" | sed -e 's/^ //' | ($GPGDECRYPT --status-fd 2 1> /dev/null) 2>&1 } -function SUBSCRIBERS { +function get_subscribers_list { # get list susbscriber's addresses - $GPGLIST | sed -ne "/$LISTNAME/Id" -e '/pub/p' | cut -d : -f 10 | grep -o '<[^<>]*>$' | sed -e 's/[<>]//g' ; + $GPGLIST | sed -ne "/$LISTNAME/Id" -e '/pub/p' | cut -d : -f 10 | grep -o '<[^<>]*>$' | sed -e 's/[<>]//g' } function get_message { - n=0; + n=0 while read STDIN; do MESSAGE[$n]="$STDIN\n" ((++n)) @@ -88,10 +100,12 @@ function get_message { } function get_gpg_message { - signal=0; x=0; + signal=0 + x=0 for ((count=0;count<=n;count++)); do if [[ $signal == "0" ]] && [[ "$(echo "${MESSAGE[$count]}" | grep -v -e "-----BEGIN PGP MESSAGE-----")" == "" ]]; then - GPG_MESSAGE[$x]=${MESSAGE[$count]}; ((++x)) + GPG_MESSAGE[$x]=${MESSAGE[$count]} + ((++x)) signal=1 elif [[ $signal == "1" ]]; then GPG_MESSAGE[$x]=${MESSAGE[$count]} @@ -103,11 +117,17 @@ function get_gpg_message { done } -function get_headers { +function get_message_headers { # get the message headers and the sender's email address FROM=$(echo -e "${MESSAGE[@]}" | grep -m 1 "From:" | cut -d : -f 2- | sed -e 's/^ //') - FROMADD=$(if [ -z "$(echo $FROM | grep '>$')" ] ; then echo $FROM ; else echo $FROM | grep -o '<[^<>]*>$' | sed -e 's/[<>]//g' ; fi) - DATE=$(echo -e "${MESSAGE[@]}" | grep -m 1 "Date:") + FROMADD=$( + if [ -z "$(echo $FROM | grep '>$')" ]; then + echo $FROM + else + echo $FROM | grep -o '<[^<>]*>$' | sed -e 's/[<>]//g' + fi + ) + DATE=$(echo -e "${MESSAGE[@]}" | grep -m 1 "Date:" | sed -e 's/^ //') SUBJECT=$(echo -e "${MESSAGE[@]}" | grep -m 1 "Subject:" | cut -d : -f 2- | sed -e 's/^ //') } @@ -120,8 +140,8 @@ Message from: $FROM Subject: $SUBJECT $DATE -$(GPGSTDERR | grep -F 'gpg: Signature made') -$(GPGSTDERR | grep -F 'gpg: Good signature from') +$(get_gpg_stderr | grep -F 'gpg: Signature made') +$(get_gpg_stderr | grep -F 'gpg: Good signature from') $(echo -e "$PASSWD\n${GPG_MESSAGE[@]}" | $GPGDECRYPT 2> /dev/null)" | sed -e 's/=20$//' | $GPGENCRYPT $1 | $MAIL -r $LISTNAME $1 } @@ -134,8 +154,8 @@ Message from: $FROM Subject: [BAD SIGNATURE] $SUBJECT $DATE -$(GPGSTDERR | grep -F 'gpg: Signature made') -$(GPGSTDERR | grep -F 'gpg: BAD signature from') +$(get_gpg_stderr | grep -F 'gpg: Signature made') +$(get_gpg_stderr | grep -F 'gpg: BAD signature from') $(echo -e "$PASSWD\n${GPG_MESSAGE[@]}" | $GPGDECRYPT 2> /dev/null)" | sed -e 's/=20$//' | $GPGENCRYPT $1 | $MAIL -r $LISTNAME $1 } @@ -162,19 +182,19 @@ function process_message { # process a message sent to the list get_message - get_headers + get_message_headers get_gpg_message # if signature is Good, encrypt and send it for each list subscriber # todo: declare a function to decrypt, re-encrypt and send the list messages - if (GPGSTDERR | grep -Fq GOODSIG); then + if (get_gpg_stderr | grep -Fq GOODSIG); then - for EMAIL in $(SUBSCRIBERS); do + for EMAIL in $(get_subscribers_list); do message_list $EMAIL done # else, if signature is BAD, email it back to the list admins and to sender - elif (GPGSTDERR | grep -Fq BADSIG) ; then + elif (get_gpg_stderr | grep -Fq BADSIG) ; then for EMAIL in $(echo $LISTADMIN $FROMADD); do message_list_error $EMAIL @@ -252,10 +272,77 @@ function gpg_args { GPGENCRYPT="$GPGCOMMAND --passphrase-fd 0 --always-trust --encrypt --sign --armor --recipient" } +function list_admin { + if [ "$1" = "use" ]; then + if [ "$#" -lt "2" ]; then + echo "$(basename $0): Too few arguments. Command \"use\" expects an email address as argument." + return 1 + elif [ "$#" -gt "2" ]; then + echo "$(basename $0): Too many arguments. Command \"use\" expects just one email address as argument." + return 1 + elif [ -z "$(echo -ne $2 | grep -o '[^@]\+@[^@]\+')" ]; then + echo "$(basename $0): Invalid argument. Command \"use\" expects an email address as argument." + return 1 + else + choose_uid $2 + fi + else + echo "$(basename $0): \"$1\": command not found" + fi +} + +function choose_uid { + + KEYID="$($GPGLIST --fixed-list-mode $1 2> /dev/null | grep ^pub | cut -d : -f 5 | grep -o '.\{8\}$')" + + if [ -z "$($GPGLIST --fingerprint --fixed-list-mode $1 2> /dev/null | grep -i \<$1\>:$ | cut -d : -f 10)" ]; then + echo "$CONFIG_FILE: \"$(echo -ne $1 | tr A-Z a-z)\" is not associated with any public key on this keyring." + return 1 + elif [ "$($GPGLIST --fixed-list-mode $1 2> /dev/null | grep ^uid | wc -l)" -eq "1" ]; then + echo "$CONFIG_FILE: \"$(echo -ne $1 | tr A-Z a-z)\" is part of the only UID on public key \"$KEYID\"." + return 1 + elif [ "$($GPGLIST --fixed-list-mode $1 2> /dev/null | grep -iF $1 | cut -d : -f 10 | wc -l)" -gt "1" ]; then + echo "$CONFIG_FILE: \"$(echo -ne $1 | tr A-Z a-z)\" is listed in more than one UID on this keyring. Narrow down your selection" + echo "$CONFIG_FILE: or delete all but one of the public keys associated with this email address." + return 1 + fi + + expect -nN -- << EOF + log_user 0 + set timeout 5 + + set keyid [eval exec $GPG --fingerprint --with-colons --fixed-list-mode $1 | grep ^fpr | cut -d : -f 10] + set uid_count [eval exec $GPG --list-keys --with-colons --fixed-list-mode \$keyid | grep ^uid | wc -l] + set chosen_uid [eval exec $GPG --list-keys --with-colons --fixed-list-mode \$keyid | grep ^uid | grep -ni $1 | cut -d : -f 1] + + eval spawn -noecho $GPG --with-colons --command-fd 0 --status-fd 1 --edit-key \$keyid + expect "GET_LINE keyedit.prompt" { + set uid 1 + while { \$uid <= \$uid_count } { + if { \$uid != \$chosen_uid } { + send "uid \$uid\n" + expect "GET_LINE keyedit.prompt" + } + set uid [incr uid] + } + send "deluid\n" + expect "GET_BOOL keyedit.remove.uid.okay" {send "yes\n"} + expect "GET_LINE keyedit.prompt" {send "save\n"} + expect "GOT_IT" + } + + wait + send_user "$CONFIG_FILE: \"$(echo -ne $1 | tr A-Z a-z)\" chosen successfully. [ expr \$uid_count - 1 ] UIDs deleted from public key \"$KEYID\".\n" + interact + +EOF +} + # main - # command line checking -if [ -z $2 ]; then - usage; exit 1 +if [ -z "$2" -a "$1" != "-c" -a "$1" != "-h" -a "$1" != "-v" ]; then + usage + exit 1 else CONFIG_FILE="$2" CONFIG_PATH="$FIRMA_LIST_PATH/$2" @@ -263,11 +350,13 @@ else fi # if the configuration file exists, disable "sourcepath" and evaluate the parameters -if [ -f $CONFIG ] && [[ $1 != "-c" ]]; then - shopt -u sourcepath && source $CONFIG -else - echo -e "\nConfiguration file \"$CONFIG\" could not be found.\n" - exit 1 +if [ "$1" != "-c" -a "$1" != "-h" -a "$1" != "-v" ]; then + if [ -f $CONFIG ]; then + shopt -u sourcepath && source $CONFIG + else + echo "$(basename $0): Configuration file \"$CONFIG\" could not be found." + exit 1 + fi fi declare -a MESSAGE @@ -276,18 +365,40 @@ declare n export LANG=en_US # get gpg parameters and check the config -gpg_args ; check_config +if [ "$1" = "-a" -o "$1" = "-p" -o "$1" = "-r" ]; then + gpg_args + check_config +fi # command line parsing -if [[ $1 == "-c" ]]; then - newlist -elif [[ $1 == "-p" ]]; then +if [ "$1" = "-a" ]; then + + declare -a ADMINCOMMANDS + + n=0 + while read STDIN; do + ADMINCOMMANDS[$n]="$STDIN\n" + ((n++)) + done + + for i in $(seq 0 $((${#ADMINCOMMANDS[@]} - 1))); do + if [ ! -z "$(echo -ne ${ADMINCOMMANDS[$i]})" -a "$(echo -ne ${ADMINCOMMANDS[$i]} | cut -c1)" != "#" ]; then + list_admin $(echo -ne "${ADMINCOMMANDS[$i]}") + fi + done + +elif [ "$1" = "-c" ]; then + newlist +elif [ "$1" = "-h" ]; then + usage +elif [ "$1" = "-p" ]; then process_message -elif [[ $1 == "-a" ]]; then - list_admin -elif [[ $1 == "-r" ]]; then +elif [ "$1" = "-r" ]; then list_request +elif [ "$1" = "-v" ]; then + version else - usage; exit 1 + usage + exit 1 fi |