aboutsummaryrefslogtreecommitdiff
path: root/firma
diff options
context:
space:
mode:
authorluis <luis>2005-08-17 00:59:17 +0000
committerluis <luis>2005-08-17 00:59:17 +0000
commitd4dc72b016f90113f1507cb9714b09174579bc16 (patch)
tree573e6debbee64bccaa7a6398fa1aad749217d409 /firma
parentf5944b1b3c0c263686ebb11bdb939aff0299d12f (diff)
downloadfirma-d4dc72b016f90113f1507cb9714b09174579bc16.tar.gz
firma-d4dc72b016f90113f1507cb9714b09174579bc16.tar.bz2
LOTS of changes. Check the CHANGELOG for details.
Diffstat (limited to 'firma')
-rwxr-xr-xfirma908
1 files changed, 627 insertions, 281 deletions
diff --git a/firma b/firma
index 523695d..1f0ff8a 100755
--- a/firma
+++ b/firma
@@ -1,35 +1,93 @@
#!/bin/bash
#
-# firma: encrypted mailing list manager
-# feedback: rhatto@riseup.net luis@riseup.net | GPL
+# firma: GnuPG-based encrypted mailing list manager
+# Feedback: rhatto@riseup.net, luis@riseup.net
+# Licensed under the GNU Public License.
#
-# list configuration is passed thru the config file,
-# where you put PARAMETER=value (whithout spaces)
+# 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).
#
-# MAIL= path for mail program
-# MAIL_ARGS= command-line arguments passed to the smtp wrapper
-# GPG= path for gnupg binary
-# LISTNAME= list email
-# LISTADMIN= list administrator email addresses (space separated)
-# GPGDIR= gpg dir for the lists' keyring
-# PASSWD= passwd for the lists' keyring
+# firma.conf should contain the following parameters:
+#
+# 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 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
+# 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.
#
-FIRMA_LIST_PATH="/usr/local/etc/lists"
+FIRMA_CONFIG_FILE="/usr/local/etc/firma.conf"
VERSION="0.3"
-function usage {
- 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 DeclareGpgVars {
+ #-------------------------------------------------------------
+ # declare gpg global variables
+ #
+ # parameter(s): none
+ # depends on function(s): none
+ # returns: 0
+ #-------------------------------------------------------------
+ GPG_FLAGS="--no-options --no-default-keyring --homedir $LIST_HOMEDIR --quiet --batch --no-tty --no-use-agent --no-auto-check-trustdb --no-permission-warning"
+ 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"
+}
+
+
+function Usage {
+ #-------------------------------------------------------------
+ # display help and exit
+ #
+ # parameter(s): none
+ # depends on function(s): none
+ # returns: 0
+ #-------------------------------------------------------------
+ echo "Usage: $(basename $0) OPTION [LIST-NAME]"
+ echo "GnuPG-based encrypted mailing list manager."
+ echo
+ echo " -a, --admin-task LIST-NAME process administrative tasks on list"
+ echo " -c, --create-newlist LIST-NAME create a new mailing list"
+ echo " -h, --help display this help and exit"
+ echo " -p, --process-message LIST-NAME process a message sent to list"
+# echo " -r, --process-request LIST-NAME process administrative and user"
+# echo " requests on list."
+ echo " -v, --version output version information and exit"
+ echo
+ echo "If option -a is given, read standard input for tasks to be performed."
+ echo "Tasks can be one or more of the following:"
+ echo
+ echo " use EMAIL-ADDRESS use the given address for message delivery instead"
+ echo " of the primary address on key"
+ echo
+# echo "Report bugs to <email@goes.here>"
}
-function version {
+
+function Version {
+ #-------------------------------------------------------------
+ # output version information and exit
+ #
+ # parameter(s): none
+ # depends on function(s): none
+ # returns: 0
+ #-------------------------------------------------------------
echo "firma $VERSION"
+ echo
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"
@@ -37,391 +95,679 @@ function version {
echo "for more details."
}
-function check_config {
- # check configuration file parameters
- if [ ! -f $GPG -o ! -x $GPG ]; then
- echo "$CONFIG_FILE: GPG binary ($GPG) could not be found."
+
+function CheckFirmaConfigFile {
+ #-------------------------------------------------------------
+ # check firma.conf parameters
+ #
+ # parameter(s): none
+ # depends on function(s): none
+ # returns: 0 if all checks are passed, 1 if any check fails
+ #-------------------------------------------------------------
+ if [[ ! -f "$GPG_BINARY" || ! -x "$GPG_BINARY" ]]; then
+ echo "$(basename $0): GPG binary ("$GPG_BINARY") could not be found."
+ exit 1
+ elif [[ ! -f "$MAIL_AGENT" || ! -x "$MAIL_AGENT" ]]; then
+ echo "$(basename $0): Mail transport agent binary ("$MAIL_AGENT") could not be found."
exit 1
- elif [ ! -f $MAIL -o ! -x $MAIL ]; then
- echo "$CONFIG_FILE: Mail program ($MAIL) could not be found."
+ elif [[ ! -d "$LISTS_DIR" ]]; then
+ echo "$(basename $0): Lists directory ("$LISTS_DIR") could not be found."
exit 1
- elif [ ! -d $GPGDIR -o ! -f $GPGDIR/pubring.gpg -o ! -f $GPGDIR/secring.gpg ]; then
- echo "$CONFIG_FILE: GPG home directory ($GPGDIR) or the GPG keyrings could not be found."
+ fi
+}
+
+
+function CheckListConfigFile {
+ #-------------------------------------------------------------
+ # check configuration file parameters
+ #
+ # parameter(s): none
+ # depends on function(s): DeclareGpgVars
+ # returns: 0 if all checks are passed, 1 if any check fails
+ #-------------------------------------------------------------
+
+ local administrator
+
+ if [[ ! -d "$LIST_HOMEDIR" || ! -f "$LIST_HOMEDIR/pubring.gpg" || ! -f "$LIST_HOMEDIR/secring.gpg" ]]; then
+ echo "$LIST_NAME: GPG home directory ("$LIST_HOMEDIR") or the GPG keyrings could not be found."
exit 1
- elif [ -z "$(cat $CONFIG | grep -o ^PASSWD=\'[^\']*\'$)" -o \
- -z "$(echo -n $PASSWD)" -o \
- "$(echo -n $PASSWD | wc -m)" -lt "25" -o \
- -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 "$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."
+ elif [[ -z "$(cat $LIST_CONFIG_FILE | grep -o "^PASSPHRASE='[^']*'$")" || \
+ -z "$PASSPHRASE" || \
+ "$(echo -n "$PASSPHRASE" | wc -c)" -lt "25" || \
+ -z "$(echo -n "$PASSPHRASE" | tr -dc '[[:lower:]]')" || \
+ -z "$(echo -n "$PASSPHRASE" | tr -dc '[[:upper:]]')" || \
+ -z "$(echo -n "$PASSPHRASE" | tr -dc '[[:digit:]]')" || \
+ "$(echo -n "$PASSPHRASE" | tr -dc '[:punct:]' | wc -c)" -lt "5" || \
+ "$(echo -n "$PASSPHRASE" | fold -w1 | uniq -cd | grep -v '^ \{6\}[23] ')" ]]; then
+ echo "$LIST_NAME: PASSPHRASE is empty or does not meet the minimum complexity requirements."
+ echo "$LIST_NAME: Please set a new passphrase for the list's private key. Make it at least"
+ echo "$LIST_NAME: 25 characters long (using a combination of numbers, upper and lower case"
+ echo "$LIST_NAME: letters and at least 5 special characters) and enclose it in 'single"
+ echo "$LIST_NAME: quotes'. The passphrase itself, though, cannot contain any single quote."
+ echo "$LIST_NAME: Also, no character should be sequentially repeated more than 3 times."
exit 1
- elif [ -z "$($GPGLIST | grep ^pub | cut -d : -f 10 | grep -i \<$LISTNAME\>$)" ]; then
- echo "$CONFIG_FILE: GPG key for list \"$LISTNAME\" could not be found."
- echo "$CONFIG_FILE: Note that this parameter expects an email address."
+ elif [[ -z "$($GPG_LIST_KEYS --fixed-list-mode 2> /dev/null | grep ^uid | cut -d : -f 10 | grep -i "<$LIST_ADDRESS>$")" ]]; then
+ echo "$LIST_NAME: Public key for list \"$(echo -ne "$LIST_ADDRESS" | tr '[:upper:]' '[:lower:]')\" could not be found."
+ echo "$LIST_NAME: 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 "$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."
+ for administrator in $LIST_ADMIN; do {
+ if [[ -z "$($GPG_LIST_KEYS --fixed-list-mode 2> /dev/null | grep ^uid | cut -d : -f 10 | grep -i "<$administrator>$")" ]]; then
+ echo "$LIST_NAME: Public key for list administrator \"$(echo -ne "$administrator" | tr '[:upper:]' '[:lower:]')\" could not be found."
+ echo "$LIST_NAME: Note that this parameter expects one or more space separated email addresses."
exit 1
fi; }
done
fi
}
-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
-}
-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'
-}
+function GetMessage {
+ #-------------------------------------------------------------
+ # read message from STDIN
+ #
+ # parameter(s): expects message from STDIN
+ # depends on function(s): none
+ # returns: 0 on success, 1 if there's no input
+ #-------------------------------------------------------------
+
+ local element
-function get_message {
- LINES=0
+ # store message in array ORIG_MESSAGE
while read STDIN; do
- MESSAGE[$LINES]="$STDIN\n"
- ((++LINES))
+ ORIG_MESSAGE[$element]="$STDIN\n"
+ ((++element))
done
+
+ # check if message was successfully stored in ORIG_MESSAGE
+ if [[ "${#ORIG_MESSAGE[@]}" -eq "0" ]]; then
+ echo "$(basename $0): Message couldn't be read from standard input."
+ exit 1
+ fi
}
-function get_gpg_message {
- signal=0
- if [ ! -z "$LINES" ]; then
- n=0;
- for ((count=0;count<=LINES;count++)); do
- if [[ $signal == "0" ]] && [[ "$(echo "${MESSAGE[$count]}" | grep -v -e "-----BEGIN PGP MESSAGE-----")" == "" ]]; then
- GPG_MESSAGE[$n]=${MESSAGE[$count]}
- ((++n))
- signal=1
- elif [[ $signal == "1" ]]; then
- GPG_MESSAGE[$n]=${MESSAGE[$count]}
- ((++n))
- if [[ "$(echo "${MESSAGE[$count]}" | grep -v -e "-----END PGP MESSAGE-----")" == "" ]]; then
- signal=0
- fi
- fi
+
+function GetMessageHeaders {
+ #-------------------------------------------------------------
+ # get message headers and store some of them on separate variables
+ #
+ # parameter(s): none
+ # depends on function(s): GetMessage
+ # returns: 0 on success, 1 if headers can't be located within message
+ #-------------------------------------------------------------
+
+ local element
+
+ # store all headers in array ORIG_MESSAGE_HEADERS
+ for element in $(seq 0 $((${#ORIG_MESSAGE[@]} - 1))); do
+ until [[ "${ORIG_MESSAGE[$element]}" == "\n" ]]; do
+ ORIG_MESSAGE_HEADERS[$element]="${ORIG_MESSAGE[$element]}"
+ ((++element))
done
+ # done, reached first blank line in message
+ # exit for loop
+ break 1
+ 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:' | 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."
+ return 1
fi
}
-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'
+
+function GetGpgMessage {
+ #-------------------------------------------------------------
+ # get gpg encrypted part of a message
+ #
+ # parameter(s): none
+ # depends on function(s): GetMessage
+ # returns: 0 on success, 1 if no encrypted data is found within message
+ #-------------------------------------------------------------
+
+ # i = ORIG_MESSAGE element being processed
+ # j = ORIG_GPG_MESSAGE element being processed
+ local i j
+
+ # for elements in ORIG_MESSAGE, do
+ for i in $(seq 0 $((${#ORIG_MESSAGE[@]} - 1))); do
+
+ # find the first line of the encrypted data
+ #+and assign it to the first element of ORIG_GPG_MESSAGE
+ if [[ "${ORIG_MESSAGE[$i]}" == "-----BEGIN PGP MESSAGE-----\n" ]]; then
+ ORIG_GPG_MESSAGE[$j]="${ORIG_MESSAGE[$i]}"
+
+ # move to next element in both arrays
+ ((++i))
+ ((++j))
+
+ # until the end of the encrypted data is reached,
+ #+assign subsequent elements in ORIG_MESSAGE to elements of ORIG_GPG_MESSAGE
+ until [[ "${ORIG_MESSAGE[$i]}" == "-----END PGP MESSAGE-----\n" ]]; do
+ ORIG_GPG_MESSAGE[$j]="${ORIG_MESSAGE[$i]}"
+ ((++i))
+ ((++j))
+ done
+
+ # 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
+ break 1
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/^ //')
+ done
+
+ # check if encrypted data was stored in ORIG_GPG_MESSAGE
+ if [[ "${#ORIG_GPG_MESSAGE[@]}" -eq "0" ]]; then
+ echo "$(basename $0): No GPG encrypted data was found within this message."
+ return 1
+ fi
+}
+
+
+function GetGpgDecryptStderr {
+ #-------------------------------------------------------------
+ # discard $GPG_DECRYPT STDOUT and get its STDERR for signature checking
+ #
+ # parameter(s): none
+ # depends on function(s): DeclareGpgVars, GetGpgMessage
+ # returns: 0 if signature in ORIG_GPG_MESSAGE is valid,
+ # 1 if signature is invalid,
+ # 2 for all other errors (incorrect passphrase, no encrypted data, etc.)
+ #-------------------------------------------------------------
+
+ echo -e "$PASSPHRASE\n${ORIG_GPG_MESSAGE[@]}" | sed -e 's/^ //' | ($GPG_DECRYPT --status-fd 2 1> /dev/null) 2>&1
+
}
-function message_list {
-# compose and send a message to the list
-# $1: subscriber email
-# sorry no identation :P
-LIST_MESSAGE=( $(echo "$PASSWD
+function GetSubscribersList {
+ #-------------------------------------------------------------
+ # get list susbscriber addresses for message encryption and delivery
+ #
+ # parameter(s): none
+ # depends on function(s): DeclareGpgVars
+ # returns: 0 on success, 1 if there are no subscribers on list
+ #-------------------------------------------------------------
+
+ if [[ "$($GPG_LIST_KEYS 2> /dev/null | sed -ne "/$LIST_ADDRESS/Id" -e '/^pub/p' | wc -l)" -ne "0" ]]; then
+ $GPG_LIST_KEYS 2> /dev/null | sed -ne "/$LIST_ADDRESS/Id" -e '/^pub/p' | cut -d : -f 10 | grep -o '<[^<>]*>$' | sed -e 's/[<>]//g'
+ else
+ echo "$LIST_NAME: There are no subscribers on list \"$(echo "$LIST_ADDRESS" | tr '[:upper:]' '[:lower:]')\"."
+ exit 1
+ fi
+}
+
+
+function SendListMessage {
+ #-------------------------------------------------------------
+ # compose and send a message to list members
+ #
+ # parameter(s): subscriber address
+ # depends on function(s): DeclareGpgVars, GetGpgMessage, GetMessageHeaders, GetGpgDecryptStderr
+ # returns: 0 on success
+ #-------------------------------------------------------------
+
+ # this is the body of the message to be sent, so no identation here
+MESSAGE_BODY=( $(echo "$PASSPHRASE
Message from: $FROM
Subject: $SUBJECT
$DATE
-$(get_gpg_stderr | grep -F 'gpg: Signature made')
-$(get_gpg_stderr | grep -F 'gpg: Good signature from')
+$(GetGpgDecryptStderr | grep '^gpg: Signature made')
+$(GetGpgDecryptStderr | grep '^gpg: Good signature from')
-$(echo -e "$PASSWD\n${GPG_MESSAGE[@]}" | $GPGDECRYPT 2> /dev/null)" | sed -e 's/=20$//' | $GPGENCRYPT $1 | sed -e 's/^\(.*\)$/\1\\n/') )
+$(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/') )
-# now send the message
-echo -e "From: $LISTNAME\nTo: $1\nSubject: none\n\n${LIST_MESSAGE[@]}" | sed -e 's/^ //' | $MAIL $MAIL_ARGS
-
+ # 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
}
-function message_list_error {
-# compose and send an error message
-# sorry no identation :P
-LIST_MESSAGE=( $(echo "$PASSWD
+function SendWarningMessage {
+ #-------------------------------------------------------------
+ # 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
+ # returns: 0 on success
+ #-------------------------------------------------------------
+
+ # this is the body of the message to be sent, so no identation here
+MESSAGE_BODY=( $(echo "$PASSPHRASE
Message from: $FROM
Subject: [BAD SIGNATURE] $SUBJECT
$DATE
-
-$(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 | sed -e 's/^\(.*\)$/\1\\n/') )
-# now send the message
-echo -e "From: $LISTNAME\nTo: $1\nSubject: none\n\n${LIST_MESSAGE[@]}" | sed -e 's/^ //' | $MAIL $MAIL_ARGS
+$(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/') )
+ # 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
}
-function message_list_return {
-# send a bounce message
-# $1: sender email (usually $FROMADD)
-# sorry no identation :P
-echo "From: $LISTNAME
+
+function SendBounceMessage {
+ #-------------------------------------------------------------
+ # send a bounce message back to sender
+ #
+ # parameter(s): sender address
+ # depends on function(s): GetMessageHeaders
+ # returns: 0 on success
+ #-------------------------------------------------------------
+
+ # this is the body of the message to be sent, so no identation here
+echo "From: $LIST_ADDRESS
To: $1
Subject: none
Message from: $FROM
Subject: [RETURNED MAIL] $SUBJECT
$DATE
-
- [ It was not possible to process this message. Either or both
- the message was not encrypted and/or signed, or you are not
- subscribed to this list. Contact the list administrator if
- you have any questions. ]
-
- --
- firma v$VERSION" | $MAIL $MAIL_ARGS
+
+ It was not possible to process this message. Either or both
+ the message was not encrypted and/or signed, or you are not
+ subscribed to this list. Contact the list administrator if
+ you have any questions.
+
+--
+firma v$VERSION" | $MAIL_AGENT $MAIL_AGENT_ARGS
}
-function process_message {
- # process a message sent to the list
- get_message
- get_message_headers
- get_gpg_message
+function ProcessMessage {
+ #-------------------------------------------------------------
+ # process a received message
+ #
+ # parameter(s): none
+ # depends on function(s): GetMessage, GetMessageHeaders, GetGpgMessage, GetGpgDecryptStderr,
+ # GetSubscribersList, SendListMessage, SendWarningMessage, SendBounceMessage
+ # returns: 0 on success
+ #-------------------------------------------------------------
- # 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 (get_gpg_stderr | grep -Fq GOODSIG); then
-
- for EMAIL in $(get_subscribers_list); do
- message_list $EMAIL
+ local email
+
+ GetMessage
+ GetMessageHeaders
+ GetGpgMessage
+
+ # if signature in message is valid, encrypt and send it for each list subscriber
+ if GetGpgDecryptStderr | grep -q '^\[GNUPG:] GOODSIG'; then
+ for email in $(GetSubscribersList); do
+ SendListMessage $email
done
-
- # else, if signature is BAD, email it back to the list admins and to sender
- elif (get_gpg_stderr | grep -Fq BADSIG) ; then
-
- for EMAIL in $(echo $LISTADMIN $FROMADD); do
- message_list_error $EMAIL
+
+ # else, if signature is invalid, email 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
-
- # else, probably either the message was not signed or the sender is not subscribed to the list
- # email the message back to sender including a note about this
+
+ # 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
- message_list_return $FROMADD
+ SendBounceMessage $SENDER_ADDRESS
fi
-
+
}
-function newlist {
- # create a list if it doesnt already exist
- if [ ! -d "$CONFIG_PATH" ]; then
- echo creating folder $CONFIG_PATH...
- mkdir "$CONFIG_PATH" # || (echo "error creating $CONFIG_PATH: installation aborted"; exit 1)
+
+function NewList {
+ #-------------------------------------------------------------
+ # create a list if it doesn't exist yet
+ #
+ # parameter(s): none
+ # depends on function(s): DeclareGpgVars
+ # returns: 0 on success, 1 if list already exists or cannot be created
+ #-------------------------------------------------------------
+
+ if [ ! -d "$LIST_PATH" ]; then
+ echo creating folder $LIST_PATH...
+ mkdir "$LIST_PATH" # || (echo "$(basename $0): error creating $LIST_PATH: installation aborted"; exit 1)
echo "creating list config file and will ask some questions."
- read -p "path to smtp command (eg, /usr/sbin/sendmail): " MAIL
- read -p "command-line arguments passed to the smtp wrapper (eg, -oem -oi -t): " MAIL_ARGS
- read -p "path to gpg binary (eg, /usr/bin/gpg): " GPG
+ # 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
- # if [ ! -x $GPG ]; then
+ # if [ ! -x "$GPG_BINARY" ]; then
- read -p "list keyring folder (defaults to $GPGDIR): " GPGDIR
+ read -rep "list keyring folder: " LIST_HOMEDIR # removed: (defaults to $LIST_HOMEDIR)
# todo: please no utf-8 (see DETAILS)
- read -p "list email (eg, firma@domain.tld): " LISTNAME
- read -p "list admins emails (space delimited): " LISTADMIN
- read -p "list description (fake?): " DESCRIPTION
- read -p "password for list keyring (use a huge one): " PASSWD
+ read -rep "list email (eg, 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
# todo: key specs (size, expiry date...)
echo "creating your config..."
- touch $CONFIG
- chown root.root $CONFIG
- chmod 600 $CONFIG
- if [ -f $CONFIG ]; then
- gpg_args
- echo -e "MAIL=$MAIL\nGPG=$GPG\nGPGDIR=$GPGDIR\nLISTNAME=$LISTNAME\nLISTADMIN=$LISTADMIN\nPASSWD=$PASSWD" > $CONFIG
+ touch $LIST_CONFIG_FILE
+ chown root.root $LIST_CONFIG_FILE
+ 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
echo "now generating your keyring..."
- $GPGCOMMAND --gen-key <<EOF
+ $GPG --gen-key <<EOF
Key-Type: DSA
Key-Length: 1024
Subkey-Type: ELG-E
Subkey-Length: 1024
- Name-Real: $DESCRIPTION
- Name-Email: $LISTNAME
+ Name-Real: $DESCRIPTION
+ Name-Email: $LIST_ADDRESS
Expire-Date: 0
- Passphrase: $PASSWD
+ Passphrase: $PASSPHRASE
%commit
EOF
fi
else
- echo error creating $CONFIG_FILE: list already exists
- exit 1
+ echo "$(basename $0): cannot create $LIST_NAME: List already exists"
+ exit 1
fi
}
-function gpg_args {
- # declare GPG variables
- GPGFLAGS="--quiet --homedir $GPGDIR --batch --no-tty --no-use-agent --no-permission-warning"
- GPGCOMMAND="$GPG $GPGFLAGS"
- GPGLIST="$GPGCOMMAND --list-keys --with-colons"
- GPGDECRYPT="$GPGCOMMAND --passphrase-fd 0 --decrypt"
- 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 ListAdministration {
+ #-------------------------------------------------------------
+ # process administrative tasks
+ #
+ # parameter(s): expects task to be performed (plus its argument(s)) from STDIN
+ # depends on function(s): ChooseUid
+ # returns: 0 if task is executed successfully,
+ # 1 if task can't be executed (command not found, too many/missing arguments, etc.)
+ #-------------------------------------------------------------
+
+ case $# in
+ 1)
+ case $1 in
+ help)
+ echo
+ echo " quit quit this prompt"
+ echo " help show this help"
+ echo " use EMAIL-ADDRESS use the given address for message delivery instead"
+ echo " of the primary address on key"
+ echo
+ ;;
+ quit)
+ exit 0
+ ;;
+ use)
+ echo >&2 "$1: missing arguments (try \"help\")"
+ return 1
+ ;;
+ *)
+ echo >&2 "Command not found -- $1 (try \"help\")"
+ return 1
+ ;;
+ esac
+ ;;
+ 2)
+ case $1 in
+ use)
+ # check if argument is an email address
+ if [[ -z "$(echo -ne $2 | grep -o '[^@]\+@[^@]\+')" ]]; then
+ echo >&2 "$1: invalid argument -- $2 (try \"help\")"
+ return 1
+ else
+ ChooseUid $2
+ fi
+ ;;
+ help|quit)
+ echo >&2 "$1: too many arguments -- $@ (try \"help\")"
+ return 1
+ ;;
+ *)
+ echo >&2 "Command not found -- $1 (try \"help\")"
+ return 1
+ ;;
+ esac
+ ;;
+ *)
+ case $1 in
+ help|quit|use)
+ echo >&2 "$1: too many arguments -- $@ (try \"help\")"
+ return 1
+ ;;
+ *)
+ echo >&2 "Command not found -- $1 (try \"help\")"
+ return 1
+ ;;
+ esac
+ ;;
+ esac
}
-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."
+function ChooseUid {
+ #-------------------------------------------------------------
+ # choose which UID of a public key should be used for message delivery,
+ # deleting all other UIDs on this key
+ #
+ # parameter(s): chosen email address
+ # depends on function(s): DeclareGpgVars
+ # returns: 0 on success,
+ # 1 if task can't be executed (public key not found, only one UID on key, etc.)
+ #-------------------------------------------------------------
+
+ local keyid="$($GPG_LIST_KEYS --with-fingerprint $1 2> /dev/null | grep ^fpr | cut -d : -f 10 | grep -o '.\{8\}$')"
+ local uid_count="$($GPG_LIST_KEYS --fixed-list-mode $keyid 2> /dev/null | grep ^uid | wc -l)"
+ local chosen_uid_number="$($GPG_LIST_KEYS --fixed-list-mode $keyid 2> /dev/null | grep ^uid | grep -ni $1 | cut -d : -f 1)"
+
+ # check if supplied address is associated with a public key
+ if [[ -z "$($GPG_LIST_KEYS --fixed-list-mode $1 2> /dev/null | grep -i "<$1>:$")" ]]; then
+ echo >&2 "use: \"$(echo -ne $1 | tr '[:upper:]' '[:lower:]')\" 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\"."
+ # then check if there's more than one UID on this public key
+ elif [[ "$($GPG_LIST_KEYS --fixed-list-mode $1 2> /dev/null | grep ^uid | wc -l)" -eq "1" ]]; then
+ echo >&2 "use: \"$(echo -ne $1 | tr '[:upper:]' '[:lower:]')\" 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."
+ # and then check if there's only one public key associated with this address
+ elif [[ "$($GPG_LIST_KEYS --fixed-list-mode $1 2> /dev/null | grep -i "<$1>:$" | wc -l)" -gt "1" ]]; then
+ echo >&2 "use: \"$(echo -ne $1 | tr '[:upper:]' '[:lower:]')\" is listed in more than one UID on this keyring."
+ echo >&2 "Delete all but one of the public keys or UIDs associated with this email address."
return 1
fi
+ # if all checks are OK, run the expect script bellow
expect -nN -- << EOF
+ # no output to STDOUT
log_user 0
+ # set a 5 seconds timeout in case anything goes wrong
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
+ # call gpg with the "--edit-key" option
+ eval spawn -noecho $GPG --with-colons --command-fd 0 --status-fd 1 --edit-key $keyid
expect "GET_LINE keyedit.prompt" {
+ # select for deletion all UIDs other than the chosen one
set uid 1
- while { \$uid <= \$uid_count } {
- if { \$uid != \$chosen_uid } {
+ while { \$uid <= $uid_count } {
+ if { \$uid != $chosen_uid_number } {
send "uid \$uid\n"
expect "GET_LINE keyedit.prompt"
}
set uid [incr uid]
}
+ # delete selected UIDs
send "deluid\n"
+ # confirm deletion
expect "GET_BOOL keyedit.remove.uid.okay" {send "yes\n"}
+ # save and exit
expect "GET_LINE keyedit.prompt" {send "save\n"}
expect "GOT_IT"
}
+ # delay until the process above terminates
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
+ # send following message to user
+ send_user "use: \"$(echo -ne $1 | tr '[:upper:]' '[:lower:]')\" chosen for message delivery. [ expr $uid_count - 1 ] UID(s) deleted from public key \"$keyid\".\n"
+ exit
EOF
}
-# main -
-umask 0777
-export LANG=en_US
-USED_ARRAYS="MESSAGE GPG_MESSAGE LIST_MESSAGE"
-
-# declare all vars
-declare n
-for array in $USED_ARRAYS; do
- declare -a $array
-done
+#-------------------------------------------------------------
+# main()
+#-------------------------------------------------------------
+# set enviromental variables and options
export LANG=en_US
+umask 0077
-# command line checking
-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"
- CONFIG="$CONFIG_PATH/$2.conf"
-fi
-
-# if the configuration file exists, disable "sourcepath" and evaluate the parameters
-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
-
-# get gpg parameters and check the config
-if [ "$1" = "-a" -o "$1" = "-p" -o "$1" = "-r" ]; then
- gpg_args
- check_config
-fi
-
-# command line parsing
-if [ "$1" = "-a" ]; then
-
- declare -a ADMINCOMMANDS
+# declare global arrays used during execution
+GLOBAL_ARRAYS="ORIG_MESSAGE ORIG_MESSAGE_HEADERS ORIG_GPG_MESSAGE MESSAGE_BODY"
- n=0
- while read STDIN; do
- ADMINCOMMANDS[$n]="$STDIN\n"
- ((n++))
- done
+for ARRAY in $GLOBAL_ARRAYS; do
+ declare -a $ARRAY
+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]}")
+# command line parsing:
+# first check number of arguments, then check what was entered
+# start main case
+case $# in
+ 0)
+ echo >&2 "$(basename $0): missing arguments"
+ Usage
+ exit 1
+ ;;
+ 1)
+ # start case #1
+ case $1 in
+ -h|--help)
+ Usage
+ ;;
+ -v|--version)
+ Version
+ ;;
+ # valid option called without its required argument
+ -a|--admin-task|-c|--create-newlist|-p|--process-message|-r|--list-request)
+ echo >&2 "$(basename $0): missing arguments"
+ Usage
+ exit 1
+ ;;
+ *)
+ echo >&2 "$(basename $0): invalid option -- $1"
+ Usage
+ exit 1
+ ;;
+ # end case #1
+ esac
+ ;;
+ 2)
+
+ # if firma.conf exists, evaluate its parameters and check them
+ if [ -f "$FIRMA_CONFIG_FILE" ]; then
+ shopt -u sourcepath && source "$FIRMA_CONFIG_FILE"
+ CheckFirmaConfigFile
+ else
+ echo >&2 "$(basename $0): cannot source \`$FIRMA_CONFIG_FILE': No such file"
+ exit 1
fi
- done
-elif [ "$1" = "-c" ]; then
- newlist
-elif [ "$1" = "-h" ]; then
- usage
-elif [ "$1" = "-p" ]; then
- process_message
-elif [ "$1" = "-r" ]; then
- list_request
-elif [ "$1" = "-v" ]; then
- version
-else
- usage
- exit 1
-fi
-
-# un-declare all vars
-declare n
-for array in $USED_ARRAYS; do
- unset $array
+ LIST_NAME="$2"
+ LIST_PATH="$LISTS_DIR/$LIST_NAME"
+ LIST_CONFIG_FILE="$LIST_PATH/$LIST_NAME.conf"
+
+ # start case #2
+ # branch directly to options which don't use a configuration
+ #+file or, for those which do, branch to a new case bellow
+ case $1 in
+ -c|--create-newlist)
+ NewList
+ ;;
+ -a|--admin-task|-p|--process-message|-r|--list-request)
+
+ # if the list configuration file exists, disable "sourcepath"
+ #+and evaluate list parameters
+ if [[ -f "$LIST_CONFIG_FILE" ]]; then
+ shopt -u sourcepath && source "$LIST_CONFIG_FILE"
+ else
+ echo >&2 "$(basename $0): cannot source \`$LIST_CONFIG_FILE': No such file"
+ exit 1
+ fi
+
+ # get gpg parameters and check the list configuration file
+ DeclareGpgVars
+ CheckListConfigFile
+
+ # start case #3
+ case $1 in
+ -a|--admin-task)
+
+ # read STDIN and, if line is not empty or commented, process command
+ while read -rep "Command> " STDIN; do
+ if [[ "$STDIN" && "$STDIN" != "#"* ]]; then
+ ListAdministration $STDIN
+ fi
+ done
+
+ ;;
+ -p|--process-message)
+ ProcessMessage
+ ;;
+ -r|--list-request)
+ # not implemented yet
+ #ListRequest
+ exit 0
+ ;;
+ # end case #3
+ esac
+ ;;
+ # valid option called with too many arguments
+ -h|--help|-v|--version)
+ echo >&2 "$(basename $0): too many arguments -- $@"
+ Usage
+ exit 1
+ ;;
+ *)
+ echo >&2 "$(basename $0): invalid option -- $1"
+ Usage
+ exit 1
+ ;;
+ # end case #2
+ esac
+ ;;
+ *)
+ # start case #4
+ case $1 in
+ # again, valid option called with too many arguments
+ -a|--admin-task|-c|--create-newlist|-h|--help|-p|--process-message|-r|--list-request|-v|--version)
+ echo >&2 "$(basename $0): too many arguments -- $@"
+ Usage
+ exit 1
+ ;;
+ *)
+ echo >&2 "$(basename $0): invalid option -- $1"
+ Usage
+ exit 1
+ ;;
+ # end case #4
+ esac
+ ;;
+# end main case
+esac
+
+# erase all global arrays
+for ARRAY in $GLOBAL_ARRAYS; do
+ unset $ARRAY
done