aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xfirma245
1 files 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 <<EOF
@@ -648,7 +741,7 @@ EOF
# main()
#-------------------------------------------------------------
-# set enviromental variables and options
+# set environmental variables and options
export LANG=en_US
umask 0077