From c5aa759053d1a5187b4de2d7cb10a96d14dce008 Mon Sep 17 00:00:00 2001 From: luis Date: Sat, 3 Sep 2005 01:20:32 +0000 Subject: Implemented the ability to use GnuPG's --hidden-recipient option and to send messages to all subscribers at once using BCC. --- firma | 245 +++++++++++++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 169 insertions(+), 76 deletions(-) diff --git a/firma b/firma index 5c8eb94..2ad30a7 100755 --- a/firma +++ b/firma @@ -2,47 +2,56 @@ # # firma: GnuPG-based encrypted mailing list manager # Feedback: firma@sarava.org -# -# Firma is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Firma 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# +# Firma is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# Firma 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. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., 59 Temple +# Place - Suite 330, Boston, MA 02111-1307, USA # # Usage: # -# All firma parameters are passed through two different -# configuration files: firma.conf, containing general parameters -# necessary to run the script, and a list specific file, -# containing its address, administrator(s), etc. In both -# files you should enter PARAMETER='value' (whithout spaces -# before or after the equal sign). +# All firma parameters are passed through two different configuration files: +# firma.conf, containing general parameters needed to run the script, and a list +# specific file, containing its address, administrator(s), etc. In both files +# you should enter PARAMETER='value' (without spaces before or after the equal +# sign). # # firma.conf should contain the following parameters: # -# GPG_BINARY= path to the gnupg binary +# GPG_BINARY= path to the GnuPG binary # MAIL_AGENT= path to the mail transport agent to be used (e.g., sendmail) # MAIL_AGENT_ARGS= command-line arguments to be passed to the command above # LISTS_DIR= path to the mailing lists directory # +# And it may contain the following optional parameters: +# +# USE_GPG_HIDDEN_RECIPIENT_OPTION= set to '1' to use GnuPG's --hidden-recipient +# option, available from version 1.4.0 onwards +# (try 'man gpg' for more information) +# SEND_MESSAGES_USING_BCC= set to '1' to send list messages to all subscribers +# at once using BCC (NOTE: this option can only be +# used in conjunction with the option above.) +# # And the list configuration file should contain: # # LIST_ADDRESS= list's email address # LIST_ADMIN= list's administrators email addresses (space separated) -# LIST_HOMEDIR= list's gnupg homedir, where the list's keyrings are located +# LIST_HOMEDIR= list's GnuPG homedir, where the list's keyrings are located # PASSPHRASE= passphrase for the list's private keyring # -# NOTE: The PASSPHRASE value _has_ to be enclosed in single -# quotes and _cannot_ contain any additional single quote as part -# of itself. +# NOTE: The passphrase _has_ to be enclosed in single quotes and _cannot_ +# contain any additional single quote as part of itself. It has to be at least +# 25 characters long, combining numbers, upper and lower case letters and at +# least 5 special characters. Also, no character can be sequentially repeated +# more than 3 times. # FIRMA_CONFIG_FILE="/usr/local/etc/firma.conf" @@ -60,7 +69,7 @@ function DeclareGpgVars { GPG="$GPG_BINARY $GPG_FLAGS" GPG_LIST_KEYS="$GPG --list-keys --with-colons" GPG_DECRYPT="$GPG --passphrase-fd 0 --decrypt" - GPG_ENCRYPT="$GPG --armor --trust-model always --local-user $LIST_ADDRESS --no-emit-version --passphrase-fd 0 --sign --encrypt --recipient" + GPG_ENCRYPT="$GPG --armor --trust-model always --local-user $LIST_ADDRESS --no-emit-version --passphrase-fd 0 --sign --encrypt" } @@ -128,13 +137,16 @@ function CheckFirmaConfigFile { elif [[ ! -d "$LISTS_DIR" ]]; then echo "$(basename $0): Lists directory ("$LISTS_DIR") could not be found." exit 1 + elif [[ "$USE_GPG_HIDDEN_RECIPIENT_OPTION" == "1" && "$($GPG_BINARY --version | head -n1 | tr -dc '[[:digit:]]')" -lt "140" ]]; then + echo "$(basename $0): GPG's \"--hidden-recipient\" option is only available from version 1.4.0 onwards." + exit 1 fi } function CheckListConfigFile { #------------------------------------------------------------- - # check configuration file parameters + # check list configuration file parameters # # parameter(s): none # depends on function(s): DeclareGpgVars @@ -228,17 +240,17 @@ function GetMessageHeaders { fi done - # list ORIG_MESSAGE_HEADERS and get some specific headers for later use - FROM=$(echo -e "${ORIG_MESSAGE_HEADERS[@]}" | grep -m 1 '^ From:' | cut -d : -f 2- | sed -e 's/^ //') - SENDER_ADDRESS=$(if [[ -z "$(echo $FROM | grep '>$')" ]]; then echo $FROM; else echo $FROM | grep -o '<[^<>]*>$' | sed -e 's/[<>]//g'; fi) - DATE=$(echo -e "${ORIG_MESSAGE_HEADERS[@]}" | grep -m 1 '^ Date:' | cut -d : -f 2- | sed -e 's/^ //') - SUBJECT=$(echo -e "${ORIG_MESSAGE_HEADERS[@]}" | grep -m 1 '^ Subject:' | cut -d : -f 2- | sed -e 's/^ //') - # check if message headers were successfully stored in ORIG_MESSAGE_HEADERS if [[ "${#ORIG_MESSAGE_HEADERS[@]}" -eq "0" ]]; then echo "$(basename $0): Message headers could not be located within this message." exit 1 fi + + # list ORIG_MESSAGE_HEADERS and get some specific headers for later use + FROM=$(echo -e "${ORIG_MESSAGE_HEADERS[@]}" | grep -m 1 '^ From:' | cut -d : -f 2- | sed -e 's/^ //') + SENDER_ADDRESS=$(if [[ -z "$(echo $FROM | grep '>$')" ]]; then echo $FROM; else echo $FROM | grep -o '<[^<>]*>$' | sed -e 's/[<>]//g'; fi) + DATE=$(echo -e "${ORIG_MESSAGE_HEADERS[@]}" | grep -m 1 '^ Date:' | cut -d : -f 2- | sed -e 's/^ //') + SUBJECT=$(echo -e "${ORIG_MESSAGE_HEADERS[@]}" | grep -m 1 '^ Subject:' | cut -d : -f 2- | sed -e 's/^ //') } @@ -277,8 +289,8 @@ function GetGpgMessage { # last, assign the line matched above to the last element of ORIG_GPG_MESSAGE ORIG_GPG_MESSAGE[$j]="${ORIG_MESSAGE[$i]}" - # no need to process lines beyond this point - # exit for loop + # no need to process lines beyond this point, + #+exit for loop break 1 fi done @@ -309,7 +321,7 @@ function GetGpgDecryptStderr { function GetSubscribersList { #------------------------------------------------------------- - # get list susbscriber addresses for message encryption and delivery + # get list subscriber addresses for message encryption and delivery # # parameter(s): none # depends on function(s): DeclareGpgVars @@ -329,13 +341,54 @@ function SendListMessage { #------------------------------------------------------------- # compose and send a message to list members # - # parameter(s): subscriber address - # depends on function(s): DeclareGpgVars, GetGpgMessage, GetMessageHeaders, GetGpgDecryptStderr + # parameter(s): none + # depends on function(s): DeclareGpgVars, GetGpgMessage, GetMessageHeaders, + # GetGpgDecryptStderr, GetSubscribersList # returns: 0 on success #------------------------------------------------------------- - # this is the body of the message to be sent, so no identation here -MESSAGE_BODY=( $(echo "$PASSPHRASE + local subscriber + local recipients + + # check if gpg's --hidden-recipient option should be used + if [[ "$USE_GPG_HIDDEN_RECIPIENT_OPTION" == "1" ]]; then + + # get the subscribers' addresses to be used by the GPG_ENCRYPT command bellow and + #+possibly by the MAIL_AGENT, in case the message is going to be sent using BCC + for subscriber in $(GetSubscribersList); do + recipients="$recipients$subscriber " + done + + # this is the body of the message to be sent, so no indentation here + +MESSAGE_BODY="$(echo "$PASSPHRASE +Message from: $FROM +Subject: $SUBJECT +Date: $DATE + +$(GetGpgDecryptStderr | grep '^gpg: Signature made') +$(GetGpgDecryptStderr | grep '^gpg: Good signature from') + +$(echo -e "$PASSPHRASE\n${ORIG_GPG_MESSAGE[@]}" | $GPG_DECRYPT 2> /dev/null)" | sed -e 's/=20$//' | $GPG_ENCRYPT --group subscribers="$recipients" --hidden-recipient subscribers 2> /dev/null)" + + # now send the message, either using BCC to sent it to all subscribers at + #+once or sending it separately to each one of them + if [[ "$SEND_MESSAGES_USING_BCC" == "1" ]]; then + echo -e "From: $LIST_ADDRESS\nBCC: $recipients\nSubject: none\n\n${MESSAGE_BODY[@]}" | sed -e 's/^ //' | $MAIL_AGENT $MAIL_AGENT_ARGS + else + for subscriber in $recipients; do + echo -e "From: $LIST_ADDRESS\nTo: $subscriber\nSubject: none\n\n${MESSAGE_BODY[@]}" | sed -e 's/^ //' | $MAIL_AGENT $MAIL_AGENT_ARGS + done + fi + + # else, if gpg's --hidden-recipient option should not be used, + #+encrypt and send message separately to each list subscriber + else + for subscriber in $(GetSubscribersList); do + + # this is the body of the message to be sent, so no indentation here + +MESSAGE_BODY="$(echo "$PASSPHRASE Message from: $FROM Subject: $SUBJECT Date: $DATE @@ -343,25 +396,68 @@ Date: $DATE $(GetGpgDecryptStderr | grep '^gpg: Signature made') $(GetGpgDecryptStderr | grep '^gpg: Good signature from') -$(echo -e "$PASSPHRASE\n${ORIG_GPG_MESSAGE[@]}" | $GPG_DECRYPT 2> /dev/null)" | sed -e 's/=20$//' | $GPG_ENCRYPT $1 2> /dev/null | sed -e 's/$/\\n/') ) +$(echo -e "$PASSPHRASE\n${ORIG_GPG_MESSAGE[@]}" | $GPG_DECRYPT 2> /dev/null)" | sed -e 's/=20$//' | $GPG_ENCRYPT --recipient $subscriber 2> /dev/null)" - # now send the message - echo -e "From: $LIST_ADDRESS\nTo: $1\nSubject: none\n\n${MESSAGE_BODY[@]}" | sed -e 's/^ //' | $MAIL_AGENT $MAIL_AGENT_ARGS + # now send the message + echo -e "From: $LIST_ADDRESS\nTo: $subscriber\nSubject: none\n\n${MESSAGE_BODY[@]}" | sed -e 's/^ //' | $MAIL_AGENT $MAIL_AGENT_ARGS + done + fi } function SendWarningMessage { #------------------------------------------------------------- - # compose and send a "BAD signature" warning to the - # list administrator(s) and to sender + # compose and send a "BAD signature" warning to the list administrator(s) and to sender # - # parameter(s): list administrator/sender address - # depends on function(s): DeclareGpgVars, GetGpgMessage, GetMessageHeaders, GetGpgDecryptStderr + # parameter(s): none + # depends on function(s): DeclareGpgVars, GetGpgMessage, GetMessageHeaders, + # GetGpgDecryptStderr, GetSubscribersList # returns: 0 on success #------------------------------------------------------------- - # this is the body of the message to be sent, so no identation here -MESSAGE_BODY=( $(echo "$PASSPHRASE + local email_address + local recipients + + # check if gpg's --hidden-recipient option should be used + if [[ "$USE_GPG_HIDDEN_RECIPIENT_OPTION" == "1" ]]; then + + # get the administrator(s) and sender addresses to be used by the GPG_ENCRYPT + #+command bellow and possibly by the MAIL_AGENT, in case the message is going + #+to be sent using BCC + for email_address in $LIST_ADMIN $SENDER_ADDRESS; do + recipients="$recipients$email_address " + done + + # this is the body of the message to be sent, so no indentation here + +MESSAGE_BODY="$(echo "$PASSPHRASE +Message from: $FROM +Subject: [BAD SIGNATURE] $SUBJECT +Date: $DATE + +$(GetGpgDecryptStderr | grep '^gpg: Signature made') +$(GetGpgDecryptStderr | grep '^gpg: BAD signature from') + +$(echo -e "$PASSPHRASE\n${ORIG_GPG_MESSAGE[@]}" | $GPG_DECRYPT 2> /dev/null)" | sed -e 's/=20$//' | $GPG_ENCRYPT --group addresses="$recipients" --hidden-recipient addresses 2> /dev/null)" + + # now send the message, either using BCC to sent it to all addresses at + #+once or sending it separately to each one of them + if [[ "$SEND_MESSAGES_USING_BCC" == "1" ]]; then + echo -e "From: $LIST_ADDRESS\nBCC: $recipients\nSubject: none\n\n${MESSAGE_BODY[@]}" | sed -e 's/^ //' | $MAIL_AGENT $MAIL_AGENT_ARGS + else + for email_address in $LIST_ADMIN $SENDER_ADDRESS; do + echo -e "From: $LIST_ADDRESS\nTo: $email_address\nSubject: none\n\n${MESSAGE_BODY[@]}" | sed -e 's/^ //' | $MAIL_AGENT $MAIL_AGENT_ARGS + done + fi + + # else, if gpg's --hidden-recipient option should not be used, + #+encrypt and send message separately to each address + else + for email_address in $LIST_ADMIN $SENDER_ADDRESS; do + + # this is the body of the message to be sent, so no indentation here + +MESSAGE_BODY="$(echo "$PASSPHRASE Message from: $FROM Subject: [BAD SIGNATURE] $SUBJECT Date: $DATE @@ -369,10 +465,12 @@ Date: $DATE $(GetGpgDecryptStderr | grep '^gpg: Signature made') $(GetGpgDecryptStderr | grep '^gpg: BAD signature from') -$(echo -e "$PASSPHRASE\n${ORIG_GPG_MESSAGE[@]}" | $GPG_DECRYPT 2> /dev/null)" | sed -e 's/=20$//' | $GPG_ENCRYPT $1 2> /dev/null | sed -e 's/$/\\n/') ) +$(echo -e "$PASSPHRASE\n${ORIG_GPG_MESSAGE[@]}" | $GPG_DECRYPT 2> /dev/null)" | sed -e 's/=20$//' | $GPG_ENCRYPT --recipient $email_address 2> /dev/null)" - # now send the message - echo -e "From: $LIST_ADDRESS\nTo: $1\nSubject: none\n\n${MESSAGE_BODY[@]}" | sed -e 's/^ //' | $MAIL_AGENT $MAIL_AGENT_ARGS + # now send the message + echo -e "From: $LIST_ADDRESS\nTo: $email_address\nSubject: none\n\n${MESSAGE_BODY[@]}" | sed -e 's/^ //' | $MAIL_AGENT $MAIL_AGENT_ARGS + done + fi } @@ -380,14 +478,14 @@ function SendBounceMessage { #------------------------------------------------------------- # send a bounce message back to sender # - # parameter(s): sender address + # parameter(s): none # depends on function(s): GetMessageHeaders # returns: 0 on success #------------------------------------------------------------- - # this is the body of the message to be sent, so no identation here + # this is the body of the message to be sent, so no indentation here echo "From: $LIST_ADDRESS -To: $1 +To: $SENDER_ADDRESS Subject: none Message from: $FROM @@ -410,33 +508,26 @@ function ProcessMessage { # # parameter(s): none # depends on function(s): GetMessage, GetMessageHeaders, GetGpgMessage, GetGpgDecryptStderr, - # GetSubscribersList, SendListMessage, SendWarningMessage, SendBounceMessage + # SendListMessage, SendWarningMessage, SendBounceMessage # returns: 0 on success #------------------------------------------------------------- - local email - GetMessage GetMessageHeaders GetGpgMessage - # if signature in message is valid, encrypt and send it for each list subscriber + # if signature in message is valid, encrypt and send it to list subscribers if GetGpgDecryptStderr | grep -q '^\[GNUPG:] GOODSIG'; then - for email in $(GetSubscribersList); do - SendListMessage $email - done + SendListMessage - # else, if signature is invalid, email it back to the list administrator(s) and to sender + # else, if signature is invalid, send it back to the list administrator(s) and to sender elif GetGpgDecryptStderr | grep -q '^\[GNUPG:] BADSIG'; then - for email in $LIST_ADMIN $SENDER_ADDRESS; do - SendWarningMessage $email - done + SendWarningMessage # else, probably either the message was not encrypted/signed or the sender is not subscribed to the list # send a bounce message back to sender including a note about this - # todo: parse STDERR to find out why the signature couldn't be checked and send more specific errors back to sender else - SendBounceMessage $SENDER_ADDRESS + SendBounceMessage fi } @@ -456,17 +547,18 @@ function NewList { mkdir "$LIST_PATH" # || (echo "$(basename $0): error creating $LIST_PATH: installation aborted"; exit 1) echo "creating list config file and will ask some questions." - # comented: -# read -rep "path to smtp command (eg, /usr/sbin/sendmail): " MAIL_AGENT -# read -rep "command-line arguments passed to the smtp wrapper (eg, -oem -oi -t): " MAIL_AGENT_ARGS -# read -rep "path to gpg binary (eg, /usr/bin/gpg): " GPG_BINARY + # commented: +# read -rep "path to smtp command (e.g., /usr/sbin/sendmail): " MAIL_AGENT +# read -rep "command-line arguments passed to the smtp wrapper (e.g., -oem -oi -t): " MAIL_AGENT_ARGS +# read -rep "path to gpg binary (e.g., /usr/bin/gpg): " GPG_BINARY # if [ ! -x "$GPG_BINARY" ]; then - read -rep "list keyring folder: " LIST_HOMEDIR # removed: (defaults to $LIST_HOMEDIR) + # removed: (defaults to $LIST_HOMEDIR) + read -rep "list keyring folder: " LIST_HOMEDIR # todo: please no utf-8 (see DETAILS) - read -rep "list email (eg, firma@domain.tld): " LIST_ADDRESS + read -rep "list email (e.g., firma@domain.tld): " LIST_ADDRESS read -rep "list admins emails (space delimited): " LIST_ADMIN read -rep "list description (fake?): " DESCRIPTION read -resp "password for list keyring (use a huge one): " PASSPHRASE @@ -479,7 +571,8 @@ function NewList { chmod 600 $LIST_CONFIG_FILE if [ -f "$LIST_CONFIG_FILE" ]; then DeclareGpgVars - echo -e "LIST_HOMEDIR=$LIST_HOMEDIR\nLIST_ADDRESS=$LIST_ADDRESS\nLIST_ADMIN=$LIST_ADMIN\nPASSPHRASE=$PASSPHRASE" > $LIST_CONFIG_FILE # removed: MAIL_AGENT=$MAIL_AGENT\nGPG_BINARY=$GPG_BINARY\n + # removed: MAIL_AGENT=$MAIL_AGENT\nGPG_BINARY=$GPG_BINARY\n + echo -e "LIST_HOMEDIR=$LIST_HOMEDIR\nLIST_ADDRESS=$LIST_ADDRESS\nLIST_ADMIN=$LIST_ADMIN\nPASSPHRASE=$PASSPHRASE" > $LIST_CONFIG_FILE echo "now generating your keyring..." $GPG --gen-key <