From 8795f18c414a00967d37652b774e98b8553e8d7b Mon Sep 17 00:00:00 2001 From: luis Date: Wed, 19 Jul 2006 05:07:02 +0000 Subject: Lots of changes. Way too many to list here. I will wait until we have a more solid version to write a decent changelog. --- firma | 1011 ++++++++++++++++++++++++++++++++++++++--------------------------- 1 file changed, 597 insertions(+), 414 deletions(-) diff --git a/firma b/firma index 0e389fb..b089e96 100755 --- a/firma +++ b/firma @@ -33,10 +33,15 @@ # # 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 +# 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_MESSAGES_USING_BCC= set to '1' to send list messages to all subscribers +# 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.) # @@ -44,6 +49,7 @@ # # 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 # @@ -54,69 +60,70 @@ # more than 4 times. # -FIRMA_CONFIG_FILE="/usr/local/etc/firma.conf" -VERSION="0.3" - -function DeclareGpgVars { +function Usage { #------------------------------------------------------------- - # declare gpg global variables + # display help # # 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" + + # this will be printed to STDOUT, so no indentation here + echo "\ +Usage: $(basename $0) OPTION [LIST-NAME] +GnuPG-based encrypted mailing list manager. + + -a, --admin-task LIST-NAME process administrative tasks on list + -c, --create-newlist LIST-NAME create a new mailing list + -h, --help display this help and exit + -p, --process-message LIST-NAME process a message sent to list + -v, --version output version information and exit + +If option -a is given, read standard input for tasks to be performed. +Tasks can be one or more of the following: + + use EMAIL-ADDRESS use the given address for message delivery instead + of the primary address on key + +Report bugs to " } -function Usage { +function Version { #------------------------------------------------------------- - # display help and exit + # display version information # # 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 " + + # this will be printed to STDOUT, so no indentation here + echo "\ +firma $VERSION + +Copyright (C) 2005 A Firma, Inc. +This program comes with ABSOLUTELY NO WARRANTY. +This is free software, and you are welcome to redistribute it +under certain conditions. See the GNU General Public License +for more details." } -function Version { +function DeclareGpgVars { #------------------------------------------------------------- - # output version information and exit + # declare GPG-related global variables # # 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" - echo "under certain conditions. See the GNU General Public License" - echo "for more details." + GPG_FLAGS="--no-options --homedir $LIST_HOMEDIR --quiet --batch --no-tty --no-use-agent --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 --passphrase-fd 0 --no-emit-version --sign --encrypt" } @@ -128,19 +135,29 @@ function CheckFirmaConfigFile { # depends on function(s): none # returns: 0 if all checks are passed, 1 if any check fails #------------------------------------------------------------- + + local -i return_code=0 + if [[ ! -f "$GPG_BINARY" || ! -x "$GPG_BINARY" ]]; then - echo "$(basename $0): GPG binary ("$GPG_BINARY") could not be found." - exit 1 + ERROR_MESSAGE="GPG binary ("$GPG_BINARY") could not be found" + return_code=1 elif [[ ! -f "$MAIL_AGENT" || ! -x "$MAIL_AGENT" ]]; then - echo "$(basename $0): Mail transport agent binary ("$MAIL_AGENT") could not be found." - exit 1 + ERROR_MESSAGE="Mail transport agent binary ("$MAIL_AGENT") could not be found" + return_code=1 elif [[ ! -d "$LISTS_DIR" ]]; then - echo "$(basename $0): Lists directory ("$LISTS_DIR") could not be found." - exit 1 + ERROR_MESSAGE="Lists directory ("$LISTS_DIR") could not be found" + return_code=1 elif [[ "$USE_GPG_HIDDEN_RECIPIENT_OPTION" == "1" && "$($GPG_BINARY --version | head -n1 | tr -dc '[[:digit:]]')" -lt "140" ]]; then - echo "$(basename $0): GPG's \"--hidden-recipient\" option is only available from version 1.4.0 onwards." - exit 1 + ERROR_MESSAGE="GPG's \"--hidden-recipient\" option is only available from version 1.4.0 onwards" + return_code=1 + elif [[ "$LOG_TO_SYSLOG" == "1" ]]; then + if [[ ! -f "$LOGGER" || ! -x "$LOGGER" ]]; then + ERROR_MESSAGE="logger binary ("$LOGGER") could not be found" + return_code=1 + fi fi + + return $return_code } @@ -153,39 +170,35 @@ function CheckListConfigFile { # returns: 0 if all checks are passed, 1 if any check fails #------------------------------------------------------------- + local -i return_code=0 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 $LIST_CONFIG_FILE | grep -o "^PASSPHRASE='[^']*'$")" || \ + 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 -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\}[234] ')" ]]; 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 4 times." - exit 1 - 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 + "$(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_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" + return_code=1 else 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 + 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 fi; } done fi + + return $return_code } @@ -198,109 +211,89 @@ function GetMessage { # returns: 0 on success, 1 if there's no input #------------------------------------------------------------- - local stdin - local element + local -i return_code=0 - # store message in array ORIG_MESSAGE - while read stdin; do - ORIG_MESSAGE[$element]="$stdin\n" - ((++element)) - done + # store message in ORIG_MESSAGE + ORIG_MESSAGE="$(sed -ne '1,$p')" - # 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 + # check if message was successfully stored + if (( "${#ORIG_MESSAGE[*]}" == 0 )); then + ERROR_MESSAGE="Message couldn't be read from standard input" + return_code=1 fi + + return $return_code } function GetMessageHeaders { #------------------------------------------------------------- - # get message headers and store some of them on separate variables + # 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 message + # returns: 0 on success, 1 if headers can't be located within the message #------------------------------------------------------------- - local element - local first_blank_line + local -i return_code=0 + local -i first_blank_line # find the first blank line in the message - for element in $(seq 0 $((${#ORIG_MESSAGE[@]} - 1))); do - if [[ "${ORIG_MESSAGE[$element]}" == "\n" ]]; then - first_blank_line=$element - # store all lines up to this one in array ORIG_MESSAGE_HEADERS - for element in $(seq 0 $(($first_blank_line - 1))); do - ORIG_MESSAGE_HEADERS[$element]="${ORIG_MESSAGE[$element]}" - done - # done, exit for loop - break 1 - fi - done + 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 were successfully stored in ORIG_MESSAGE_HEADERS - if [[ "${#ORIG_MESSAGE_HEADERS[@]}" -eq "0" ]]; then - echo "$(basename $0): Message headers could not be located within this message." - exit 1 + # 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 - # list ORIG_MESSAGE_HEADERS and get some specific headers for later use - FROM=$(echo -e "${ORIG_MESSAGE_HEADERS[@]}" | grep -m 1 '^ From:' | cut -d : -f 2- | sed -e 's/^ //') - SENDER_ADDRESS=$(if [[ -z "$(echo $FROM | grep '>$')" ]]; then echo $FROM; else echo $FROM | grep -o '<[^<>]*>$' | sed -e 's/[<>]//g'; fi) - DATE=$(echo -e "${ORIG_MESSAGE_HEADERS[@]}" | grep -m 1 '^ Date:' | cut -d : -f 2- | sed -e 's/^ //') - SUBJECT=$(echo -e "${ORIG_MESSAGE_HEADERS[@]}" | grep -m 1 '^ Subject:' | cut -d : -f 2- | sed -e 's/^ //') + return $return_code } function GetGpgMessage { #------------------------------------------------------------- - # get gpg encrypted part of a message + # get the gpg encrypted part of the message # # parameter(s): none # depends on function(s): GetMessage - # returns: 0 on success, 1 if no encrypted data is found within message + # returns: 0 on success, 1 if encrypted bloc can't be located within the message #------------------------------------------------------------- - # i = ORIG_MESSAGE element being processed - # j = ORIG_GPG_MESSAGE element being processed - local i - local 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]}" + local -i return_code=0 + local -i encrypted_bloc_begins + local -i encrypted_bloc_ends - # move to next element in both arrays - ((++i)) - ((++j)) + # 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) - # 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 - done + # 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")" + fi - # 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 + # check if the bloc was successfully stored + if (( "${#ORIG_GPG_MESSAGE[*]}" == 0 )); then + ERROR_MESSAGE="No valid GPG encrypted bloc found within this message" + return_code=1 fi + + return $return_code } @@ -315,7 +308,7 @@ function GetGpgDecryptStderr { # 2 for all other errors (incorrect passphrase, no encrypted data, etc.) #------------------------------------------------------------- - GPG_DECRYPT_STDERR="$(echo -e "$PASSPHRASE\n${ORIG_GPG_MESSAGE[@]}" | sed -e 's/^ //' | ($GPG_DECRYPT --status-fd 2 1> /dev/null) 2>&1)" + GPG_DECRYPT_STDERR="$(echo -e "${PASSPHRASE}\n${ORIG_GPG_MESSAGE}" | ($GPG_DECRYPT --status-fd 2 1> /dev/null) 2>&1 )" } @@ -326,132 +319,225 @@ function GetSubscribersList { # # parameter(s): none # depends on function(s): DeclareGpgVars - # returns: 0 on success, 1 if there are no subscribers on list + # returns: 0 on success, 1 if there are no valid subscribers on list #------------------------------------------------------------- - if [[ "$($GPG_LIST_KEYS 2> /dev/null | sed -ne "/$LIST_ADDRESS/Id" -e '/^pub:[ire]:/d' -e '/^pub/p' | wc -l)" -ne "0" ]]; then - SUBSCRIBERS_LIST="$($GPG_LIST_KEYS 2> /dev/null | sed -ne "/$LIST_ADDRESS/Id" -e '/^pub:[ire]:/d' -e '/^pub/p' | cut -d : -f 10 | grep -o '<[^<>]*>$' | sed -e 's/[<>]//g')" - else - echo "$LIST_NAME: There are no valid subscribers on list \"$(echo "$LIST_ADDRESS" | tr '[:upper:]' '[:lower:]')\"." - exit 1 + 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' | \ + cut -d : -f 10 | \ + grep -o '<[^<>]*>$' | \ + sed -e 's/[<>]//g' | \ + 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:]')\"" + return_code=1 fi + + return $return_code } -function SendListMessage { +function DecryptGpgMessage { #------------------------------------------------------------- - # compose and send a message to list members + # decrypt the gpg encrypted part of the message # # parameter(s): none - # depends on function(s): DeclareGpgVars, GetMessageHeaders, GetGpgMessage, - # GetGpgDecryptStderr, GetSubscribersList - # returns: 0 on success + # depends on function(s): DeclareGpgVars, GetGpgMessage + # returns: 0 #------------------------------------------------------------- - local subscriber + DECRYPTED_MESSAGE="$(echo -e "${PASSPHRASE}\n${ORIG_GPG_MESSAGE}" | $GPG_DECRYPT 2> /dev/null)" - GetSubscribersList +} - # check if gpg's --hidden-recipient option should be used - if [[ "$USE_GPG_HIDDEN_RECIPIENT_OPTION" == "1" ]]; then + +function MimeWrapMessage { + #------------------------------------------------------------- + # MIME wrap message to be sent, adding needed headers and body parts + # + # parameter(s): none + # depends on function(s): GetMessageHeaders, EncryptAndSendListMessage, + # EncryptAndSendWarningMessage + # returns: 0 + #------------------------------------------------------------- + + local boundary + + if [[ "$CONTENT_TYPE_SUBTYPE" == "multipart/encrypted" ]]; then + boundary="---------------firma$( date "+%s" | md5sum | cut -c 1-15 | tr '[:lower:]' '[:upper:]' )" + + # 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" # 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 + +Version: 1 + +--$boundary +Content-Type: application/octet-stream; name=\"encrypted.asc\" +Content-Disposition: inline; filename=\"encrypted.asc\" +Content-Description: OpenPGP encrypted message + +${GPG_MESSAGE} + +--${boundary}--" + + else + + # 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 +)" + + # this is the body of the message to be sent, so no indentation here + MESSAGE_BODY="${GPG_MESSAGE}" + fi + + # assemble entire message + MESSAGE="\ +${MESSAGE_HEADERS} + +${MESSAGE_BODY}" +} + + +function EncryptAndSendListMessage { + #------------------------------------------------------------- + # send the message to list members + # + # parameter(s): none + # depends on function(s): DeclareGpgVars, DecryptGpgMessage, GetSubscribersList, + # MimeWrapMessage + # returns: 0 + #------------------------------------------------------------- + + local subscriber + + # check if message should be encrypted to all subscribers at once + if [[ "$USE_GPG_HIDDEN_RECIPIENT_OPTION" == 1 ]]; then -MESSAGE_BODY="$(echo "$PASSPHRASE -Message from: $FROM -Subject: $SUBJECT -Date: $DATE + GPG_MESSAGE="$(echo -e "${PASSPHRASE}\n${DECRYPTED_MESSAGE}" | \ + $GPG_ENCRYPT --group subscribers="$(echo $SUBSCRIBERS_LIST)" --hidden-recipient subscribers 2> /dev/null )" -$(echo "$GPG_DECRYPT_STDERR" | grep -E '^gpg: Signature made|^gpg: Good signature from|^gpg: *aka') + # check if message should be sent to all subscribers at once + if [[ "$SEND_TO_ALL_AT_ONCE" == 1 ]]; then -$(echo -e "$PASSPHRASE\n${ORIG_GPG_MESSAGE[@]}" | $GPG_DECRYPT 2> /dev/null | mimencode -q -u | sed -e '/^Content-/d' -e 's/ÿ//')" | $GPG_ENCRYPT --group subscribers="$(echo $SUBSCRIBERS_LIST)" --hidden-recipient subscribers 2> /dev/null)" + 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 - # now send the message, either using BCC to sent it to all subscribers at - #+once or sending it separately to each one of them - if [[ "$SEND_MESSAGES_USING_BCC" == "1" ]]; then - echo -e "From: $LIST_ADDRESS\nBCC: $(echo $SUBSCRIBERS_LIST)\nSubject: none\n\n$MESSAGE_BODY" | $MAIL_AGENT $MAIL_AGENT_ARGS + # else, send the message to one subscriber at a time else for subscriber in $SUBSCRIBERS_LIST; do - echo -e "From: $LIST_ADDRESS\nTo: $subscriber\nSubject: none\n\n$MESSAGE_BODY" | $MAIL_AGENT $MAIL_AGENT_ARGS + + RECIPIENTS="To: $subscriber" + if echo "$SUBJECT" | grep -qF "$SUBJECT_PREFIX"; then + SUBJECT_PREFIX="" + fi + MimeWrapMessage + echo "$MESSAGE" | $MAIL_AGENT $MAIL_AGENT_ARGS + done fi - # else, if gpg's --hidden-recipient option should not be used, - #+encrypt and send message separately to each list subscriber + # else, message should be encrypted and sent to one subscriber at a time else for subscriber in $SUBSCRIBERS_LIST; do - # this is the body of the message to be sent, so no indentation here - -MESSAGE_BODY="$(echo "$PASSPHRASE -Message from: $FROM -Subject: $SUBJECT -Date: $DATE - -$(echo "$GPG_DECRYPT_STDERR" | grep -E '^gpg: Signature made|^gpg: Good signature from|^gpg: *aka') - -$(echo -e "$PASSPHRASE\n${ORIG_GPG_MESSAGE[@]}" | $GPG_DECRYPT 2> /dev/null | mimencode -q -u | sed -e '/^Content-/d' -e 's/ÿ//')" | $GPG_ENCRYPT --recipient $subscriber 2> /dev/null)" + 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 - # now send the message - echo -e "From: $LIST_ADDRESS\nTo: $subscriber\nSubject: none\n\n$MESSAGE_BODY" | $MAIL_AGENT $MAIL_AGENT_ARGS done fi } -function SendWarningMessage { +function EncryptAndSendWarningMessage { #------------------------------------------------------------- - # compose and send a "BAD signature" warning to the list administrator(s) and to sender + # send a "BAD signature" warning message to the list administrator(s) and to sender # # parameter(s): none - # depends on function(s): DeclareGpgVars, GetMessageHeaders, GetGpgMessage, GetGpgDecryptStderr - # returns: 0 on success + # depends on function(s): DeclareGpgVars, GetMessageHeaders, DecryptGpgMessage, + # MimeWrapMessage + # returns: 0 #------------------------------------------------------------- local email_address - # check if gpg's --hidden-recipient option should be used - if [[ "$USE_GPG_HIDDEN_RECIPIENT_OPTION" == "1" ]]; then - - # this is the body of the message to be sent, so no indentation here + # check if message should be encrypted to all addresses at once + if [[ "$USE_GPG_HIDDEN_RECIPIENT_OPTION" == 1 ]]; then -MESSAGE_BODY="$(echo "$PASSPHRASE -Message from: $FROM -Subject: [BAD SIGNATURE] $SUBJECT -Date: $DATE + 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)" -$(echo "$GPG_DECRYPT_STDERR" | grep -E '^gpg: Signature made|^gpg: BAD signature from|^gpg: *aka') + # check if message should be sent to all addresses at once + if [[ "$SEND_TO_ALL_AT_ONCE" == 1 ]]; then -$(echo -e "$PASSPHRASE\n${ORIG_GPG_MESSAGE[@]}" | $GPG_DECRYPT 2> /dev/null | mimencode -q -u | sed -e '/^Content-/d' -e 's/ÿ//')" | $GPG_ENCRYPT --group addresses="$LIST_ADMIN $SENDER_ADDRESS" --hidden-recipient addresses 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 - # now send the message, either using BCC to sent it to all addresses at - #+once or sending it separately to each one of them - if [[ "$SEND_MESSAGES_USING_BCC" == "1" ]]; then - echo -e "From: $LIST_ADDRESS\nBCC: $LIST_ADMIN $SENDER_ADDRESS\nSubject: none\n\n$MESSAGE_BODY" | $MAIL_AGENT $MAIL_AGENT_ARGS + # else, send the message to one address at a time else for email_address in $LIST_ADMIN $SENDER_ADDRESS; do - echo -e "From: $LIST_ADDRESS\nTo: $email_address\nSubject: none\n\n$MESSAGE_BODY" | $MAIL_AGENT $MAIL_AGENT_ARGS + + RECIPIENTS="To: $email_address" + SUBJECT_PREFIX="[BAD SIGNATURE] " + MimeWrapMessage + echo "$MESSAGE" | $MAIL_AGENT $MAIL_AGENT_ARGS + done fi - # else, if gpg's --hidden-recipient option should not be used, - #+encrypt and send message separately to each address + # else, message should be encrypted and sent to one address at a time else for email_address in $LIST_ADMIN $SENDER_ADDRESS; do - # this is the body of the message to be sent, so no indentation here + 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 -MESSAGE_BODY="$(echo "$PASSPHRASE -Message from: $FROM -Subject: [BAD SIGNATURE] $SUBJECT -Date: $DATE - -$(echo "$GPG_DECRYPT_STDERR" | grep -E '^gpg: Signature made|^gpg: BAD signature from|^gpg: *aka') - -$(echo -e "$PASSPHRASE\n${ORIG_GPG_MESSAGE[@]}" | $GPG_DECRYPT 2> /dev/null | mimencode -q -u | sed -e '/^Content-/d' -e 's/ÿ//')" | $GPG_ENCRYPT --recipient $email_address 2> /dev/null)" - - # now send the message - echo -e "From: $LIST_ADDRESS\nTo: $email_address\nSubject: none\n\n$MESSAGE_BODY" | $MAIL_AGENT $MAIL_AGENT_ARGS done fi } @@ -462,18 +548,15 @@ function SendBounceMessage { # send a bounce message back to sender # # parameter(s): none - # depends on function(s): GetMessageHeaders - # returns: 0 on success + # depends on function(s): GetMessageHeaders, ProcessMessage + # returns: 0 #------------------------------------------------------------- # this is the body of the message to be sent, so no indentation here -echo "From: $LIST_ADDRESS + echo "\ +From: $LIST_ADDRESS To: $SENDER_ADDRESS -Subject: none - -Message from: $FROM Subject: [RETURNED MAIL] $SUBJECT -Date: $DATE $MESSAGE_BODY @@ -487,63 +570,104 @@ function ProcessMessage { # process a received message # # parameter(s): none - # depends on function(s): GetMessage, GetMessageHeaders, GetGpgMessage, GetGpgDecryptStderr, - # SendListMessage, SendWarningMessage, SendBounceMessage - # returns: 0 on success + # 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 #------------------------------------------------------------- - GetMessage - GetMessageHeaders - GetGpgMessage - GetGpgDecryptStderr + local -i return_code=0 - # first, 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 + # try to read message from STDIN and to fetch its headers + if GetMessage && GetMessageHeaders; then - # if signature in message is valid, decrypt, re-encrypt and send it to list subscribers - if echo "$GPG_DECRYPT_STDERR" | grep -q '^\[GNUPG:] GOODSIG'; then - SendListMessage + # check if the message was encrypted + if GetGpgMessage; then + GetGpgDecryptStderr - # 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 - SendWarningMessage + # 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 - # 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 + # if signature in message is valid + if echo "$GPG_DECRYPT_STDERR" | grep -q '^\[GNUPG:] GOODSIG'; then + + # check if list has valid subscribers + if GetSubscribersList; then + + # if it does, decrypt, MIME wrap, re-encrypt and send the message + DecryptGpgMessage + EncryptAndSendListMessage - # this is the body of the message to be sent, so no indentation here - MESSAGE_BODY="\ + 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 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 + 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. Your email address is not subscribed to this list. Contact the list administrator if you have any questions." - SendBounceMessage + SendBounceMessage + fi - # else, if message can be decrypted but its signature can't be checked, then message wasn't signed - # send a note about this to sender - elif echo "$GPG_DECRYPT_STDERR" | grep -q '^\[GNUPG:] DECRYPTION_OKAY'; then + # 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 - # 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 + SendBounceMessage + fi + fi - # else, message wasn't encrypted with the list's public key - # send a note about this to sender - else + # 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 - # 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 + SendBounceMessage + fi + fi + + # else, message wasn't encrypted at all + # send a note about this back to sender + else + 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 + fi + fi + + # else, message could not be read from STDIN or its headers could not be fetched + else + return_code=1 fi + + return $return_code } @@ -556,26 +680,22 @@ function NewList { # 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." + local -i return_code=0 - # commented: -# read -rep "path to smtp command (e.g., /usr/sbin/sendmail): " MAIL_AGENT -# read -rep "command-line arguments passed to the smtp wrapper (e.g., -oem -oi -t): " MAIL_AGENT_ARGS -# read -rep "path to gpg binary (e.g., /usr/bin/gpg): " GPG_BINARY + if [ ! -d "$LIST_PATH" ]; then - # if [ ! -x "$GPG_BINARY" ]; then + echo "Creating folder $LIST_PATH..." + if mkdir "$LIST_PATH"; then # || (echo "$(basename $0): error creating $LIST_PATH: installation aborted"; exit 1) + echo "creating list config file and will ask some questions." - # removed: (defaults to $LIST_HOMEDIR) - read -rep "list keyring folder: " LIST_HOMEDIR + read -rep " List keyring location: ("$LIST_PATH") " LIST_HOMEDIR + LIST_HOMEDIR=${LIST_HOMEDIR:-"$LIST_PATH"} - # todo: please no utf-8 (see DETAILS) - read -rep "list email (e.g., firma@domain.tld): " LIST_ADDRESS - read -rep "list admins emails (space delimited): " LIST_ADMIN - read -rep "list description (fake?): " DESCRIPTION - read -resp "password for list keyring (use a huge one): " PASSPHRASE + # NAO USAR UTF-8 (VER DETAILS) + read -rep " List email address: " LIST_ADDRESS + read -rep " List administrator(s) email address(es) (space delimited): " LIST_ADMIN + read -rep " List description (optional): " DESCRIPTION + read -resp " Passphrase to protect the list's secret key: " PASSPHRASE # todo: key specs (size, expiry date...) @@ -605,11 +725,18 @@ function NewList { EOF + else + echo "$(basename $0): cannot create $LIST_PATH: Installation aborted" + return_code=1 + fi + fi else echo "$(basename $0): cannot create $LIST_NAME: List already exists" - exit 1 + return_code=1 fi + + return return_code } @@ -617,33 +744,37 @@ function ListAdministration { #------------------------------------------------------------- # process administrative tasks # - # parameter(s): expects task to be performed (plus its argument(s)) from STDIN + # parameter(s): task to be performed (plus its argument(s)) # 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.) + # 1 if task can't be executed (command not found, too many/missing arguments, etc.), + # 2 if a quit command is entered #------------------------------------------------------------- + local -i return_code=0 + 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 + # this will be printed to STDOUT, so no indentation here + echo " + quit quit this prompt + help show this help + use EMAIL-ADDRESS use the given address for message delivery instead + of the primary address on key +" ;; quit) - exit 0 + return_code=2 ;; use) echo >&2 "$1: missing arguments (try \"help\")" - return 1 + return_code=1 ;; *) echo >&2 "Command not found -- $1 (try \"help\")" - return 1 + return_code=1 ;; esac ;; @@ -651,20 +782,20 @@ function ListAdministration { case $1 in use) # check if argument is an email address - if [[ -z "$(echo -ne $2 | grep -o '[^@]\+@[^@]\+')" ]]; then + if [[ -z "$(echo $2 | grep -o '[^@]\+@[^@]\+')" ]]; then echo >&2 "$1: invalid argument -- $2 (try \"help\")" - return 1 + return_code=1 else ChooseUid $2 fi ;; help|quit) echo >&2 "$1: too many arguments -- $@ (try \"help\")" - return 1 + return_code=1 ;; *) echo >&2 "Command not found -- $1 (try \"help\")" - return 1 + return_code=1 ;; esac ;; @@ -672,22 +803,24 @@ function ListAdministration { case $1 in help|quit|use) echo >&2 "$1: too many arguments -- $@ (try \"help\")" - return 1 + return_code=1 ;; *) echo >&2 "Command not found -- $1 (try \"help\")" - return 1 + return_code=1 ;; esac ;; esac + + return $return_code } function ChooseUid { #------------------------------------------------------------- # choose which UID of a public key should be used for message delivery, - # deleting all other UIDs on this key + #+deleting all other UIDs on the key # # parameter(s): chosen email address # depends on function(s): DeclareGpgVars @@ -695,81 +828,102 @@ function ChooseUid { # 1 if task can't be executed (public key not found, only one UID on key, etc.) #------------------------------------------------------------- + local -i return_code=0 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)" + local -i uid_count="$($GPG_LIST_KEYS --fixed-list-mode $keyid 2> /dev/null | grep ^uid | wc -l)" + local -i 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 + if [[ -z "$($GPG_LIST_KEYS --fixed-list-mode "<$1>" 2> /dev/null | grep -v '^tru:')" ]]; then + echo >&2 "use: \"$(echo $1 | tr '[:upper:]' '[:lower:]')\" is not associated with any public key on this keyring." + return_code=1 # 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 (( "$($GPG_LIST_KEYS --fixed-list-mode $1 2> /dev/null | grep ^uid | wc -l)" == 1 )); then + echo >&2 "use: \"$(echo $1 | tr '[:upper:]' '[:lower:]')\" is part of the only UID on public key \"$keyid\"." + return_code=1 # 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." + elif (( "$($GPG_LIST_KEYS --fixed-list-mode $1 2> /dev/null | grep -i "<$1>:$" | wc -l)" > 1 )); then + echo >&2 "use: \"$(echo $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 + return_code=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 - - # 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_number } { - send "uid \$uid\n" - expect "GET_LINE keyedit.prompt" + if (( $return_code == 0 )); then + expect -nN -- << EOF + # no output to STDOUT + log_user 0 + # set a 5 seconds timeout in case anything goes wrong + set timeout 5 + + # 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_number } { + send "uid \$uid\n" + expect "GET_LINE keyedit.prompt" + } + set uid [incr uid] } - 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" } - # 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 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 + # delay until the process above terminates + wait + # send following message to user + send_user "use: \"$(echo $1 | tr '[:upper:]' '[:lower:]')\" chosen for message delivery. [ expr $uid_count - 1 ] UID(s) deleted from public key \"$keyid\".\n" + exit EOF + fi + + return $return_code } #------------------------------------------------------------- # main() #------------------------------------------------------------- +# path to firma.conf and firma version +FIRMA_CONFIG_FILE="/usr/local/etc/firma.conf" +VERSION="0.3" + # set environmental variables and options export LANG=en_US umask 0077 -# declare global arrays and variables used during execution -GLOBAL_ARRAYS="ORIG_MESSAGE ORIG_MESSAGE_HEADERS ORIG_GPG_MESSAGE" -for ARRAY in $GLOBAL_ARRAYS; do - declare -a $ARRAY -done - -GLOBAL_VARS="GPG_BINARY MAIL_AGENT MAIL_AGENT_ARGS LISTS_DIR USE_GPG_HIDDEN_RECIPIENT_OPTION SEND_MESSAGES_USING_BCC LIST_ADDRESS LIST_ADMIN LIST_HOMEDIR PASSPHRASE FIRMA_CONFIG_FILE VERSION GPG_FLAGS GPG GPG_LIST_KEYS GPG_DECRYPT GPG_ENCRYPT FROM DATE SUBJECT SENDER_ADDRESS GPG_DECRYPT_STDERR SUBSCRIBERS_LIST MESSAGE_BODY DESCRIPTION LIST_NAME LIST_PATH LIST_CONFIG_FILE STDIN GLOBAL_ARRAYS ARRAY GLOBAL_VARS VAR" +# 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" for VAR in $GLOBAL_VARS; do - declare VAR + declare $VAR done +# set initial exit code +EXIT_CODE=0 + # command line parsing: # first check number of arguments, then check what was entered # start main case @@ -777,118 +931,140 @@ case $# in 0) echo >&2 "$(basename $0): missing arguments" Usage - exit 1 + EXIT_CODE=1 ;; 1) # start case #1 case $1 in -h|--help) Usage + EXIT_CODE=0 ;; -v|--version) Version + EXIT_CODE=0 ;; # valid option called without its required argument - -a|--admin-task|-c|--create-newlist|-p|--process-message|-r|--list-request) + -a|--admin-task|-c|--create-newlist|-p|--process-message) echo >&2 "$(basename $0): missing arguments" Usage - exit 1 + EXIT_CODE=1 ;; *) echo >&2 "$(basename $0): invalid option -- $1" Usage - exit 1 + EXIT_CODE=1 ;; # end case #1 esac ;; 2) - - # if firma.conf exists, evaluate its parameters and check them + # if firma.conf exists if [ -f "$FIRMA_CONFIG_FILE" ]; then + + # evaluate its parameters shopt -u sourcepath && source "$FIRMA_CONFIG_FILE" - CheckFirmaConfigFile - else - echo >&2 "$(basename $0): cannot source \`$FIRMA_CONFIG_FILE': No such file" - exit 1 - fi - LIST_NAME="$2" - LIST_PATH="$LISTS_DIR/$LIST_NAME" - LIST_CONFIG_FILE="$LIST_PATH/$LIST_NAME.conf" + # set SYSLOG_PRIORITY to the default value, if needed + if [[ "$LOG_TO_SYSLOG" == 1 ]]; then + SYSLOG_PRIORITY=${SYSLOG_PRIORITY:-"user.err"} + fi - # 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 + # and finally check firma.conf + if CheckFirmaConfigFile; then - # get gpg parameters and check the list configuration file - DeclareGpgVars - CheckListConfigFile + LIST_NAME="$2" + LIST_PATH="$LISTS_DIR/$LIST_NAME" + LIST_CONFIG_FILE="$LIST_PATH/$LIST_NAME.conf" - # start case #3 + # start case #2 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 + -c|--create-newlist) + NewList + ;; + # options that depend on the list configuration file + -a|--admin-task|-p|--process-message) + + # if the configuration file exists, disable bash's + #+sourcepath and evaluate list parameters + if [[ -f "$LIST_CONFIG_FILE" ]]; then + shopt -u sourcepath && source "$LIST_CONFIG_FILE" + + # get gpg parameters + DeclareGpgVars + + # check the list configuration file + if CheckListConfigFile; then + + # start case #3 + case $1 in + -a|--admin-task) + + # while a quit command isn't entered (returns 2), read STDIN + while (( $EXIT_CODE != 2 )) && read -rep "Command> " STDIN; do + # if line is not empty or commented, process command + if [[ "$STDIN" && "$STDIN" != "#"* ]]; then + ListAdministration $STDIN + EXIT_CODE=$? + fi + done + + # since quit was entered, exit without error + EXIT_CODE=0 + + ;; + -p|--process-message) + ProcessMessage + EXIT_CODE=$? + ;; + # end case #3 + esac + # else, list configuration file checking returned an error + else + EXIT_CODE=$? fi - done - + # else, list configuration file could not be found + else + ERROR_MESSAGE="Cannot source \`$LIST_CONFIG_FILE': No such file or directory" + EXIT_CODE=1 + fi ;; - -p|--process-message) - ProcessMessage + # valid option called with too many arguments + -h|--help|-v|--version) + echo >&2 "$(basename $0): too many arguments -- $@" + Usage + EXIT_CODE=1 ;; - -r|--list-request) - # not implemented yet - #ListRequest - exit 0 + *) + echo >&2 "$(basename $0): invalid option -- $1" + Usage + EXIT_CODE=1 ;; - # end case #3 + # end case #2 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 + # else, firma.conf checking returned an error + else + EXIT_CODE=$? + fi + # else, firma.conf could not be found + else + ERROR_MESSAGE="Cannot source \`$FIRMA_CONFIG_FILE': No such file or directory" + EXIT_CODE=1 + fi ;; *) # 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) + -a|--admin-task|-c|--create-newlist|-h|--help|-p|--process-message|-v|--version) echo >&2 "$(basename $0): too many arguments -- $@" Usage - exit 1 + EXIT_CODE=1 ;; *) echo >&2 "$(basename $0): invalid option -- $1" Usage - exit 1 + EXIT_CODE=1 ;; # end case #4 esac @@ -896,11 +1072,18 @@ case $# in # end main case esac -# erase all global arrays and variables -for ARRAY in $GLOBAL_ARRAYS; do - unset $ARRAY -done +# print/log error message, if any, and exit +if [[ -n "$ERROR_MESSAGE" ]]; then + if [[ "$LOG_TO_SYSLOG" == 1 ]]; then + echo "$ERROR_MESSAGE" | $LOGGER -p "$SYSLOG_PRIORITY" -t "$(basename $0)" + else + echo >&2 "$(basename $0): $ERROR_MESSAGE" + fi +fi + +exit $EXIT_CODE +# erase all global variables for VAR in $GLOBAL_VARS; do unset $VAR done -- cgit v1.2.3