]> gitweb.fluxo.info Git - firma.git/commitdiff
Lots of changes. Way too many to list here. I will wait until we have a
authorluis <luis>
Wed, 19 Jul 2006 05:07:02 +0000 (05:07 +0000)
committerluis <luis>
Wed, 19 Jul 2006 05:07:02 +0000 (05:07 +0000)
more solid version to write a decent changelog.

firma

diff --git a/firma b/firma
index 0e389fb0d5b44ed937ffc28a0c69e27421cf963a..b089e96dc7f5d145cc9aab099abd28399a45774e 100755 (executable)
--- a/firma
+++ b/firma
 #
 # 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
 #
 # 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 <firma@sarava.org>"
 }
 
 
-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 <firma@sarava.org>"
+
+  # 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