diff options
author | luis <luis> | 2006-07-28 16:00:27 +0000 |
---|---|---|
committer | luis <luis> | 2006-07-28 16:00:27 +0000 |
commit | 8430641d0153b761eb42416b55e309e511af45bf (patch) | |
tree | 8704c2588fb0a849f8cdd47ae094b4a34b1077cc | |
parent | 8795f18c414a00967d37652b774e98b8553e8d7b (diff) | |
download | firma-8430641d0153b761eb42416b55e309e511af45bf.tar.gz firma-8430641d0153b761eb42416b55e309e511af45bf.tar.bz2 |
Again, lots of changes. Please, wait for the changelog.
-rwxr-xr-x | firma | 809 |
1 files changed, 498 insertions, 311 deletions
@@ -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 + |