#!/bin/bash # # firma: GnuPG-based encrypted mailing list manager # Feedback: firma@sarava.org # # Firma is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # Firma is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA # # Usage: # # All firma parameters are passed through two different # configuration files: firma.conf, containing general parameters # necessary to run the script, and a list specific file, # containing its address, administrator(s), etc. In both # files you should enter PARAMETER='value' (whithout spaces # before or after the equal sign). # # firma.conf should contain the following parameters: # # GPG_BINARY= path to the gnupg binary # MAIL_AGENT= path to the mail transport agent to be used (e.g., sendmail) # MAIL_AGENT_ARGS= command-line arguments to be passed to the command above # LISTS_DIR= path to the mailing lists directory # # And the list configuration file should contain: # # LIST_ADDRESS= list's email address # LIST_ADMIN= list's administrators email addresses (space separated) # LIST_HOMEDIR= list's gnupg homedir, where the list's keyrings are located # PASSPHRASE= passphrase for the list's private keyring # # NOTE: The PASSPHRASE value _has_ to be enclosed in single # quotes and _cannot_ contain any additional single quote as part # of itself. # FIRMA_CONFIG_FILE="/usr/local/etc/firma.conf" VERSION="0.3" function DeclareGpgVars { #------------------------------------------------------------- # declare gpg global variables # # parameter(s): none # depends on function(s): none # returns: 0 #------------------------------------------------------------- GPG_FLAGS="--no-options --no-default-keyring --homedir $LIST_HOMEDIR --quiet --batch --no-tty --no-use-agent --no-auto-check-trustdb --no-permission-warning" GPG="$GPG_BINARY $GPG_FLAGS" GPG_LIST_KEYS="$GPG --list-keys --with-colons" GPG_DECRYPT="$GPG --passphrase-fd 0 --decrypt" GPG_ENCRYPT="$GPG --armor --trust-model always --local-user $LIST_ADDRESS --no-emit-version --passphrase-fd 0 --sign --encrypt --recipient" } function Usage { #------------------------------------------------------------- # display help and exit # # parameter(s): none # depends on function(s): none # returns: 0 #------------------------------------------------------------- echo "Usage: $(basename $0) OPTION [LIST-NAME]" echo "GnuPG-based encrypted mailing list manager." echo echo " -a, --admin-task LIST-NAME process administrative tasks on list" echo " -c, --create-newlist LIST-NAME create a new mailing list" echo " -h, --help display this help and exit" echo " -p, --process-message LIST-NAME process a message sent to list" # echo " -r, --process-request LIST-NAME process administrative and user" # echo " requests on list." echo " -v, --version output version information and exit" echo echo "If option -a is given, read standard input for tasks to be performed." echo "Tasks can be one or more of the following:" echo echo " use EMAIL-ADDRESS use the given address for message delivery instead" echo " of the primary address on key" echo echo "Report bugs to " } function Version { #------------------------------------------------------------- # output version information and exit # # parameter(s): none # depends on function(s): none # returns: 0 #------------------------------------------------------------- echo "firma $VERSION" echo echo "Copyright (C) 2005 A Firma, Inc." echo "This program comes with ABSOLUTELY NO WARRANTY." echo "This is free software, and you are welcome to redistribute it" echo "under certain conditions. See the GNU General Public License" echo "for more details." } function CheckFirmaConfigFile { #------------------------------------------------------------- # check firma.conf parameters # # parameter(s): none # depends on function(s): none # returns: 0 if all checks are passed, 1 if any check fails #------------------------------------------------------------- if [[ ! -f "$GPG_BINARY" || ! -x "$GPG_BINARY" ]]; then echo "$(basename $0): GPG binary ("$GPG_BINARY") could not be found." exit 1 elif [[ ! -f "$MAIL_AGENT" || ! -x "$MAIL_AGENT" ]]; then echo "$(basename $0): Mail transport agent binary ("$MAIL_AGENT") could not be found." exit 1 elif [[ ! -d "$LISTS_DIR" ]]; then echo "$(basename $0): Lists directory ("$LISTS_DIR") could not be found." exit 1 fi } function CheckListConfigFile { #------------------------------------------------------------- # check configuration file parameters # # parameter(s): none # depends on function(s): DeclareGpgVars # returns: 0 if all checks are passed, 1 if any check fails #------------------------------------------------------------- local administrator if [[ ! -d "$LIST_HOMEDIR" || ! -f "$LIST_HOMEDIR/pubring.gpg" || ! -f "$LIST_HOMEDIR/secring.gpg" ]]; then echo "$LIST_NAME: GPG home directory ("$LIST_HOMEDIR") or the GPG keyrings could not be found." exit 1 elif [[ -z "$(cat $LIST_CONFIG_FILE | grep -o "^PASSPHRASE='[^']*'$")" || \ -z "$PASSPHRASE" || \ "$(echo -n "$PASSPHRASE" | wc -c)" -lt "25" || \ -z "$(echo -n "$PASSPHRASE" | tr -dc '[[:lower:]]')" || \ -z "$(echo -n "$PASSPHRASE" | tr -dc '[[:upper:]]')" || \ -z "$(echo -n "$PASSPHRASE" | tr -dc '[[:digit:]]')" || \ "$(echo -n "$PASSPHRASE" | tr -dc '[:punct:]' | wc -c)" -lt "5" || \ "$(echo -n "$PASSPHRASE" | fold -w1 | uniq -cd | grep -v '^ \{6\}[23] ')" ]]; then echo "$LIST_NAME: PASSPHRASE is empty or does not meet the minimum complexity requirements." echo "$LIST_NAME: Please set a new passphrase for the list's private key. Make it at least" echo "$LIST_NAME: 25 characters long (using a combination of numbers, upper and lower case" echo "$LIST_NAME: letters and at least 5 special characters) and enclose it in 'single" echo "$LIST_NAME: quotes'. The passphrase itself, though, cannot contain any single quote." echo "$LIST_NAME: Also, no character should be sequentially repeated more than 3 times." exit 1 elif [[ -z "$($GPG_LIST_KEYS --fixed-list-mode 2> /dev/null | grep ^uid | cut -d : -f 10 | grep -i "<$LIST_ADDRESS>$")" ]]; then echo "$LIST_NAME: Public key for list \"$(echo -ne "$LIST_ADDRESS" | tr '[:upper:]' '[:lower:]')\" could not be found." echo "$LIST_NAME: Note that this parameter expects an email address." exit 1 else for administrator in $LIST_ADMIN; do { if [[ -z "$($GPG_LIST_KEYS --fixed-list-mode 2> /dev/null | grep ^uid | cut -d : -f 10 | grep -i "<$administrator>$")" ]]; then echo "$LIST_NAME: Public key for list administrator \"$(echo -ne "$administrator" | tr '[:upper:]' '[:lower:]')\" could not be found." echo "$LIST_NAME: Note that this parameter expects one or more space separated email addresses." exit 1 fi; } done fi } function GetMessage { #------------------------------------------------------------- # read message from STDIN # # parameter(s): expects message from STDIN # depends on function(s): none # returns: 0 on success, 1 if there's no input #------------------------------------------------------------- local stdin local element # store message in array ORIG_MESSAGE while read stdin; do ORIG_MESSAGE[$element]="$stdin\n" ((++element)) done # check if message was successfully stored in ORIG_MESSAGE if [[ "${#ORIG_MESSAGE[@]}" -eq "0" ]]; then echo "$(basename $0): Message couldn't be read from standard input." exit 1 fi } function GetMessageHeaders { #------------------------------------------------------------- # get message headers and store some of them on separate variables # # parameter(s): none # depends on function(s): GetMessage # returns: 0 on success, 1 if headers can't be located within message #------------------------------------------------------------- local element local 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 # 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/^ //') # 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 fi } function GetGpgMessage { #------------------------------------------------------------- # get gpg encrypted part of a message # # parameter(s): none # depends on function(s): GetMessage # returns: 0 on success, 1 if no encrypted data is found within message #------------------------------------------------------------- # i = ORIG_MESSAGE element being processed # j = ORIG_GPG_MESSAGE element being processed local i j # for elements in ORIG_MESSAGE, do for i in $(seq 0 $((${#ORIG_MESSAGE[@]} - 1))); do # find the first line of the encrypted data #+and assign it to the first element of ORIG_GPG_MESSAGE if [[ "${ORIG_MESSAGE[$i]}" == "-----BEGIN PGP MESSAGE-----\n" ]]; then ORIG_GPG_MESSAGE[$j]="${ORIG_MESSAGE[$i]}" # move to next element in both arrays ((++i)) ((++j)) # until the end of the encrypted data is reached, #+assign subsequent elements in ORIG_MESSAGE to elements of ORIG_GPG_MESSAGE until [[ "${ORIG_MESSAGE[$i]}" == "-----END PGP MESSAGE-----\n" ]]; do ORIG_GPG_MESSAGE[$j]="${ORIG_MESSAGE[$i]}" ((++i)) ((++j)) done # last, assign the line matched above to the last element of ORIG_GPG_MESSAGE ORIG_GPG_MESSAGE[$j]="${ORIG_MESSAGE[$i]}" # no need to process lines beyond this point # exit for loop break 1 fi done # check if encrypted data was stored in ORIG_GPG_MESSAGE if [[ "${#ORIG_GPG_MESSAGE[@]}" -eq "0" ]]; then echo "$(basename $0): No GPG encrypted data was found within this message." return 1 fi } function GetGpgDecryptStderr { #------------------------------------------------------------- # discard $GPG_DECRYPT STDOUT and get its STDERR for signature checking # # parameter(s): none # depends on function(s): DeclareGpgVars, GetGpgMessage # returns: 0 if signature in ORIG_GPG_MESSAGE is valid, # 1 if signature is invalid, # 2 for all other errors (incorrect passphrase, no encrypted data, etc.) #------------------------------------------------------------- echo -e "$PASSPHRASE\n${ORIG_GPG_MESSAGE[@]}" | sed -e 's/^ //' | ($GPG_DECRYPT --status-fd 2 1> /dev/null) 2>&1 } function GetSubscribersList { #------------------------------------------------------------- # get list susbscriber addresses for message encryption and delivery # # parameter(s): none # depends on function(s): DeclareGpgVars # returns: 0 on success, 1 if there are no subscribers on list #------------------------------------------------------------- if [[ "$($GPG_LIST_KEYS 2> /dev/null | sed -ne "/$LIST_ADDRESS/Id" -e '/^pub/p' | wc -l)" -ne "0" ]]; then $GPG_LIST_KEYS 2> /dev/null | sed -ne "/$LIST_ADDRESS/Id" -e '/^pub/p' | cut -d : -f 10 | grep -o '<[^<>]*>$' | sed -e 's/[<>]//g' else echo "$LIST_NAME: There are no subscribers on list \"$(echo "$LIST_ADDRESS" | tr '[:upper:]' '[:lower:]')\"." exit 1 fi } function SendListMessage { #------------------------------------------------------------- # compose and send a message to list members # # parameter(s): subscriber address # depends on function(s): DeclareGpgVars, GetGpgMessage, GetMessageHeaders, GetGpgDecryptStderr # returns: 0 on success #------------------------------------------------------------- # this is the body of the message to be sent, so no identation here MESSAGE_BODY=( $(echo "$PASSPHRASE Message from: $FROM Subject: $SUBJECT Date: $DATE $(GetGpgDecryptStderr | grep '^gpg: Signature made') $(GetGpgDecryptStderr | grep '^gpg: Good signature from') $(echo -e "$PASSPHRASE\n${ORIG_GPG_MESSAGE[@]}" | $GPG_DECRYPT 2> /dev/null)" | sed -e 's/=20$//' | $GPG_ENCRYPT $1 2> /dev/null | sed -e 's/$/\\n/') ) # now send the message echo -e "From: $LIST_ADDRESS\nTo: $1\nSubject: none\n\n${MESSAGE_BODY[@]}" | sed -e 's/^ //' | $MAIL_AGENT $MAIL_AGENT_ARGS } function SendWarningMessage { #------------------------------------------------------------- # compose and send a "BAD signature" warning to the # list administrator(s) and to sender # # parameter(s): list administrator/sender address # depends on function(s): DeclareGpgVars, GetGpgMessage, GetMessageHeaders, GetGpgDecryptStderr # returns: 0 on success #------------------------------------------------------------- # this is the body of the message to be sent, so no identation here MESSAGE_BODY=( $(echo "$PASSPHRASE Message from: $FROM Subject: [BAD SIGNATURE] $SUBJECT Date: $DATE $(GetGpgDecryptStderr | grep '^gpg: Signature made') $(GetGpgDecryptStderr | grep '^gpg: BAD signature from') $(echo -e "$PASSPHRASE\n${ORIG_GPG_MESSAGE[@]}" | $GPG_DECRYPT 2> /dev/null)" | sed -e 's/=20$//' | $GPG_ENCRYPT $1 2> /dev/null | sed -e 's/$/\\n/') ) # now send the message echo -e "From: $LIST_ADDRESS\nTo: $1\nSubject: none\n\n${MESSAGE_BODY[@]}" | sed -e 's/^ //' | $MAIL_AGENT $MAIL_AGENT_ARGS } function SendBounceMessage { #------------------------------------------------------------- # send a bounce message back to sender # # parameter(s): sender address # depends on function(s): GetMessageHeaders # returns: 0 on success #------------------------------------------------------------- # this is the body of the message to be sent, so no identation here echo "From: $LIST_ADDRESS To: $1 Subject: none Message from: $FROM Subject: [RETURNED MAIL] $SUBJECT Date: $DATE It was not possible to process this message. Either or both the message was not encrypted and/or signed, or you are not subscribed to this list. Contact the list administrator if you have any questions. -- firma v$VERSION" | $MAIL_AGENT $MAIL_AGENT_ARGS } function ProcessMessage { #------------------------------------------------------------- # process a received message # # parameter(s): none # depends on function(s): GetMessage, GetMessageHeaders, GetGpgMessage, GetGpgDecryptStderr, # GetSubscribersList, SendListMessage, SendWarningMessage, SendBounceMessage # returns: 0 on success #------------------------------------------------------------- local email GetMessage GetMessageHeaders GetGpgMessage # if signature in message is valid, encrypt and send it for each list subscriber if GetGpgDecryptStderr | grep -q '^\[GNUPG:] GOODSIG'; then for email in $(GetSubscribersList); do SendListMessage $email done # else, if signature is invalid, email it back to the list administrator(s) and to sender elif GetGpgDecryptStderr | grep -q '^\[GNUPG:] BADSIG'; then for email in $LIST_ADMIN $SENDER_ADDRESS; do SendWarningMessage $email done # else, probably either the message was not encrypted/signed or the sender is not subscribed to the list # send a bounce message back to sender including a note about this # todo: parse STDERR to find out why the signature couldn't be checked and send more specific errors back to sender else SendBounceMessage $SENDER_ADDRESS fi } function NewList { #------------------------------------------------------------- # create a list if it doesn't exist yet # # parameter(s): none # depends on function(s): DeclareGpgVars # returns: 0 on success, 1 if list already exists or cannot be created #------------------------------------------------------------- if [ ! -d "$LIST_PATH" ]; then echo creating folder $LIST_PATH... mkdir "$LIST_PATH" # || (echo "$(basename $0): error creating $LIST_PATH: installation aborted"; exit 1) echo "creating list config file and will ask some questions." # comented: # read -rep "path to smtp command (eg, /usr/sbin/sendmail): " MAIL_AGENT # read -rep "command-line arguments passed to the smtp wrapper (eg, -oem -oi -t): " MAIL_AGENT_ARGS # read -rep "path to gpg binary (eg, /usr/bin/gpg): " GPG_BINARY # if [ ! -x "$GPG_BINARY" ]; then read -rep "list keyring folder: " LIST_HOMEDIR # removed: (defaults to $LIST_HOMEDIR) # todo: please no utf-8 (see DETAILS) read -rep "list email (eg, firma@domain.tld): " LIST_ADDRESS read -rep "list admins emails (space delimited): " LIST_ADMIN read -rep "list description (fake?): " DESCRIPTION read -resp "password for list keyring (use a huge one): " PASSPHRASE # todo: key specs (size, expiry date...) echo "creating your config..." touch $LIST_CONFIG_FILE chown root.root $LIST_CONFIG_FILE chmod 600 $LIST_CONFIG_FILE if [ -f "$LIST_CONFIG_FILE" ]; then DeclareGpgVars echo -e "LIST_HOMEDIR=$LIST_HOMEDIR\nLIST_ADDRESS=$LIST_ADDRESS\nLIST_ADMIN=$LIST_ADMIN\nPASSPHRASE=$PASSPHRASE" > $LIST_CONFIG_FILE # removed: MAIL_AGENT=$MAIL_AGENT\nGPG_BINARY=$GPG_BINARY\n echo "now generating your keyring..." $GPG --gen-key <&2 "$1: missing arguments (try \"help\")" return 1 ;; *) echo >&2 "Command not found -- $1 (try \"help\")" return 1 ;; esac ;; 2) case $1 in use) # check if argument is an email address if [[ -z "$(echo -ne $2 | grep -o '[^@]\+@[^@]\+')" ]]; then echo >&2 "$1: invalid argument -- $2 (try \"help\")" return 1 else ChooseUid $2 fi ;; help|quit) echo >&2 "$1: too many arguments -- $@ (try \"help\")" return 1 ;; *) echo >&2 "Command not found -- $1 (try \"help\")" return 1 ;; esac ;; *) case $1 in help|quit|use) echo >&2 "$1: too many arguments -- $@ (try \"help\")" return 1 ;; *) echo >&2 "Command not found -- $1 (try \"help\")" return 1 ;; esac ;; esac } function ChooseUid { #------------------------------------------------------------- # choose which UID of a public key should be used for message delivery, # deleting all other UIDs on this key # # parameter(s): chosen email address # depends on function(s): DeclareGpgVars # returns: 0 on success, # 1 if task can't be executed (public key not found, only one UID on key, etc.) #------------------------------------------------------------- local keyid="$($GPG_LIST_KEYS --with-fingerprint $1 2> /dev/null | grep ^fpr | cut -d : -f 10 | grep -o '.\{8\}$')" local uid_count="$($GPG_LIST_KEYS --fixed-list-mode $keyid 2> /dev/null | grep ^uid | wc -l)" local chosen_uid_number="$($GPG_LIST_KEYS --fixed-list-mode $keyid 2> /dev/null | grep ^uid | grep -ni $1 | cut -d : -f 1)" # check if supplied address is associated with a public key if [[ -z "$($GPG_LIST_KEYS --fixed-list-mode $1 2> /dev/null | grep -i "<$1>:$")" ]]; then echo >&2 "use: \"$(echo -ne $1 | tr '[:upper:]' '[:lower:]')\" is not associated with any public key on this keyring." return 1 # 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 # and then check if there's only one public key associated with this address elif [[ "$($GPG_LIST_KEYS --fixed-list-mode $1 2> /dev/null | grep -i "<$1>:$" | wc -l)" -gt "1" ]]; then echo >&2 "use: \"$(echo -ne $1 | tr '[:upper:]' '[:lower:]')\" is listed in more than one UID on this keyring." echo >&2 "Delete all but one of the public keys or UIDs associated with this email address." return 1 fi # if all checks are OK, run the expect script bellow expect -nN -- << EOF # no output to STDOUT log_user 0 # set a 5 seconds timeout in case anything goes wrong set timeout 5 # 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] } # 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 EOF } #------------------------------------------------------------- # main() #------------------------------------------------------------- # set enviromental variables and options export LANG=en_US umask 0077 # declare global arrays used during execution GLOBAL_ARRAYS="ORIG_MESSAGE ORIG_MESSAGE_HEADERS ORIG_GPG_MESSAGE MESSAGE_BODY" for ARRAY in $GLOBAL_ARRAYS; do declare -a $ARRAY done # command line parsing: # first check number of arguments, then check what was entered # start main case case $# in 0) echo >&2 "$(basename $0): missing arguments" Usage exit 1 ;; 1) # start case #1 case $1 in -h|--help) Usage ;; -v|--version) Version ;; # valid option called without its required argument -a|--admin-task|-c|--create-newlist|-p|--process-message|-r|--list-request) echo >&2 "$(basename $0): missing arguments" Usage exit 1 ;; *) echo >&2 "$(basename $0): invalid option -- $1" Usage exit 1 ;; # end case #1 esac ;; 2) # if firma.conf exists, evaluate its parameters and check them if [ -f "$FIRMA_CONFIG_FILE" ]; then shopt -u sourcepath && source "$FIRMA_CONFIG_FILE" CheckFirmaConfigFile else echo >&2 "$(basename $0): cannot source \`$FIRMA_CONFIG_FILE': No such file" exit 1 fi LIST_NAME="$2" LIST_PATH="$LISTS_DIR/$LIST_NAME" LIST_CONFIG_FILE="$LIST_PATH/$LIST_NAME.conf" # start case #2 # branch directly to options which don't use a configuration #+file or, for those which do, branch to a new case bellow case $1 in -c|--create-newlist) NewList ;; -a|--admin-task|-p|--process-message|-r|--list-request) # if the list configuration file exists, disable "sourcepath" #+and evaluate list parameters if [[ -f "$LIST_CONFIG_FILE" ]]; then shopt -u sourcepath && source "$LIST_CONFIG_FILE" else echo >&2 "$(basename $0): cannot source \`$LIST_CONFIG_FILE': No such file" exit 1 fi # get gpg parameters and check the list configuration file DeclareGpgVars CheckListConfigFile # start case #3 case $1 in -a|--admin-task) # read STDIN and, if line is not empty or commented, process command while read -rep "Command> " STDIN; do if [[ "$STDIN" && "$STDIN" != "#"* ]]; then ListAdministration $STDIN fi done ;; -p|--process-message) ProcessMessage ;; -r|--list-request) # not implemented yet #ListRequest exit 0 ;; # end case #3 esac ;; # valid option called with too many arguments -h|--help|-v|--version) echo >&2 "$(basename $0): too many arguments -- $@" Usage exit 1 ;; *) echo >&2 "$(basename $0): invalid option -- $1" Usage exit 1 ;; # end case #2 esac ;; *) # start case #4 case $1 in # again, valid option called with too many arguments -a|--admin-task|-c|--create-newlist|-h|--help|-p|--process-message|-r|--list-request|-v|--version) echo >&2 "$(basename $0): too many arguments -- $@" Usage exit 1 ;; *) echo >&2 "$(basename $0): invalid option -- $1" Usage exit 1 ;; # end case #4 esac ;; # end main case esac # erase all global arrays for ARRAY in $GLOBAL_ARRAYS; do unset $ARRAY done