aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorluis <luis>2006-07-28 16:00:27 +0000
committerluis <luis>2006-07-28 16:00:27 +0000
commit8430641d0153b761eb42416b55e309e511af45bf (patch)
tree8704c2588fb0a849f8cdd47ae094b4a34b1077cc
parent8795f18c414a00967d37652b774e98b8553e8d7b (diff)
downloadfirma-8430641d0153b761eb42416b55e309e511af45bf.tar.gz
firma-8430641d0153b761eb42416b55e309e511af45bf.tar.bz2
Again, lots of changes. Please, wait for the changelog.
-rwxr-xr-xfirma809
1 files changed, 498 insertions, 311 deletions
diff --git a/firma b/firma
index b089e96..866a39e 100755
--- a/firma
+++ b/firma
@@ -33,26 +33,38 @@
#
# And it may contain the following optional parameters:
#
-# LOG_TO_SYSLOG= set to "1" to log fatal errors to syslog, else firma will
-# print errors to STDERR
-# LOGGER= if logging to syslog, set the path to logger's binary
+# LOG_TO_SYSLOG= set to "1" to log errors and warnings to syslog, else firma
+# will print errors to STDERR
+# LOGGER_BINARY= if logging to syslog, set the path to logger's binary
# SYSLOG_PRIORITY= if logging to syslog, set a priority for the error messages
# (defaults to "user.err")
# 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_TO_ALL_AT_ONCE= 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.)
+# option, available from version 1.4.0 onwards
+# (try 'man gpg' for more information)
+# REMOVE_THESE_HEADERS_ON_ALL_LISTS= headers that should be stripped from list
+# messages on all lists running under firma
+# (space separated case-insensitive entries)
+# (may include regexps (e.g., X-.*)
+# SEND_BOUNCE_MESSAGES= set to '1' to send bounces back to sender when an
+# invalid message is received (message not signed/
+# encrypted, sender not subscribed to the list, etc.)
#
# And the list configuration file should contain:
#
# LIST_ADDRESS= list's email address
# LIST_ADMIN= list's administrators email addresses (space separated)
-# SUBJECT_PREFIX= prefix to be included in the subject of list messages, if any
# LIST_HOMEDIR= list's GnuPG homedir, where the list's keyrings are located
# PASSPHRASE= passphrase for the list's private keyring
#
+# And it may contain the following optional parameters:
+#
+# SUBJECT_PREFIX= prefix to be included in the subject of list messages
+# REMOVE_THESE_HEADERS= headers that should be stripped from list messages
+# (space separated case-insensitive entries)
+# (may include regexps (e.g., X-.*)
+# REPLIES_SHOULD_GO_TO_LIST= set to '1' to add a Reply-To header containing the
+# list address
+#
# 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
@@ -113,7 +125,7 @@ for more details."
function DeclareGpgVars {
#-------------------------------------------------------------
- # declare GPG-related global variables
+ # declare gpg-related global variables
#
# parameter(s): none
# depends on function(s): none
@@ -133,27 +145,32 @@ function CheckFirmaConfigFile {
#
# parameter(s): none
# depends on function(s): none
- # returns: 0 if all checks are passed, 1 if any check fails
+ # returns: 0 if all checks are passed,
+ # 1 on any fatal errors
#-------------------------------------------------------------
local -i return_code=0
if [[ ! -f "$GPG_BINARY" || ! -x "$GPG_BINARY" ]]; then
- ERROR_MESSAGE="GPG binary ("$GPG_BINARY") could not be found"
+ ERROR_MESSAGE="FATAL: GPG binary ("$GPG_BINARY") could not be found. Quitting."
return_code=1
elif [[ ! -f "$MAIL_AGENT" || ! -x "$MAIL_AGENT" ]]; then
- ERROR_MESSAGE="Mail transport agent binary ("$MAIL_AGENT") could not be found"
+ ERROR_MESSAGE="FATAL: Mail transport agent binary ("$MAIL_AGENT") could not be found. Quitting."
return_code=1
elif [[ ! -d "$LISTS_DIR" ]]; then
- ERROR_MESSAGE="Lists directory ("$LISTS_DIR") could not be found"
+ ERROR_MESSAGE="FATAL: Lists directory ("$LISTS_DIR") could not be found. Quitting."
return_code=1
elif [[ "$USE_GPG_HIDDEN_RECIPIENT_OPTION" == "1" && "$($GPG_BINARY --version | head -n1 | tr -dc '[[:digit:]]')" -lt "140" ]]; then
- ERROR_MESSAGE="GPG's \"--hidden-recipient\" option is only available from version 1.4.0 onwards"
- return_code=1
+ ERROR_MESSAGE="\
+WARNING: GPG's \"--hidden-recipient\" option is only available from version 1.4.0 onwards.
+WARNING: Setting USE_GPG_HIDDEN_RECIPIENT_OPTION to '0'."
+ USE_GPG_HIDDEN_RECIPIENT_OPTION=0
elif [[ "$LOG_TO_SYSLOG" == "1" ]]; then
- if [[ ! -f "$LOGGER" || ! -x "$LOGGER" ]]; then
- ERROR_MESSAGE="logger binary ("$LOGGER") could not be found"
- return_code=1
+ if [[ ! -f "$LOGGER_BINARY" || ! -x "$LOGGER_BINARY" ]]; then
+ ERROR_MESSAGE="\
+WARNING: logger binary ("$LOGGER_BINARY") could not be found.
+WARNING: Setting LOG_TO_SYSLOG to '0'."
+ LOG_TO_SYSLOG=0
fi
fi
@@ -167,35 +184,41 @@ function CheckListConfigFile {
#
# parameter(s): none
# depends on function(s): DeclareGpgVars
- # returns: 0 if all checks are passed, 1 if any check fails
+ # returns: 0 if all checks are passed,
+ # 1 on any fatal errors
#-------------------------------------------------------------
local -i return_code=0
local administrator
+ local valid_admins
if [[ ! -d "$LIST_HOMEDIR" || ! -f "$LIST_HOMEDIR/pubring.gpg" || ! -f "$LIST_HOMEDIR/secring.gpg" ]]; then
- ERROR_MESSAGE="$LIST_NAME: GPG home directory ("$LIST_HOMEDIR") or the GPG keyrings could not be found"
- return_code=1
- elif [[ -z "$(grep -o "^PASSPHRASE='[^']*'$" $LIST_CONFIG_FILE)" || \
- -z "$PASSPHRASE" || \
- "$(echo "$PASSPHRASE" | wc -c)" -lt "25" || \
- -z "$(echo "$PASSPHRASE" | tr -dc '[[:lower:]]')" || \
- -z "$(echo "$PASSPHRASE" | tr -dc '[[:upper:]]')" || \
- -z "$(echo "$PASSPHRASE" | tr -dc '[[:digit:]]')" || \
- "$(echo "$PASSPHRASE" | tr -dc '[:punct:]' | wc -c)" -lt "5" || \
- "$(echo "$PASSPHRASE" | fold -w1 | uniq -cd | grep -v '^ \{6\}[234] ')" ]]; then
- ERROR_MESSAGE="$LIST_NAME: List passphrase is empty or does not meet the minimum complexity requirements"
+ ERROR_MESSAGE="FATAL: $LIST_NAME: GPG home directory ("$LIST_HOMEDIR") or the GPG keyrings could not be found. Quitting."
return_code=1
- elif [[ -z "$($GPG_LIST_KEYS --fixed-list-mode "<$LIST_ADDRESS>" 2> /dev/null | grep -v '^tru:')" ]]; then
- ERROR_MESSAGE="$LIST_NAME: Public key for list \"$(echo "$LIST_ADDRESS" | tr '[:upper:]' '[:lower:]')\" could not be found"
+# elif [[ -z "$(grep -o "^PASSPHRASE='[^']*'$" $LIST_CONFIG_FILE)" || \
+# -z "$PASSPHRASE" || \
+# "$(echo "$PASSPHRASE" | wc -c)" -lt "25" || \
+# -z "$(echo "$PASSPHRASE" | tr -dc '[[:lower:]]')" || \
+# -z "$(echo "$PASSPHRASE" | tr -dc '[[:upper:]]')" || \
+# -z "$(echo "$PASSPHRASE" | tr -dc '[[:digit:]]')" || \
+# "$(echo "$PASSPHRASE" | tr -dc '[:punct:]' | wc -c)" -lt "5" || \
+# "$(echo "$PASSPHRASE" | fold -w1 | uniq -cd | grep -v '^ \{6\}[234] ')" ]]; then
+# ERROR_MESSAGE="$LIST_NAME: List passphrase is empty or does not meet the minimum complexity requirements"
+# return_code=1
+ elif [[ -z "$($GPG --list-secret-keys --with-colons --fixed-list-mode "<$LIST_ADDRESS>" 2> /dev/null)" ]]; then
+ ERROR_MESSAGE="FATAL: $LIST_NAME: Secret key for list "$LIST_ADDRESS" could not be found. Quitting."
return_code=1
else
for administrator in $LIST_ADMIN; do {
if [[ -z "$($GPG_LIST_KEYS --fixed-list-mode "<$administrator>" 2> /dev/null | grep -v '^tru:')" ]]; then
- ERROR_MESSAGE="$LIST_NAME: Public key for list administrator \"$(echo "$administrator" | tr '[:upper:]' '[:lower:]')\" could not be found"
- return_code=1
+ ERROR_MESSAGE="\
+WARNING: $LIST_NAME: Public key for list administrator "$administrator" could not be found.
+WARNING: $LIST_NAME: Removing this address from LIST_ADMIN."
+ else
+ valid_admins="$valid_admins $administrator"
fi; }
done
+ LIST_ADMIN="$valid_admins"
fi
return $return_code
@@ -206,9 +229,10 @@ function GetMessage {
#-------------------------------------------------------------
# read message from STDIN
#
- # parameter(s): expects message from STDIN
+ # parameter(s): none
# depends on function(s): none
- # returns: 0 on success, 1 if there's no input
+ # returns: 0 on success,
+ # 1 if there's no input
#-------------------------------------------------------------
local -i return_code=0
@@ -217,7 +241,7 @@ function GetMessage {
ORIG_MESSAGE="$(sed -ne '1,$p')"
# check if message was successfully stored
- if (( "${#ORIG_MESSAGE[*]}" == 0 )); then
+ if [[ -z "$ORIG_MESSAGE" ]]; then
ERROR_MESSAGE="Message couldn't be read from standard input"
return_code=1
fi
@@ -226,70 +250,44 @@ function GetMessage {
}
-function GetMessageHeaders {
- #-------------------------------------------------------------
- # get the 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 the message
- #-------------------------------------------------------------
-
- local -i return_code=0
- local -i first_blank_line
-
- # find the first blank line in the message
- first_blank_line=$(echo "$ORIG_MESSAGE" | grep -nm 1 '^$' | cut -d : -f 1)
-
- # store everything up to this line in ORIG_MESSAGE_HEADERS
- if [[ "$first_blank_line" != 0 ]]; then
- ORIG_MESSAGE_HEADERS="$(echo "$ORIG_MESSAGE" | sed -ne "1,${first_blank_line}p" | sed -e :a -e '$!N;s/[ \t]*\n[ \t]\+/ /;ta' -e 'P;D')"
- fi
-
- # check if message headers could not be stored successfully
- if (( "${#ORIG_MESSAGE_HEADERS[*]}" == 0 )); then
- ERROR_MESSAGE="Message headers could not be located within this message"
- return_code=1
- # else, list ORIG_MESSAGE_HEADERS and get some specific headers for later use
- else
- FROM=$(echo "$ORIG_MESSAGE_HEADERS" | grep -im 1 '^From:' | cut -d : -f 2- | sed -e 's/^[ \t]\+//' -e 's/[ \t]\+$//')
- SENDER_ADDRESS=$(if [[ -z "$(echo $FROM | grep '>$')" ]]; then echo $FROM; else echo $FROM | grep -o '<[^<>]*>$' | sed -e 's/[<>]//g'; fi)
- SUBJECT=$(echo "$ORIG_MESSAGE_HEADERS" | grep -im 1 '^Subject:' | cut -d : -f 2- | sed -e 's/^[ \t]\+//' -e 's/[ \t]\+$//')
- CONTENT_HEADERS=$(echo "$ORIG_MESSAGE_HEADERS" | sed -ne '/^Content-/p')
- if [[ -n "$CONTENT_HEADERS" ]]; then
- CONTENT_TYPE_SUBTYPE=$(echo "$CONTENT_HEADERS" | grep -iom 1 '^Content-Type:[^;]*' | cut -d : -f 2 | sed -e 's/^[ \t]\+//' -e 's/[ \t]\+$//')
- fi
- fi
-
- return $return_code
-}
-
-
function GetGpgMessage {
#-------------------------------------------------------------
# get the gpg encrypted part of the message
#
# parameter(s): none
# depends on function(s): GetMessage
- # returns: 0 on success, 1 if encrypted bloc can't be located within the message
+ # returns: 0 on success,
+ # 1 if encrypted bloc can't be located within the message
#-------------------------------------------------------------
local -i return_code=0
- local -i encrypted_bloc_begins
- local -i encrypted_bloc_ends
-
- # find the beginning and the end of the encrypted bloc, if any
- encrypted_bloc_begins=$(echo "$ORIG_MESSAGE" | grep -nm 1 -- '-----BEGIN PGP MESSAGE-----' | cut -d : -f 1)
- encrypted_bloc_ends=$(echo "$ORIG_MESSAGE" | grep -nm 1 -- '-----END PGP MESSAGE-----' | cut -d : -f 1)
- # if there's an encrypted bloc, store it in ORIG_GPG_MESSAGE
- if [[ "$encrypted_bloc_begins" != 0 && "$encrypted_bloc_ends" != 0 ]]; then
- ORIG_GPG_MESSAGE="$(echo "$ORIG_MESSAGE" | sed -ne "${encrypted_bloc_begins},${encrypted_bloc_ends}p")"
+ # find the first blank line in the message
+ FIRST_BLANK_LINE=$(echo "$ORIG_MESSAGE" | grep -nm 1 '^$' | cut -d : -f 1)
+
+ # then, find the beginning of the encrypted bloc
+ if [[ -n $FIRST_BLANK_LINE ]]; then
+ ENCRYPTED_BLOC_BEGINS=$(echo "$ORIG_MESSAGE" | sed -ne "$(($FIRST_BLANK_LINE + 1)),\$p" | grep -nm 1 -- '^-----BEGIN PGP MESSAGE-----' | cut -d : -f 1)
+
+ # and then find the end of the bloc
+ if [[ -n $ENCRYPTED_BLOC_BEGINS ]]; then
+ ENCRYPTED_BLOC_BEGINS=$(($ENCRYPTED_BLOC_BEGINS + $FIRST_BLANK_LINE))
+ ENCRYPTED_BLOC_ENDS=$(echo "$ORIG_MESSAGE" | sed -ne "$((ENCRYPTED_BLOC_BEGINS + 1)),\$p" | grep -nm 1 -- '^-----END PGP MESSAGE-----' | cut -d : -f 1)
+
+ # if there's an encrypted bloc, store it in ORIG_GPG_MESSAGE
+ if [[ -n $ENCRYPTED_BLOC_ENDS ]]; then
+ ENCRYPTED_BLOC_ENDS=$(($ENCRYPTED_BLOC_ENDS + $ENCRYPTED_BLOC_BEGINS))
+ ORIG_GPG_MESSAGE="$(
+ echo "$ORIG_MESSAGE" | \
+ sed -ne "$((${ENCRYPTED_BLOC_ENDS} + 1))q;${ENCRYPTED_BLOC_BEGINS},${ENCRYPTED_BLOC_ENDS}p"
+ )"
+ fi
+ fi
fi
# check if the bloc was successfully stored
- if (( "${#ORIG_GPG_MESSAGE[*]}" == 0 )); then
- ERROR_MESSAGE="No valid GPG encrypted bloc found within this message"
+ if [[ -z "$ORIG_GPG_MESSAGE" ]]; then
+ ERROR_MESSAGE="No valid GPG encrypted bloc found within the message"
return_code=1
fi
@@ -297,19 +295,64 @@ function GetGpgMessage {
}
-function GetGpgDecryptStderr {
+function ParseGpgDecryptStderr {
#-------------------------------------------------------------
- # discard $GPG_DECRYPT STDOUT and get its STDERR for signature checking
+ # parse $GPG_DECRYPT 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.)
+ # returns: 0
#-------------------------------------------------------------
- GPG_DECRYPT_STDERR="$(echo -e "${PASSPHRASE}\n${ORIG_GPG_MESSAGE}" | ($GPG_DECRYPT --status-fd 2 1> /dev/null) 2>&1 )"
+ local gpg_decrypt_stderr
+
+ # get GPG_DECRYPT STDERR, discarding its STDOUT
+ gpg_decrypt_stderr="$(
+ echo -e "${PASSPHRASE}\n${ORIG_GPG_MESSAGE}" | \
+ ($GPG_DECRYPT --status-fd 2 1> /dev/null) 2>&1
+ )"
+
+ # check if message was encrypted with the list's public key
+ if
+ echo "$gpg_decrypt_stderr" | \
+ grep -q "^\[GNUPG:] ENC_TO $(
+ $GPG_LIST_KEYS $LIST_ADDRESS | \
+ sed -ne '/^sub:[^ired]:/p' -e '/:[sca]*[^e][sca]*:$/d' | \
+ cut -d : -f 5
+ )"
+ then
+ ENCRYPTED_TO_LIST=1
+
+ # if it was, check if its signature is valid
+ if
+ echo "$gpg_decrypt_stderr" | \
+ grep -q '^\[GNUPG:] GOODSIG'
+ then
+ GOOD_SIGNATURE=1
+
+ # else, check if the signature is invalid (BAD signature)
+ elif
+ echo "$gpg_decrypt_stderr" | \
+ grep -q '^\[GNUPG:] BADSIG'
+ then
+ BAD_SIGNATURE=1
+
+ # else, check if the signature couldn't be verified
+ elif
+ echo "$gpg_decrypt_stderr" | \
+ grep -q '^\[GNUPG:] ERRSIG'
+ then
+ SIGNATURE_CHECKING_FAILED=1
+
+ # else, check if the message could at least be decrypted
+ elif
+ echo "$gpg_decrypt_stderr" | \
+ grep -q '^\[GNUPG:] DECRYPTION_OKAY'
+ then
+ MESSAGE_DECRYPTION_OKAY=1
+ fi
+ fi
}
@@ -319,23 +362,26 @@ function GetSubscribersList {
#
# parameter(s): none
# depends on function(s): DeclareGpgVars
- # returns: 0 on success, 1 if there are no valid subscribers on list
+ # returns: 0 on success,
+ # 1 if there are no valid subscribers on list
#-------------------------------------------------------------
local -i return_code=0
- # get subscribers' email addresses, excluding invalid, revoked, expired and disabled keys,
- #+as well as any signing only keys
- SUBSCRIBERS_LIST="$($GPG_LIST_KEYS 2> /dev/null | \
- sed -ne "/$LIST_ADDRESS/Id" -e '/^pub:[ired]:/d' -e '/:[^:]*D[^:]*:[^:]*$/d' -e '/:[^E:]*:[^:]*$/d' -e '/^pub/p' | \
+ # get subscribers' email addresses, excluding invalid, revoked,
+ #+expired and disabled keys, as well as any signing only keys
+ SUBSCRIBERS_LIST="$(
+ $GPG_LIST_KEYS 2> /dev/null | \
+ sed -ne "/$LIST_ADDRESS/Id" -e '/:[scaeSCA]*[^E][scaeSCA]*:$/d' -e '/:[scaeSCAE]*D[scaeSCAE]*:$/d' -e '/^pub:[^ired]:/p' | \
cut -d : -f 10 | \
grep -o '<[^<>]*>$' | \
sed -e 's/[<>]//g' | \
- sort -d)"
+ sort -d
+ )"
# check if the list has valid subscribers
- if (( "${#SUBSCRIBERS_LIST[*]}" == 0 )); then
- ERROR_MESSAGE="$LIST_NAME: No valid subscribers on list \"$(echo "$LIST_ADDRESS" | tr '[:upper:]' '[:lower:]')\""
+ if [[ -z "$SUBSCRIBERS_LIST" ]]; then
+ ERROR_MESSAGE="FATAL: $LIST_NAME: No valid subscribers on list \"$LIST_ADDRESS\""
return_code=1
fi
@@ -343,225 +389,311 @@ function GetSubscribersList {
}
-function DecryptGpgMessage {
+function GetMessageHeadersAndBody {
#-------------------------------------------------------------
- # decrypt the gpg encrypted part of the message
+ # store the message headers and body in two separate variables
#
# parameter(s): none
- # depends on function(s): DeclareGpgVars, GetGpgMessage
+ # depends on function(s): GetMessage, GetGpgMessage
# returns: 0
#-------------------------------------------------------------
- DECRYPTED_MESSAGE="$(echo -e "${PASSPHRASE}\n${ORIG_GPG_MESSAGE}" | $GPG_DECRYPT 2> /dev/null)"
-
+ # store everything up to the first blank line in ORIG_MESSAGE_HEADERS,
+ #+unfolding any folded headers
+ ORIG_MESSAGE_HEADERS="$(
+ echo "$ORIG_MESSAGE" | \
+ sed -ne "${FIRST_BLANK_LINE}q;1,$(($FIRST_BLANK_LINE - 1))p" | \
+ sed -e :a -e '$!N;s/[ \t]*\n[ \t]\+/ /;ta' -e 'P;D'
+ )"
+
+ # store everything after this line in ORIG_MESSAGE_BODY
+ ORIG_MESSAGE_BODY="$(
+ echo "$ORIG_MESSAGE" | \
+ sed -ne "$(($FIRST_BLANK_LINE + 1)),\$p"
+ )"
}
-function MimeWrapMessage {
+function EditListMessageHeaders {
#-------------------------------------------------------------
- # MIME wrap message to be sent, adding needed headers and body parts
+ # edit the headers of a list message, removing specific lines, adding
+ #+a prefix to the Subject, etc
#
# parameter(s): none
- # depends on function(s): GetMessageHeaders, EncryptAndSendListMessage,
- # EncryptAndSendWarningMessage
+ # depends on function(s): GetMessageHeadersAndBody
# returns: 0
#-------------------------------------------------------------
- local boundary
+ local header
+ local sed_args
- if [[ "$CONTENT_TYPE_SUBTYPE" == "multipart/encrypted" ]]; then
- boundary="---------------firma$( date "+%s" | md5sum | cut -c 1-15 | tr '[:lower:]' '[:upper:]' )"
+ # remove headers as/if defined by firma configuration file
+ if [[ -n "$REMOVE_THESE_HEADERS_ON_ALL_LISTS" ]]; then
+ for header in $REMOVE_THESE_HEADERS_ON_ALL_LISTS; do
+ sed_args="$sed_args -e /^${header}/Id"
+ done
- # these are the headers of the message to be sent, so no indentation here
- MESSAGE_HEADERS="\
-From: $FROM
-${RECIPIENTS}
-Reply-To: $LIST_ADDRESS
-Subject: ${SUBJECT_PREFIX}${SUBJECT}
-MIME-Version: 1.0
-Content-Type: multipart/encrypted;
- protocol=\"application/pgp-encrypted\";
- boundary=\"${boundary}\"
-Content-Disposition: inline"
+ MESSAGE_HEADERS="$(
+ echo "$ORIG_MESSAGE_HEADERS" | \
+ sed $sed_args
+ )"
+ fi
- # this is the body of the message to be sent, so no indentation here
- MESSAGE_BODY="\
-This is an OpenPGP/MIME encrypted message (RFC 2440 and 3156)
---$boundary
-Content-Type: application/pgp-encrypted
-Content-Description: PGP/MIME version identification
+ # remove additional headers as/if defined by the list configuration file
+ if [[ -n "$REMOVE_THESE_HEADERS" ]]; then
-Version: 1
+ # remove local variables contents, in case they have been used above
+ header=''
+ sed_args=''
---$boundary
-Content-Type: application/octet-stream; name=\"encrypted.asc\"
-Content-Disposition: inline; filename=\"encrypted.asc\"
-Content-Description: OpenPGP encrypted message
+ for header in $REMOVE_THESE_HEADERS; do
+ sed_args="$sed_args -e /^${header}/Id"
+ done
-${GPG_MESSAGE}
+ MESSAGE_HEADERS="$(
+ echo "$MESSAGE_HEADERS" | \
+ sed $sed_args
+ )"
+ fi
---${boundary}--"
+ # insert/replace the Reply-To header
+ if [[ -n "$REPLIES_SHOULD_GO_TO_LIST" ]]; then
- else
+ if ! echo "$MESSAGE_HEADERS" | \
+ grep -iq '^Reply-To:'; then
- # these are the headers of the message to be sent, so no indentation here
- MESSAGE_HEADERS="\
-From: $FROM
-${RECIPIENTS}
-Reply-To: $LIST_ADDRESS
-Subject: ${SUBJECT_PREFIX}${SUBJECT}\
-$(
- if [[ -n "${CONTENT_HEADERS}" ]]; then
- echo -e "\nMIME-Version: 1.0"
- echo "${CONTENT_HEADERS}" | sed -e 's/;/;\n /g'
- else
- echo -n
- fi
-)"
+ # these are the headers of the message to be sent, so no indentation here
+ MESSAGE_HEADERS="\
+$MESSAGE_HEADERS
+Reply-To: $LIST_ADDRESS"
- # this is the body of the message to be sent, so no indentation here
- MESSAGE_BODY="${GPG_MESSAGE}"
+ else
+ MESSAGE_HEADERS="$(
+ echo "$MESSAGE_HEADERS" | \
+ sed -e "s/^Reply-To:.*$/Reply-To: $LIST_ADDRESS/I"
+ )"
+ fi
fi
- # assemble entire message
- MESSAGE="\
-${MESSAGE_HEADERS}
+ # insert the Subject prefix, if any
+ if [[ -n "$SUBJECT_PREFIX" ]]; then
-${MESSAGE_BODY}"
+ # first, check if there's a Subject line
+ if echo "$MESSAGE_HEADERS" | grep -iq '^Subject:'; then
+
+ # and then check if the Subject already contains the list prefix
+ if ! echo "$MESSAGE_HEADERS" | \
+ grep -im 1 '^Subject:' | \
+ grep -qF "$SUBJECT_PREFIX"; then
+
+ # if it doesn't, insert it
+ MESSAGE_HEADERS="$(
+ echo "$MESSAGE_HEADERS" | \
+ sed -e "s/^Subject:[ \t]*/Subject: $SUBJECT_PREFIX/I"
+ )"
+ fi
+
+ # else, if there's no Subject line, add one containing only the list prefix
+ else
+
+ # these are the headers of the message to be sent, so no indentation here
+ MESSAGE_HEADERS="\
+$MESSAGE_HEADERS
+Subject: $SUBJECT_PREFIX"
+
+ fi
+ fi
}
-function EncryptAndSendListMessage {
+function DecryptGpgMessage {
#-------------------------------------------------------------
- # send the message to list members
+ # decrypt the gpg encrypted part of the message
#
# parameter(s): none
- # depends on function(s): DeclareGpgVars, DecryptGpgMessage, GetSubscribersList,
- # MimeWrapMessage
+ # depends on function(s): DeclareGpgVars, GetGpgMessage
# returns: 0
#-------------------------------------------------------------
- local subscriber
+ DECRYPTED_MESSAGE="$(
+ echo -e "${PASSPHRASE}\n${ORIG_GPG_MESSAGE}" | \
+ $GPG_DECRYPT 2> /dev/null
+ )"
+}
- # check if message should be encrypted to all subscribers at once
- if [[ "$USE_GPG_HIDDEN_RECIPIENT_OPTION" == 1 ]]; then
- GPG_MESSAGE="$(echo -e "${PASSPHRASE}\n${DECRYPTED_MESSAGE}" | \
- $GPG_ENCRYPT --group subscribers="$(echo $SUBSCRIBERS_LIST)" --hidden-recipient subscribers 2> /dev/null )"
+function ReplaceGpgMessage {
+ #-------------------------------------------------------------
+ # replace the original encrypted bloc by one generated for the list subscribers
+ #
+ # parameter(s): none
+ # depends on function(s): GetGpgMessage, GetMessageHeadersAndBody
+ # returns: 0
+ #-------------------------------------------------------------
- # check if message should be sent to all subscribers at once
- if [[ "$SEND_TO_ALL_AT_ONCE" == 1 ]]; then
+ MESSAGE_BODY="$(
+ echo "$ORIG_MESSAGE_BODY" | \
+ sed -e "$(($ENCRYPTED_BLOC_BEGINS - $FIRST_BLANK_LINE)),$(($ENCRYPTED_BLOC_ENDS - $FIRST_BLANK_LINE))c $(
+ echo "$GPG_MESSAGE" | \
+ sed -e '$! s/$/\\/'
+ )"
+ )"
+}
- RECIPIENTS="To: $(echo $SUBSCRIBERS_LIST | sed -e 's/ /,\n /g')"
- if echo "$SUBJECT" | grep -qF "$SUBJECT_PREFIX"; then
- SUBJECT_PREFIX=""
- fi
- MimeWrapMessage
- echo "$MESSAGE" | $MAIL_AGENT $MAIL_AGENT_ARGS
- # else, send the message to one subscriber at a time
- else
- for subscriber in $SUBSCRIBERS_LIST; do
+function GetSenderAddress {
+ #-------------------------------------------------------------
+ # get the sender address, needed for warning and bounce messages processing
+ #
+ # parameter(s): none
+ # depends on function(s): GetMessage
+ # returns: 0
+ #-------------------------------------------------------------
- RECIPIENTS="To: $subscriber"
- if echo "$SUBJECT" | grep -qF "$SUBJECT_PREFIX"; then
- SUBJECT_PREFIX=""
- fi
- MimeWrapMessage
- echo "$MESSAGE" | $MAIL_AGENT $MAIL_AGENT_ARGS
+ local from
- done
+ from=$(echo "$ORIG_MESSAGE" | grep -im 1 '^From:')
+ SENDER_ADDRESS=$(
+ if [[ -z "$(echo $from | grep '>$')" ]]; then
+ echo $from
+ else
+ echo $from | grep -o '<[^<>]*>$' | sed -e 's/[<>]//g'
fi
+ )
+}
- # else, message should be encrypted and sent to one subscriber at a time
- else
- for subscriber in $SUBSCRIBERS_LIST; do
- GPG_MESSAGE="$(echo -e "${PASSPHRASE}\n${DECRYPTED_MESSAGE}" | $GPG_ENCRYPT --recipient $subscriber )"
- RECIPIENTS="To: $subscriber"
- if echo "$SUBJECT" | grep -qF "$SUBJECT_PREFIX"; then
- SUBJECT_PREFIX=""
- fi
- MimeWrapMessage
- echo "$MESSAGE" | $MAIL_AGENT $MAIL_AGENT_ARGS
+function AssembleMessage {
+ #-------------------------------------------------------------
+ # just put the message headers and body together
+ #
+ # parameter(s): none
+ # depends on function(s): EditListMessageHeaders, ReplaceGpgMessage,
+ # ComposeAndSendWarningMessage,
+ # ComposeAndSendBounceMessage
+ # returns: 0
+ #-------------------------------------------------------------
+
+ # this is the actual message which will be sent, so no indentation here
+ MESSAGE="\
+$MESSAGE_HEADERS
+
+$MESSAGE_BODY"
- done
- fi
}
-function EncryptAndSendWarningMessage {
+function ReEncryptAndSendListMessage {
#-------------------------------------------------------------
- # send a "BAD signature" warning message to the list administrator(s) and to sender
+ # send message to list subscribers
#
# parameter(s): none
- # depends on function(s): DeclareGpgVars, GetMessageHeaders, DecryptGpgMessage,
- # MimeWrapMessage
+ # depends on function(s): DeclareGpgVars, DecryptGpgMessage,
+ # GetSubscribersList, AssembleMessage
# returns: 0
#-------------------------------------------------------------
- local email_address
+ local recipients
+ local subscriber
- # check if message should be encrypted to all addresses at once
- if [[ "$USE_GPG_HIDDEN_RECIPIENT_OPTION" == 1 ]]; then
+ recipients="$(echo $SUBSCRIBERS_LIST)"
- GPG_MESSAGE="$(echo -e "${PASSPHRASE}\n$DECRYPTED_MESSAGE" | \
- $GPG_ENCRYPT --group admin_and_sender="$LIST_ADMIN $SENDER_ADDRESS" --hidden-recipient admin_and_sender 2> /dev/null)"
+ # check if message should be encrypted and sent to all subscribers at once
+ if [[ "$USE_GPG_HIDDEN_RECIPIENT_OPTION" == 1 ]]; then
- # check if message should be sent to all addresses at once
- if [[ "$SEND_TO_ALL_AT_ONCE" == 1 ]]; then
+ GPG_MESSAGE="$(
+ echo -e "${PASSPHRASE}\n${DECRYPTED_MESSAGE}" | \
+ $GPG_ENCRYPT --group subscribers="$recipients" --hidden-recipient subscribers 2> /dev/null
+ )"
- RECIPIENTS="To: $(echo "$LIST_ADMIN $SENDER_ADDRESS" | sed -e 's/ /,\n /g' )"
- SUBJECT_PREFIX="[BAD SIGNATURE] "
- MimeWrapMessage
- echo "$MESSAGE" | $MAIL_AGENT $MAIL_AGENT_ARGS
+ ReplaceGpgMessage
+ AssembleMessage
- # else, send the message to one address at a time
- else
- for email_address in $LIST_ADMIN $SENDER_ADDRESS; do
+ # send message
+ echo "$MESSAGE" | $MAIL_AGENT $MAIL_AGENT_ARGS $recipients
- RECIPIENTS="To: $email_address"
- SUBJECT_PREFIX="[BAD SIGNATURE] "
- MimeWrapMessage
- echo "$MESSAGE" | $MAIL_AGENT $MAIL_AGENT_ARGS
+ # else, message should be encrypted and sent to one subscriber at a time
+ else
+ for subscriber in $recipients; do
- done
- fi
+ GPG_MESSAGE="$(
+ echo -e "${PASSPHRASE}\n${DECRYPTED_MESSAGE}" | \
+ $GPG_ENCRYPT --recipient $subscriber
+ )"
- # else, message should be encrypted and sent to one address at a time
- else
- for email_address in $LIST_ADMIN $SENDER_ADDRESS; do
+ ReplaceGpgMessage
+ AssembleMessage
- GPG_MESSAGE="$(echo -e "${PASSPHRASE}\n$DECRYPTED_MESSAGE" | $GPG_ENCRYPT --recipient $email_address 2> /dev/null )"
- RECIPIENTS="To: $email_address"
- SUBJECT_PREFIX="[BAD SIGNATURE] "
- MimeWrapMessage
- echo "$MESSAGE" | $MAIL_AGENT $MAIL_AGENT_ARGS
+ # send message
+ echo "$MESSAGE" | $MAIL_AGENT $MAIL_AGENT_ARGS $subscriber
done
fi
}
-function SendBounceMessage {
+function ComposeAndSendWarningMessage {
#-------------------------------------------------------------
- # send a bounce message back to sender
+ # send a "BAD signature" warning to the list administrator(s) and to sender
#
# parameter(s): none
- # depends on function(s): GetMessageHeaders, ProcessMessage
+ # depends on function(s): GetMessage, GetSenderAddress, AssembleMessage
# returns: 0
#-------------------------------------------------------------
- # this is the body of the message to be sent, so no indentation here
- echo "\
+ local recipients
+
+ recipients="$LIST_ADMIN $SENDER_ADDRESS"
+
+ # these are the headers of the message to be sent, so no indentation here
+ MESSAGE_HEADERS="\
From: $LIST_ADDRESS
+Subject: BAD signature from $SENDER_ADDRESS
To: $SENDER_ADDRESS
-Subject: [RETURNED MAIL] $SUBJECT
+Cc: $(echo $LIST_ADMIN | sed -e 's/ /, /g')"
+
+ # this is the body of the message to be sent, so no indentation here
+ MESSAGE_BODY="\
+-------- Original Message --------
+$ORIG_MESSAGE"
+
+ AssembleMessage
+
+ # send message
+ echo "$MESSAGE" | $MAIL_AGENT $MAIL_AGENT_ARGS $recipients
+}
+
+
+function ComposeAndSendBounceMessage {
+ #-------------------------------------------------------------
+ # send a bounce message back to sender
+ #
+ # parameter(s): none
+ # depends on function(s): GetMessage, GetSenderAddress, AssembleMessage
+ # returns: 0
+ #-------------------------------------------------------------
+
+ local subject
+
+ subject="$(echo "$ORIG_MESSAGE" | grep -im 1 '^Subject:' | cut -d : -f 2- )"
+ # these are the headers of the message to be sent, so no indentation here
+ MESSAGE_HEADERS="\
+From: $LIST_ADDRESS
+Subject: [RETURNED MAIL]$subject
+To: $SENDER_ADDRESS"
+
+ # this is the body of the message to be sent, so no indentation here
+ MESSAGE_BODY="\
$MESSAGE_BODY
--
-firma v$VERSION" | $MAIL_AGENT $MAIL_AGENT_ARGS
+firma"
+
+ AssembleMessage
+
+ # send message
+ echo "$MESSAGE" | $MAIL_AGENT $MAIL_AGENT_ARGS $SENDER_ADDRESS
}
@@ -570,99 +702,120 @@ function ProcessMessage {
# process a received message
#
# parameter(s): none
- # depends on function(s): GetMessage, GetMessageHeaders, GetGpgMessage,
- # GetGpgDecryptStderr, GetSubscribersList, DecryptGpgMessage,
- # EncryptAndSendListMessage, EncryptAndSendWarningMessage,
- # SendBounceMessage
- # returns: 0 on success, 1 if any of the above functions return an error
+ # depends on function(s): GetMessage, GetGpgMessage, GetSubscribersList,
+ # GetSenderAddress
+ # returns: 0 on success,
+ # 1 if any of the above functions return an error
#-------------------------------------------------------------
local -i return_code=0
- # try to read message from STDIN and to fetch its headers
- if GetMessage && GetMessageHeaders; then
+ # try to read message from STDIN
+ if GetMessage; then
# check if the message was encrypted
if GetGpgMessage; then
- GetGpgDecryptStderr
- # and then check if the message was encrypted with the list's public key
- if echo "$GPG_DECRYPT_STDERR" | grep -q "^\[GNUPG:] ENC_TO $($GPG_LIST_KEYS $LIST_ADDRESS | sed -ne '/^sub:[ire]:/d' -e '/:e:$/p' | cut -d : -f 5)"; then
+ # if it was, parse gpg decrypt STDERR to decide what to do next
+ ParseGpgDecryptStderr
- # if signature in message is valid
- if echo "$GPG_DECRYPT_STDERR" | grep -q '^\[GNUPG:] GOODSIG'; then
+ # if the message was encrypted with the list's public key and if the
+ #+message signature is valid, send message to list subscribers
+ if [[ $ENCRYPTED_TO_LIST == 1 && $GOOD_SIGNATURE == 1 ]]; then
- # check if list has valid subscribers
- if GetSubscribersList; then
+ # check if the list has valid subscribers
+ if GetSubscribersList; then
- # if it does, decrypt, MIME wrap, re-encrypt and send the message
- DecryptGpgMessage
- EncryptAndSendListMessage
+ GetMessageHeadersAndBody
+ EditListMessageHeaders
+ DecryptGpgMessage
+ ReEncryptAndSendListMessage
- else
- return_code=1
- fi
+ else
+ return_code=1
+ fi
- # else, if signature is invalid, send a warning about this to the list administrator(s) and to sender
- elif echo "$GPG_DECRYPT_STDERR" | grep -q '^\[GNUPG:] BADSIG'; then
- EncryptAndSendWarningMessage
+ # else, if the message was correctly encrypted but its signature is invalid,
+ #+send a warning about this to the list administrator(s) and to sender
+ elif [[ $ENCRYPTED_TO_LIST == 1 && $BAD_SIGNATURE == 1 ]]; then
- # else, if signature can't be checked, then probably the sender is not subscribed to the list
- # send a note about this back to sender
- elif echo "$GPG_DECRYPT_STDERR" | grep -q '^\[GNUPG:] ERRSIG'; then
+ GetSenderAddress
+
+ if [[ -n $(echo $LIST_ADMINS) || -n "$SENDER_ADDRESS" ]]; then
+ ComposeAndSendWarningMessage
+ fi
+
+ # else, a bounce should be sent
+ else
+
+ # if bounce processing is enabled, continue
+ if [[ "$SEND_BOUNCE_MESSAGES" == 1 ]]; then
+
+ GetSenderAddress
if [[ -n "$SENDER_ADDRESS" ]]; then
- # this is the body of the message to be sent, so no indentation here
- MESSAGE_BODY="\
+ # if the message was encrypted with the list's public key
+ if [[ $ENCRYPTED_TO_LIST == 1 ]]; then
+
+ # then, if signature can't be checked, then probably the sender is not subscribed to the list
+ # send a bounce, if possible
+ if [[ $SIGNATURE_CHECKING_FAILED == 1 ]]; then
+
+ # this is the body of the message to be sent, so no indentation here
+ MESSAGE_BODY="\
It was not possible to process this message. Your email
address is not subscribed to this list. Contact the list
administrator if you have any questions."
- SendBounceMessage
- fi
+ ComposeAndSendBounceMessage
- # else, if message can be decrypted but its signature can't be checked, then message wasn't signed
- # send a note about this back to sender
- elif echo "$GPG_DECRYPT_STDERR" | grep -q '^\[GNUPG:] DECRYPTION_OKAY'; then
- if [[ -n "$SENDER_ADDRESS" ]]; then
+ # or, if message can be decrypted but its signature can't be checked, then message wasn't signed
+ # send a bounce, if possible
+ elif [[ $MESSAGE_DECRYPTION_OKAY == 1 ]]; then
- # this is the body of the message to be sent, so no indentation here
- MESSAGE_BODY="\
+ # this is the body of the message to be sent, so no indentation here
+ MESSAGE_BODY="\
It was not possible to process this message. Message was
not signed. Contact the list administrator if you have any
questions."
- SendBounceMessage
- fi
- fi
+ ComposeAndSendBounceMessage
+ fi
- # else, message wasn't encrypted with the list's public key
- # send a note about this back to sender
- else
- if [[ -n "$SENDER_ADDRESS" ]]; then
+ # else, message wasn't encrypted with the list's public key
+ # send a bounce, if possible
+ else
- # this is the body of the message to be sent, so no indentation here
- MESSAGE_BODY="\
+ # this is the body of the message to be sent, so no indentation here
+ MESSAGE_BODY="\
It was not possible to process this message. Message was
not encrypted with the list's public key. Contact the list
administrator if you have any questions."
- SendBounceMessage
+ ComposeAndSendBounceMessage
+ fi
+ fi
fi
fi
# else, message wasn't encrypted at all
- # send a note about this back to sender
+ # send a bounce, if possible
else
- if [[ -n "$SENDER_ADDRESS" ]]; then
- # this is the body of the message to be sent, so no indentation here
- MESSAGE_BODY="\
+ # if bounce processing is enabled, continue
+ if [[ "$SEND_BOUNCE_MESSAGES" == 1 ]]; then
+
+ GetSenderAddress
+ if [[ -n "$SENDER_ADDRESS" ]]; then
+
+ # this is the body of the message to be sent, so no indentation here
+ MESSAGE_BODY="\
It was not possible to process this message. Message was
not encrypted. Contact the list administrator if you have
have any questions."
- SendBounceMessage
+ ComposeAndSendBounceMessage
+ fi
fi
fi
- # else, message could not be read from STDIN or its headers could not be fetched
+ # else, message could not be read from STDIN
else
return_code=1
fi
@@ -900,23 +1053,52 @@ VERSION="0.3"
export LANG=en_US
umask 0077
-# declare global variables used during execution
-GLOBAL_VARS="GPG_BINARY MAIL_AGENT MAIL_AGENT_ARGS LISTS_DIR LOG_TO_SYSLOG LOGGER \
- SYSLOG_PRIORITY USE_GPG_HIDDEN_RECIPIENT_OPTION SEND_TO_ALL_AT_ONCE \
- LIST_ADDRESS LIST_ADMIN LIST_HOMEDIR PASSPHRASE SUBJECT_PREFIX \
- FIRMA_CONFIG_FILE VERSION \
- ERROR_MESSAGE EXIT_CODE \
- DESCRIPTION LIST_NAME LIST_PATH LIST_CONFIG_FILE \
- GPG_FLAGS GPG GPG_LIST_KEYS GPG_DECRYPT GPG_ENCRYPT \
- STDIN \
- ORIG_MESSAGE \
- ORIG_MESSAGE_HEADERS FROM SENDER_ADDRESS SUBJECT \
- ORIG_GPG_MESSAGE \
- GPG_DECRYPT_STDERR \
- SUBSCRIBERS_LIST \
- DECRYPTED_MESSAGE \
- GPG_MESSAGE RECIPIENTS MESSAGE_HEADERS MESSAGE_BODY MESSAGE \
- GLOBAL_VARS VAR"
+# declare global variables and functions used during execution
+GLOBAL_VARS="
+ GPG_BINARY MAIL_AGENT MAIL_AGENT_ARGS LISTS_DIR LOG_TO_SYSLOG LOGGER_BINARY SYSLOG_PRIORITY
+ USE_GPG_HIDDEN_RECIPIENT_OPTION REMOVE_THESE_HEADERS_ON_ALL_LISTS SEND_BOUNCE_MESSAGES
+ LIST_ADDRESS LIST_ADMIN LIST_HOMEDIR PASSPHRASE SUBJECT_PREFIX REMOVE_THESE_HEADERS REPLIES_SHOULD_GO_TO_LIST
+ FIRMA_CONFIG_FILE VERSION
+ ERROR_MESSAGE EXIT_CODE
+ DESCRIPTION LIST_NAME LIST_PATH LIST_CONFIG_FILE
+ GPG_FLAGS GPG GPG_LIST_KEYS GPG_DECRYPT GPG_ENCRYPT
+ STDIN
+ ORIG_MESSAGE
+ FIRST_BLANK_LINE ENCRYPTED_BLOC_BEGINS ENCRYPTED_BLOC_ENDS ORIG_GPG_MESSAGE
+ ENCRYPTED_TO_LIST GOOD_SIGNATURE BAD_SIGNATURE SIGNATURE_CHECKING_FAILED MESSAGE_DECRYPTION_OKAY
+ SUBSCRIBERS_LIST
+ ORIG_MESSAGE_HEADERS ORIG_MESSAGE_BODY
+ GPG_MESSAGE
+ DECRYPTED_MESSAGE
+ MESSAGE_HEADERS MESSAGE_BODY
+ MESSAGE
+ FUNCTION FUNCTIONS
+ GLOBAL_VARS VAR"
+
+FUNCTIONS="
+ Usage
+ Version
+ DeclareGpgVars
+ CheckFirmaConfigFile
+ CheckListConfigFile
+ GetMessage
+ GetGpgMessage
+ ParseGpgDecryptStderr
+ GetSubscribersList
+ GetMessageHeadersAndBody
+ EditListMessageHeaders
+ DecryptGpgMessage
+ ReplaceGpgMessage
+ GetSenderAddress
+ AssembleMessage
+ ReEncryptAndSendListMessage
+ ComposeAndSendWarningMessage
+ ComposeAndSendBounceMessage
+ ProcessMessage
+ NewList
+ ListAdministration
+ ChooseUid"
+
for VAR in $GLOBAL_VARS; do
declare $VAR
done
@@ -1072,19 +1254,24 @@ case $# in
# end main case
esac
-# print/log error message, if any, and exit
+# print/log error message, if any
if [[ -n "$ERROR_MESSAGE" ]]; then
if [[ "$LOG_TO_SYSLOG" == 1 ]]; then
- echo "$ERROR_MESSAGE" | $LOGGER -p "$SYSLOG_PRIORITY" -t "$(basename $0)"
+ echo "$ERROR_MESSAGE" | $LOGGER_BINARY -p "$SYSLOG_PRIORITY" -t "$(basename $0)"
else
echo >&2 "$(basename $0): $ERROR_MESSAGE"
fi
fi
-exit $EXIT_CODE
+# erase all functions and global variables
+for FUNCTION in $FUNCTIONS; do
+ unset -f $FUNCTION
+done
-# erase all global variables
for VAR in $GLOBAL_VARS; do
unset $VAR
done
+# exit
+exit $EXIT_CODE
+