source: firma

Last change on this file was c992730, checked in by Silvio Rhatto <rhatto@…>, 4 years ago

Releasing 0.4

  • Property mode set to 100755
File size: 84.9 KB
Line 
1#!/bin/bash
2#
3# firma: GnuPG-based encrypted mailing list manager
4# Feedback: firma@sarava.org
5#
6#  Firma is free software; you can redistribute it and/or modify it under the
7#  terms of the GNU General Public License as published by the Free Software
8#  Foundation; either version 2 of the License, or (at your option) any later
9#  version.
10#
11#  Firma is distributed in the hope that it will be useful, but WITHOUT ANY
12#  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
13#  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
14#
15#  You should have received a copy of the GNU General Public License along with
16#  this program; if not, write to the Free Software Foundation, Inc., 59 Temple
17#  Place - Suite 330, Boston, MA 02111-1307, USA
18#
19
20function Usage {
21  #-------------------------------------------------------------
22  # display help
23  #
24  # parameter(s): none
25  # depends on function(s): none
26  # returns: 0
27  #-------------------------------------------------------------
28
29  # this will be printed to STDOUT, so no indentation here
30  echo "\
31Usage: $BASENAME OPTION [LIST-NAME]
32GnuPG-based encrypted mailing list manager.
33
34  -a, --admin-task LIST-NAME        process administrative tasks on list
35  -c, --create-newlist LIST-NAME    create a new mailing list
36  -e, --email-admin-task LIST-NAME  process administrative tasks via email
37  -h, --help                        display help and exit
38  -p, --process-message LIST-NAME   process a message sent to list
39  -v, --version                     output version information and exit
40
41If option -a is given, read standard input for tasks to be performed.
42Tasks can be one or more of the following:
43$(AdminHelp)
44
45For help with admin and config paramaters, type $BASENAME --help task-name
46
47Report bugs to <firma@firma.sarava.org>, encrypting your message using the
48public key 3DF104314023E18358EFDD270D51B4E79E693CF7, available at keys.indymedia.org."
49}
50
51
52function Version {
53  #-------------------------------------------------------------
54  # display version information
55  #
56  # parameter(s): none
57  # depends on function(s): none
58  # returns: 0
59  #-------------------------------------------------------------
60
61  # this will be printed to STDOUT, so no indentation here
62  echo "\
63firma $VERSION
64
65Copyright (C) 2005-2007 A Firma
66This program comes with ABSOLUTELY NO WARRANTY.
67This is free software, and you are welcome to redistribute it
68under certain conditions. See the GNU General Public License
69for more details."
70}
71
72
73function DeclareGpgVars {
74  #-------------------------------------------------------------
75  # declare gpg-related global variables
76  #
77  # parameter(s): none
78  # depends on function(s): none
79  # returns: 0
80  #-------------------------------------------------------------
81
82  GPG_FLAGS="--no-options --no-default-keyring --homedir $LIST_HOMEDIR --quiet --no-tty --batch --no-use-agent --no-permission-warning"
83  GPG_FLAGS_NO_BATCH="--no-options --no-default-keyring --homedir $LIST_HOMEDIR --quiet --no-batch --no-use-agent --no-permission-warning"
84  GPG="$GPG_BINARY $GPG_FLAGS"
85  GPG_NOBATCH="$GPG_BINARY $GPG_FLAGS_NO_BATCH"
86  GPG_LIST_KEYS="$GPG --list-keys --with-colons"
87  GPG_DECRYPT="$GPG --passphrase-fd 0 --decrypt"
88  GPG_ENCRYPT="$GPG --armor --trust-model always --local-user $LIST_ADDRESS --passphrase-fd 0 --no-emit-version --sign --encrypt"
89}
90
91
92function CheckPassphrase {
93  #-------------------------------------------------------------
94  # check if a passphrase is valid: is 25 characters long or more;
95  #+includes lower and upper case letters, numbers and at least 1
96  #+punctuation character; and no character is sequentially
97  #+repeated more than 4 times.
98  #
99  # parameter(s): none
100  # depends on function(s): none
101  # returns: 0 if passphrase is valid,
102  #          1 if invalid
103  #-------------------------------------------------------------
104
105  local -i return_code=0
106
107  if [[ -z "$PASSPHRASE" || \
108       "$(echo "$PASSPHRASE" | wc -c)" -lt "25" || \
109       -z "$(echo "$PASSPHRASE" | tr -dc '[:lower:]')" || \
110       -z "$(echo "$PASSPHRASE" | tr -dc '[:upper:]')" || \
111       -z "$(echo "$PASSPHRASE" | tr -dc '[:digit:]')" || \
112       "$(echo "$PASSPHRASE" | tr -dc '[:punct:]' | wc -c)" -lt "1" || \
113       -n "$(echo "$PASSPHRASE" | fold -w1 | uniq -cd | grep -v '^ \{6\}[234] ')"
114     ]]; then
115    return_code=1
116  fi
117
118  return $return_code
119}
120
121
122function CheckFirmaConfigFile {
123  #-------------------------------------------------------------
124  # check firma.conf parameters
125  #
126  # parameter(s): none
127  # depends on function(s): none
128  # returns: 0 if all checks are passed,
129  #          1 on any fatal errors
130  #-------------------------------------------------------------
131
132  local -i return_code=0
133  local gpg_version
134
135  # check LOG_TO_SYSLOG value first, since it will define if firma
136  #+should print or log error messages
137  if [[ -n "$LOG_TO_SYSLOG" && \
138        "$LOG_TO_SYSLOG" != "0" && \
139        "$LOG_TO_SYSLOG" != "1"
140     ]]; then
141
142    LOG_TO_SYSLOG="0"
143    LogMessage "\
144WARNING: LOG_TO_SYSLOG should be set either to '0' or '1'.
145WARNING: Setting LOG_TO_SYSLOG to '0' for this run."
146
147  elif [[ -z "$LOG_TO_SYSLOG" ]]; then
148    LOG_TO_SYSLOG="0"
149  elif [[ "$LOG_TO_SYSLOG" == "1" ]]; then
150
151    if [[ ! -f "$LOGGER_BINARY" || ! -x "$LOGGER_BINARY" ]]; then
152
153      LOG_TO_SYSLOG="0"
154      LogMessage "\
155WARNING: Logger binary ($LOGGER_BINARY) could not be found.
156WARNING: Setting LOG_TO_SYSLOG to '0' for this run."
157
158    else # SYSLOG_PRIORITY defaults to "user.err"
159      SYSLOG_PRIORITY=${SYSLOG_PRIORITY:-"user.err"}
160    fi
161
162  fi
163
164  # check GPG_BINARY value
165  if [[ ! -f "$GPG_BINARY" || ! -x "$GPG_BINARY" ]]; then
166    LogMessage "FATAL: GPG binary ($GPG_BINARY) could not be found. Quitting."
167    return_code=1
168
169  # check MAIL_AGENT value
170  elif [[ ! -f "$MAIL_AGENT" || ! -x "$MAIL_AGENT" ]]; then
171    LogMessage "FATAL: Mail transport agent binary ($MAIL_AGENT) could not be found. Quitting."
172    return_code=1
173
174  # check LISTS_DIR value
175  elif [[ ! -d "$LISTS_DIR" ]]; then
176    LogMessage "FATAL: Lists directory ($LISTS_DIR) could not be found. Quitting."
177    return_code=1
178
179  # optional parameters
180  else
181
182    # check USE_GPG_HIDDEN_RECIPIENT_OPTION value
183    if [[ -n "$USE_GPG_HIDDEN_RECIPIENT_OPTION" && \
184          "$USE_GPG_HIDDEN_RECIPIENT_OPTION" != "0" && \
185          "$USE_GPG_HIDDEN_RECIPIENT_OPTION" != "1"
186       ]]; then
187
188      LogMessage "\
189WARNING: USE_GPG_HIDDEN_RECIPIENT_OPTION should be set either to '0' or '1'.
190WARNING: Setting USE_GPG_HIDDEN_RECIPIENT_OPTION to '0' for this run."
191      USE_GPG_HIDDEN_RECIPIENT_OPTION="0"
192
193    elif [[ -z "$USE_GPG_HIDDEN_RECIPIENT_OPTION" ]]; then
194      USE_GPG_HIDDEN_RECIPIENT_OPTION="0"
195    elif [[ "$USE_GPG_HIDDEN_RECIPIENT_OPTION" == "1" ]]; then
196
197      gpg_version="$($GPG_BINARY --version | head -n 1 | tr -dc '[:digit:]')"
198      if [[ "$gpg_version" -lt "140" ]]; then
199
200        LogMessage "\
201WARNING: GPG's \"--hidden-recipient\" option is only available from version 1.4.0 onwards.
202WARNING: Setting USE_GPG_HIDDEN_RECIPIENT_OPTION to '0' for this run."
203        USE_GPG_HIDDEN_RECIPIENT_OPTION="0"
204
205      fi
206
207    fi
208
209    # check FIRMA_USER value
210    if [[ -z "$(echo "$FIRMA_USER" | tr -d '[:space:]')" ]]; then
211      FIRMA_USER="nobody"
212    fi
213
214    # check FIRMA_GROUP value
215    if [[ -z "$(echo "$FIRMA_GROUP" | tr -d '[:space:]')" ]]; then
216      FIRMA_GROUP="nobody"
217    fi
218
219    # check KEYSERVER value
220    if [[ -z "$(echo "$KEYSERVER" | tr -d '[:space:]')" ]]; then
221      KEYSERVER="keys.indymedia.org"
222    fi
223
224  fi
225
226  return $return_code
227}
228
229
230function CheckListConfigFile {
231  #-------------------------------------------------------------
232  # check list configuration file parameters
233  #
234  # parameter(s): none
235  # depends on function(s): DeclareGpgVars
236  # returns: 0 if all checks are passed,
237  #          1 on any fatal errors
238  #-------------------------------------------------------------
239
240  local -i return_code=0
241  local administrator
242  local valid_admins
243  local replay_default_file="/var/log/firma/replay.db"
244
245  # check LIST_HOMEDIR value
246  if [[ ! -d "$LIST_HOMEDIR" || \
247        ! -f "$LIST_HOMEDIR/pubring.gpg" || \
248        ! -f "$LIST_HOMEDIR/secring.gpg"
249     ]]; then
250    LogMessage "FATAL: $LIST_NAME: GPG home directory ($LIST_HOMEDIR) or the GPG keyrings could not be found. Quitting."
251    return_code=1
252
253  # check PASSPHRASE value
254  elif [[ -z "$(grep -o "^PASSPHRASE='[^']*'$" $LIST_CONFIG_FILE)" ]] || \
255          ! CheckPassphrase; then
256    LogMessage "FATAL: $LIST_NAME: List passphrase is empty or does not meet the minimum complexity requirements. Quitting."
257    return_code=1
258
259  # check LIST_ADDRESS value, confirming if the list private key is present
260  elif [[ -z "$($GPG --list-secret-keys --with-colons --fixed-list-mode "<$LIST_ADDRESS>" 2> /dev/null)" ]]; then
261    LogMessage "FATAL: $LIST_NAME: List's secret key could not be found. Quitting."
262    return_code=1
263
264  # optional parameters
265  else
266
267    # check if the list has an administrator (or more than one)
268    if [[ -z "$(echo "$LIST_ADMIN" | tr -d '[:space:]')" ]]; then
269      LogMessage "WARNING: $LIST_NAME: List has no administrator."
270      LIST_ADMIN=""
271    else
272
273      # check if the public key(s) of the list administrator(s) is(are) present
274      valid_admins=""
275      for administrator in $LIST_ADMIN; do
276
277        if [[ -z "$($GPG_LIST_KEYS --fixed-list-mode "<$administrator>" 2> /dev/null | \
278                      grep -v '^tru:')"
279           ]]; then
280          LogMessage "\
281WARNING: $LIST_NAME: Public key for list administrator \"$administrator\" could not be found.
282WARNING: $LIST_NAME: Removing this address from LIST_ADMIN for this run."
283        else
284          valid_admins="$valid_admins $administrator"
285        fi
286
287      done
288      LIST_ADMIN="$valid_admins"
289
290      if [[ -z "$(echo "$LIST_ADMIN" | tr -d '[:space:]')" ]]; then
291        LogMessage "WARNING: $LIST_NAME: List has no valid administrator."
292        LIST_ADMIN=""
293      fi
294
295    fi
296
297    # check if LIST_REQUEST_ADDRESS has already been set
298    if [[ -z "$(echo "$LIST_REQUEST_ADDRESS" | tr -d '[:space:]')" ]]; then
299      LIST_REQUEST_ADDRESS="$(echo $LIST_ADDRESS | cut -d @ -f 1)-request@$(echo $LIST_ADDRESS | cut -d @ -f 2)"
300    fi
301
302    # check REQUIRE_SIGNATURE value
303    if [[ -n "$REQUIRE_SIGNATURE" && \
304          "$REQUIRE_SIGNATURE" != "0" && \
305          "$REQUIRE_SIGNATURE" != "1"
306       ]]; then
307
308      LogMessage "\
309WARNING: $LIST_NAME: REQUIRE_SIGNATURE should be set either to '0' or '1'.
310WARNING: $LIST_NAME: Setting REQUIRE_SIGNATURE to '1' for this run."
311      REQUIRE_SIGNATURE="1"
312
313    elif [[ -z "$REQUIRE_SIGNATURE" ]]; then
314      REQUIRE_SIGNATURE="1"
315    fi
316
317    # check REPLIES_SHOULD_GO_TO_LIST value
318    if [[ -n "$REPLIES_SHOULD_GO_TO_LIST" && \
319          "$REPLIES_SHOULD_GO_TO_LIST" != "0" && \
320          "$REPLIES_SHOULD_GO_TO_LIST" != "1"
321       ]]; then
322
323      LogMessage "\
324WARNING: $LIST_NAME: REPLIES_SHOULD_GO_TO_LIST should be set either to '0' or '1'.
325WARNING: $LIST_NAME: Setting REPLIES_SHOULD_GO_TO_LIST to '0' for this run."
326      REPLIES_SHOULD_GO_TO_LIST="0"
327
328    elif [[ -z "$REPLIES_SHOULD_GO_TO_LIST" ]]; then
329      REPLIES_SHOULD_GO_TO_LIST="0"
330    fi
331
332    # check HIDE_SENDER value
333    if [[ -n "$HIDE_SENDER" && \
334          "$HIDE_SENDER" != "0" && \
335          "$HIDE_SENDER" != "1"
336       ]]; then
337
338      LogMessage "\
339WARNING: $LIST_NAME: HIDE_SENDER should be set either to '0' or '1'.
340WARNING: $LIST_NAME: Setting HIDE_SENDER to '0' for this run."
341      HIDE_SENDER="0"
342
343    elif [[ -z "$HIDE_SENDER" ]]; then
344      HIDE_SENDER="0"
345    fi
346
347    # check REPLAY_PROTECTION value
348    if [[ -n "$REPLAY_PROTECTION" && \
349          "$REPLAY_PROTECTION" != "0" && \
350          "$REPLAY_PROTECTION" != "1"
351       ]]; then
352
353      LogMessage "\
354WARNING: $LIST_NAME: REPLAY_PROTECTION should be set either to '0' or '1'.
355WARNING: $LIST_NAME: Setting REPLAY_PROTECTION to '0' for this run."
356      REPLAY_PROTECTION="0"
357
358    elif [[ -z "$REPLAY_PROTECTION" ]]; then
359      REPLAY_PROTECTION="0"
360    elif [[ "$REPLAY_PROTECTION" == "1" ]]; then
361
362      # check REPLAY_COUNT value
363      if [[ -n "$REPLAY_COUNT" && \
364            -n "$(echo "$REPLAY_COUNT" | tr -d '[:digit:]')"
365         ]]; then
366
367        LogMessage "\
368WARNING: $LIST_NAME: REPLAY_COUNT should be a number.
369WARNING: $LIST_NAME: Setting REPLAY_COUNT to '150' for this run."
370        REPLAY_COUNT="150"
371
372      elif [[ -z "$REPLAY_COUNT" ]]; then
373        REPLAY_COUNT="150"
374      else # REPLAY_COUNT is either set to 0 (defaults to 150) or
375           #+contains a valid value
376
377        REPLAY_COUNT="$(( 10#$(echo "$REPLAY_COUNT" | tr -dc '[:digit:]') ))"
378        if [[ "$REPLAY_COUNT" == "0" ]]; then
379
380          LogMessage "\
381WARNING: $LIST_NAME: REPLAY_COUNT has to be greater than '0'.
382WARNING: $LIST_NAME: Setting REPLAY_COUNT to '150' for this run."
383          REPLAY_COUNT="150"
384
385        fi
386
387      fi
388
389      # check REPLAY_FILE value
390      if [[ -z "$(echo "$REPLAY_FILE" | tr -d '[:space:]')" ]]; then
391        REPLAY_FILE="$replay_default_file"
392      fi
393
394      touch "$REPLAY_FILE" 2> /dev/null
395      chown "$FIRMA_USER":"$FIRMA_GROUP" "$REPLAY_FILE" 2> /dev/null
396      chmod 600 "$REPLAY_FILE" 2> /dev/null
397
398      if [[ ! -r "$REPLAY_FILE" || ! -w "$REPLAY_FILE" ]]; then
399
400        LogMessage "\
401WARNING: $LIST_NAME: REPLAY_FILE ($REPLAY_FILE) can't be read or written to.
402WARNING: $LIST_NAME: Setting REPLAY_PROTECTION to '0' for this run."
403        REPLAY_PROTECTION="0"
404
405      fi
406
407    fi
408
409    # check DELIVERY_RANDOMIZATION value
410    if [[ -n "$DELIVERY_RANDOMIZATION" && \
411          -n "$(echo "$DELIVERY_RANDOMIZATION" | tr -d '[:digit:]')"
412       ]]; then
413
414      LogMessage "\
415WARNING: $LIST_NAME: DELIVERY_RANDOMIZATION should be a number.
416WARNING: $LIST_NAME: Setting DELIVERY_RANDOMIZATION to '0' for this run."
417      DELIVERY_RANDOMIZATION="0"
418
419    else # DELIVERY_RANDOMIZATION is either empty (defaults to 0) or
420         #+contains a valid value
421
422      DELIVERY_RANDOMIZATION="$(( 10#$(echo $DELIVERY_RANDOMIZATION | tr -dc '[:digit:]') ))"
423
424    fi
425
426    # check SILENTLY_DISCARD_INVALID_MESSAGES value
427    if [[ -n "$SILENTLY_DISCARD_INVALID_MESSAGES" && \
428          "$SILENTLY_DISCARD_INVALID_MESSAGES" != "0" && \
429          "$SILENTLY_DISCARD_INVALID_MESSAGES" != "1"
430       ]]; then
431
432      LogMessage "\
433WARNING: $LIST_NAME: SILENTLY_DISCARD_INVALID_MESSAGES should be set either to '0' or '1'.
434WARNING: $LIST_NAME: Setting SILENTLY_DISCARD_INVALID_MESSAGES to '0' for this run."
435      SILENTLY_DISCARD_INVALID_MESSAGES="0"
436
437    elif [[ -z "$SILENTLY_DISCARD_INVALID_MESSAGES" ]]; then
438      SILENTLY_DISCARD_INVALID_MESSAGES="0"
439    fi
440
441  fi
442
443  return $return_code
444}
445
446
447function GetMessage {
448  #-------------------------------------------------------------
449  # read message from STDIN
450  #
451  # parameter(s): none
452  # depends on function(s): none
453  # returns: 0 on success,
454  #          1 if there's no input
455  #-------------------------------------------------------------
456
457  local -i return_code=0
458
459  # store message in ORIG_MESSAGE
460  ORIG_MESSAGE="$(sed -ne '1,$p')"
461
462  # check if message was successfully stored
463  if [[ -z "$ORIG_MESSAGE" ]]; then
464    LogMessage "FATAL: Message couldn't be read from standard input. Quitting."
465    return_code=1
466  fi
467
468  return $return_code
469}
470
471
472function GetGpgMessage {
473  #-------------------------------------------------------------
474  # get the gpg encrypted part of the message
475  #
476  # parameter(s): none
477  # depends on function(s): GetMessage
478  # returns: 0 on success,
479  #          1 if encrypted bloc can't be located within the message
480  #-------------------------------------------------------------
481
482  local -i return_code=0
483
484  # find the first blank line in the message
485  FIRST_BLANK_LINE=$(echo "$ORIG_MESSAGE" | grep -nm 1 '^$' | cut -d : -f 1)
486
487  # then, find the beginning of the encrypted bloc
488  if [[ -n $FIRST_BLANK_LINE ]]; then
489    ENCRYPTED_BLOC_BEGINS=$(echo "$ORIG_MESSAGE" | grep -nm 1 -- '^-----BEGIN PGP MESSAGE-----' | cut -d : -f 1)
490
491    # and then find the end of the bloc
492    if [[ -n $ENCRYPTED_BLOC_BEGINS ]]; then
493      ENCRYPTED_BLOC_ENDS=$(echo "$ORIG_MESSAGE" | grep -nm 1 -- '^-----END PGP MESSAGE-----' | cut -d : -f 1)
494
495      # if there's an encrypted bloc, store it in ORIG_GPG_MESSAGE
496      if [[ -n $ENCRYPTED_BLOC_ENDS ]]; then
497        ORIG_GPG_MESSAGE="$(
498          echo "$ORIG_MESSAGE" | \
499          sed -ne "$((${ENCRYPTED_BLOC_ENDS} + 1))q;${ENCRYPTED_BLOC_BEGINS},${ENCRYPTED_BLOC_ENDS}p"
500        )"
501      fi
502    fi
503  fi
504
505  # check if the bloc was successfully stored
506  if [[ -z "$ORIG_GPG_MESSAGE" ]]; then
507    LogMessage "WARNING: No valid GPG encrypted bloc found within the message"
508    return_code=1
509  fi
510
511  return $return_code
512}
513
514
515function ParseGpgDecryptStderr {
516  #-------------------------------------------------------------
517  # parse $GPG_DECRYPT STDERR for signature checking
518  #
519  # parameter(s): none
520  # depends on function(s): DeclareGpgVars, GetGpgMessage, GetSenderAddress
521  # returns: 0
522  #-------------------------------------------------------------
523
524  local gpg_decrypt_stderr
525
526  # get GPG_DECRYPT STDERR, discarding its STDOUT
527  gpg_decrypt_stderr="$(
528    echo -e "${PASSPHRASE}\n${ORIG_GPG_MESSAGE}" | \
529    ($GPG_DECRYPT --status-fd 2 1> /dev/null) 2>&1
530  )"
531
532  # check if message was encrypted with the list's public key
533  if
534    echo "$gpg_decrypt_stderr" | \
535    grep -q "^\[GNUPG:] ENC_TO $(
536      $GPG_LIST_KEYS $LIST_ADDRESS 2> /dev/null | \
537      sed -ne '/:[sca]*[^e][sca]*:$/d' -e '/^sub:[^ired]:/p' | \
538      cut -d : -f 5
539    )"
540  then
541    ENCRYPTED_TO_LIST="1"
542
543    # if it was, check if its signature is valid
544    if
545      echo "$gpg_decrypt_stderr" | \
546      grep -q '^\[GNUPG:] GOODSIG'
547    then
548      GOOD_SIGNATURE="1"
549
550      if [[ -z "$SENDER_ADDRESS" ]]; then
551        GetSenderAddress
552      fi
553
554      if
555        echo "$gpg_decrypt_stderr" | \
556        grep '^\[GNUPG:] GOODSIG' | \
557        grep -q "$SENDER_ADDRESS"
558      then
559        SIGNATURE_MADE_BY_SENDER="1"
560      else
561        SIGNATURE_MADE_BY_SENDER="0"
562      fi
563
564    # else, check if the signature is invalid (BAD signature)
565    elif
566      echo "$gpg_decrypt_stderr" | \
567      grep -q '^\[GNUPG:] BADSIG'
568    then
569      BAD_SIGNATURE="1"
570
571    # else, check if the signature couldn't be verified
572    elif
573      echo "$gpg_decrypt_stderr" | \
574      grep -q '^\[GNUPG:] ERRSIG'
575    then
576      SIGNATURE_CHECKING_FAILED="1"
577
578    # else, check if the message could at least be decrypted
579    elif
580      echo "$gpg_decrypt_stderr" | \
581      grep -q '^\[GNUPG:] DECRYPTION_OKAY'
582    then
583      MESSAGE_DECRYPTION_OKAY="1"
584
585    fi
586  fi
587}
588
589
590function GetSubscribersList {
591  #-------------------------------------------------------------
592  # get list subscriber addresses for message encryption and delivery
593  #
594  # parameter(s): none
595  # depends on function(s): DeclareGpgVars
596  # returns: 0 on success,
597  #          1 if there are no valid subscribers on list
598  #-------------------------------------------------------------
599
600  local -i return_code=0
601
602  # get subscribers' email addresses, excluding invalid, revoked,
603  #+expired and disabled keys, as well as any signing only keys
604  SUBSCRIBERS_LIST="$(
605    $GPG_LIST_KEYS 2> /dev/null | \
606    sed -ne "/$LIST_ADDRESS/Id" -e '/:[scaeSCA]*[^E][scaeSCA]*:$/d' -e '/:[scaeSCAE]*D[scaeSCAE]*:$/d' -e '/^pub:[^ired]:/p' | \
607    cut -d : -f 10 | \
608    grep -o '<[^<>]*>$' | \
609    sed -e 's/[<>]//g' | \
610    sort -d
611  )"
612
613  # check if the list has valid subscribers
614  if [[ -z "$SUBSCRIBERS_LIST" ]]; then
615    LogMessage "FATAL: $LIST_NAME: No valid subscribers on list \"$LIST_ADDRESS\". Quitting."
616    return_code=1
617  fi
618
619  return $return_code
620}
621
622
623function GetMessageHeadersAndBody {
624  #-------------------------------------------------------------
625  # store the message headers and body in two separate variables
626  #
627  # parameter(s): none
628  # depends on function(s): GetMessage, GetGpgMessage
629  # returns: 0
630  #-------------------------------------------------------------
631
632  # store everything up to the first blank line in ORIG_MESSAGE_HEADERS,
633  #+unfolding any folded headers
634  ORIG_MESSAGE_HEADERS="$(
635    echo "$ORIG_MESSAGE" | \
636    sed -ne "${FIRST_BLANK_LINE}q;1,$(($FIRST_BLANK_LINE - 1))p" | \
637    sed -e :a -e '$!N;s/[ \t]*\n[ \t]\+/ /;ta' -e 'P;D'
638  )"
639
640  # store everything after this line in ORIG_MESSAGE_BODY
641  ORIG_MESSAGE_BODY="$(
642    echo "$ORIG_MESSAGE" | \
643    sed -ne "$(($FIRST_BLANK_LINE + 1)),\$p"
644  )"
645}
646
647
648function EditListMessageHeaders {
649  #-------------------------------------------------------------
650  # edit the headers of a list message, removing specific lines, adding
651  #+a prefix to the Subject, etc
652  #
653  # parameter(s): none
654  # depends on function(s): GetMessageHeadersAndBody
655  # returns: 0
656  #-------------------------------------------------------------
657
658  local header
659  local sed_args
660
661  MESSAGE_HEADERS="$ORIG_MESSAGE_HEADERS"
662
663  # remove headers as/if defined by firma configuration file
664  if [[ -n "$REMOVE_THESE_HEADERS_ON_ALL_LISTS" ]]; then
665    for header in $REMOVE_THESE_HEADERS_ON_ALL_LISTS; do
666      sed_args="$sed_args -e /^${header}/Id"
667    done
668
669    MESSAGE_HEADERS="$(
670      echo "$MESSAGE_HEADERS" | \
671      sed $sed_args
672    )"
673  fi
674
675  # remove additional headers as/if defined by the list configuration file
676  if [[ -n "$REMOVE_THESE_HEADERS" ]]; then
677
678    # remove local variables contents, in case they have been used above
679    header=""
680    sed_args=""
681
682    for header in $REMOVE_THESE_HEADERS; do
683      sed_args="$sed_args -e /^${header}/Id"
684    done
685
686    MESSAGE_HEADERS="$(
687      echo "$MESSAGE_HEADERS" | \
688      sed $sed_args
689    )"
690  fi
691
692  # insert/replace the Reply-To header
693  if [[ "$REPLIES_SHOULD_GO_TO_LIST" == "1" ]]; then
694
695    if ! echo "$MESSAGE_HEADERS" | \
696         grep -iq '^Reply-To:'; then
697
698      # these are the headers of the message to be sent, so no indentation here
699      MESSAGE_HEADERS="\
700$MESSAGE_HEADERS
701Reply-To: $LIST_ADDRESS"
702
703    else
704      MESSAGE_HEADERS="$(
705        echo "$MESSAGE_HEADERS" | \
706        sed -e "s/^Reply-To:.*$/Reply-To: $LIST_ADDRESS/I"
707      )"
708    fi
709  fi
710
711  # hide the sender
712  if [[ "$HIDE_SENDER" == "1" ]]; then
713    MESSAGE_HEADERS="$(
714      echo "$MESSAGE_HEADERS" | \
715      sed -e "s/^From:.*$/From: $LIST_ADDRESS/I"
716    )"
717  fi
718
719  # insert the Subject prefix, if any
720  if [[ -n "$SUBJECT_PREFIX" ]]; then
721
722    # first, check if there's a Subject line
723    if echo "$MESSAGE_HEADERS" | grep -iq '^Subject:'; then
724
725      # and then check if the Subject already contains the list prefix
726      if ! echo "$MESSAGE_HEADERS" | \
727           grep -im 1 '^Subject:' | \
728           grep -qF "$SUBJECT_PREFIX"; then
729
730        # if it doesn't, insert it
731        MESSAGE_HEADERS="$(
732          echo "$MESSAGE_HEADERS" | \
733          sed -e "s/^Subject:[ \t]*/Subject: $SUBJECT_PREFIX/I"
734        )"
735      fi
736
737    # else, if there's no Subject line, add one containing only the list prefix
738    else
739
740      # these are the headers of the message to be sent, so no indentation here
741      MESSAGE_HEADERS="\
742$MESSAGE_HEADERS
743Subject: $SUBJECT_PREFIX"
744
745    fi
746  fi
747}
748
749
750function DecryptGpgMessage {
751  #-------------------------------------------------------------
752  # decrypt the gpg encrypted part of the message
753  #
754  # parameter(s): none
755  # depends on function(s): DeclareGpgVars, GetGpgMessage
756  # returns: 0
757  #-------------------------------------------------------------
758
759  DECRYPTED_MESSAGE="$(
760    echo -e "${PASSPHRASE}\n${ORIG_GPG_MESSAGE}" | \
761    $GPG_DECRYPT 2> /dev/null
762  )"
763}
764
765
766function ReplaceGpgMessage {
767  #-------------------------------------------------------------
768  # replace the original encrypted bloc by one generated for the list subscribers
769  #
770  # parameter(s): none
771  # depends on function(s): GetGpgMessage, GetMessageHeadersAndBody
772  # returns: 0
773  #-------------------------------------------------------------
774
775  MESSAGE_BODY="$(
776    echo "$ORIG_MESSAGE_BODY" | \
777    sed -e "$(($ENCRYPTED_BLOC_BEGINS - $FIRST_BLANK_LINE)),$(($ENCRYPTED_BLOC_ENDS - $FIRST_BLANK_LINE))c $(
778      echo "$GPG_MESSAGE" | \
779      sed -e '$! s/$/\\/'
780    )"
781  )"
782}
783
784
785function GetSenderAddress {
786  #-------------------------------------------------------------
787  # get the sender address, needed for warning and bounce messages processing
788  #
789  # parameter(s): none
790  # depends on function(s): GetMessage
791  # returns: 0
792  #-------------------------------------------------------------
793
794  local from
795
796  from="$(echo "$ORIG_MESSAGE" | grep -im 1 '^From:')"
797  SENDER_ADDRESS=$(
798    if [[ -z "$(echo $from | grep '>$')" ]]; then
799      echo $from
800    else
801      echo $from | grep -o '<[^<>]*>$' | sed -e 's/[<>]//g'
802    fi
803  )
804}
805
806
807function AssembleMessage {
808  #-------------------------------------------------------------
809  # just put the message headers and body together
810  #
811  # parameter(s): none
812  # depends on function(s): EditListMessageHeaders, ReplaceGpgMessage,
813  #                         ComposeAndSendWarningMessage,
814  #                         ComposeAndSendBounceMessage
815  # returns: 0
816  #-------------------------------------------------------------
817
818  # this is the actual message which will be sent, so no indentation here
819  MESSAGE="\
820$MESSAGE_HEADERS
821
822$MESSAGE_BODY"
823
824}
825
826
827function ReEncryptAndSendListMessage {
828  #-------------------------------------------------------------
829  # send message to list subscribers
830  #
831  # parameter(s): none
832  # depends on function(s): DeclareGpgVars, DecryptGpgMessage,
833  #                         GetSubscribersList, AssembleMessage
834  # returns: 0
835  #-------------------------------------------------------------
836
837  local recipients
838  local subscriber
839
840  recipients="$(echo $SUBSCRIBERS_LIST)"
841
842  # check if message should be encrypted and sent to all subscribers at once
843  if [[ "$USE_GPG_HIDDEN_RECIPIENT_OPTION" == "1" ]]; then
844
845    GPG_MESSAGE="$(
846      echo -e "${PASSPHRASE}\n${DECRYPTED_MESSAGE}" | \
847      $GPG_ENCRYPT --group subscribers="$recipients" --hidden-recipient subscribers 2> /dev/null
848    )"
849
850    ReplaceGpgMessage
851    AssembleMessage
852    DeliveryRandomization
853
854    # send message
855    echo "$MESSAGE" | $MAIL_AGENT $MAIL_AGENT_ARGS $recipients
856
857  # else, message should be encrypted and sent to one subscriber at a time
858  else
859    for subscriber in $recipients; do
860
861      GPG_MESSAGE="$(
862        echo -e "${PASSPHRASE}\n${DECRYPTED_MESSAGE}" | \
863        $GPG_ENCRYPT --recipient $subscriber
864      )"
865
866      ReplaceGpgMessage
867      AssembleMessage
868      DeliveryRandomization
869
870      # send message
871      echo "$MESSAGE" | $MAIL_AGENT $MAIL_AGENT_ARGS $subscriber
872
873    done
874  fi
875}
876
877
878function ComposeAndSendWarningMessage {
879  #-------------------------------------------------------------
880  # send a "BAD signature" warning to the list administrator(s) and to sender
881  #
882  # parameter(s): none
883  # depends on function(s): GetMessage, GetSenderAddress, AssembleMessage
884  # returns: 0
885  #-------------------------------------------------------------
886
887  local recipients
888
889  recipients="$LIST_ADMIN $SENDER_ADDRESS"
890
891  # these are the headers of the message to be sent, so no indentation here
892  MESSAGE_HEADERS="\
893From: $LIST_ADDRESS
894Subject: BAD signature from $SENDER_ADDRESS
895To: $SENDER_ADDRESS
896Cc: $(echo $LIST_ADMIN | sed -e 's/ /, /g')"
897
898  # this is the body of the message to be sent, so no indentation here
899  MESSAGE_BODY="\
900-------- Original Message --------
901$ORIG_MESSAGE"
902
903  AssembleMessage
904
905  # send message
906  echo "$MESSAGE" | $MAIL_AGENT $MAIL_AGENT_ARGS $recipients
907}
908
909
910function ComposeAndSendBounceMessage {
911  #-------------------------------------------------------------
912  # send a bounce message back to sender
913  #
914  # parameter(s): none
915  # depends on function(s): GetMessage, GetSenderAddress, AssembleMessage
916  # returns: 0
917  #-------------------------------------------------------------
918
919  local subject
920
921  subject="$(echo "$ORIG_MESSAGE" | grep -im 1 '^Subject:' | cut -d : -f 2- )"
922
923  # these are the headers of the message to be sent, so no indentation here
924  MESSAGE_HEADERS="\
925From: $LIST_ADDRESS
926Subject: [RETURNED MAIL]$subject
927To: $SENDER_ADDRESS"
928
929  # this is the body of the message to be sent, so no indentation here
930  MESSAGE_BODY="\
931$MESSAGE_BODY
932
933--
934firma"
935
936  AssembleMessage
937
938  # send message
939  echo "$MESSAGE" | $MAIL_AGENT $MAIL_AGENT_ARGS $SENDER_ADDRESS
940}
941
942
943function ProcessMessage {
944  #-------------------------------------------------------------
945  # process a received message
946  #
947  # parameter(s): none
948  # depends on function(s): GetMessage, GetGpgMessage, GetSubscribersList,
949  #                         GetSenderAddress
950  # returns: 0 on success,
951  #          1 if any of the above functions return an error
952  #-------------------------------------------------------------
953
954  local -i return_code=0
955
956  # try to read message from STDIN
957  if GetMessage; then
958
959    # check if the message was encrypted
960    if GetGpgMessage; then
961
962      # look for replay attacks
963      if ReplayProtectionCheck; then
964
965        # if it was, parse gpg decrypt STDERR to decide what to do next
966        ParseGpgDecryptStderr
967
968        # if the message was encrypted with the list's public key and if the
969        #+message signature is valid, send message to list subscribers
970        if AllowMessageProcessing; then
971
972          # check if the list has valid subscribers
973
974          GetSenderAddress
975          GetMessageHeadersAndBody
976          EditListMessageHeaders
977          DecryptGpgMessage
978
979          if [[ "$MODE" == "list-message" ]]; then
980            if GetSubscribersList; then
981              ReEncryptAndSendListMessage
982            else
983              return_code=1
984            fi
985          elif [[ "$MODE" == "admin-non-interactive" ]]; then
986            EmailListAdministration
987          fi
988
989        # else, if the message was correctly encrypted but its signature is invalid,
990        #+send a warning about this to the list administrator(s) and to sender
991        elif [[ "$ENCRYPTED_TO_LIST" == "1" && "$BAD_SIGNATURE" == "1" && "$REQUIRE_SIGNATURE" == "1" ]]; then
992
993          GetSenderAddress
994
995          if [[ -n $(echo $LIST_ADMIN) || -n "$SENDER_ADDRESS" ]]; then
996            ComposeAndSendWarningMessage
997          fi
998
999        # else, a bounce should be sent
1000        else
1001
1002          # if bounce processing is enabled, continue
1003          if [[ "$SILENTLY_DISCARD_INVALID_MESSAGES" != "1" ]]; then
1004
1005            GetSenderAddress
1006            if [[ -n "$SENDER_ADDRESS" ]]; then
1007
1008              # if the message was encrypted with the list's public key
1009              if [[ $ENCRYPTED_TO_LIST == "1" ]]; then
1010
1011                # then, if signature can't be checked, then probably the sender is not subscribed to the list
1012                # send a bounce, if possible
1013                if [[ "$SIGNATURE_CHECKING_FAILED" == "1" && "$REQUIRE_SIGNATURE" == "1" ]]; then
1014
1015                  # this is the body of the message to be sent, so no indentation here
1016                  MESSAGE_BODY="\
1017 It was not possible to process this message. Your email
1018 address is not subscribed to this list. Contact the list
1019 administrator if you have any questions."
1020                  ComposeAndSendBounceMessage
1021
1022                # or, if message can be decrypted but its signature can't be checked, then message wasn't signed
1023                # send a bounce, if possible
1024                elif [[ $MESSAGE_DECRYPTION_OKAY == "1" ]]; then
1025
1026                  # this is the body of the message to be sent, so no indentation here
1027                  MESSAGE_BODY="\
1028 It was not possible to process this message. Message was
1029 not signed. Contact the list administrator if you have any
1030 questions."
1031                  ComposeAndSendBounceMessage
1032
1033                elif [[ "$SIGNATURE_MADE_BY_SENDER" != "1" && "$REQUIRE_SIGNATURE" == "1" ]]; then
1034
1035                # this is the body of the message to be sent, so no indentation here
1036                MESSAGE_BODY="\
1037 It was not possible to process this message. Message was
1038 not sent by the person who signed it."
1039
1040                  ComposeAndSendBounceMessage
1041
1042                fi
1043
1044              # else, message wasn't encrypted with the list's public key
1045              # send a bounce, if possible
1046              else
1047
1048                # this is the body of the message to be sent, so no indentation here
1049                MESSAGE_BODY="\
1050 It was not possible to process this message. Message was
1051 not encrypted with the list's public key. Contact the list
1052 administrator if you have any questions."
1053                ComposeAndSendBounceMessage
1054              fi
1055            fi
1056          fi
1057        fi
1058      else
1059
1060        # if bounce processing is enabled, continue
1061        if [[ "$SILENTLY_DISCARD_INVALID_MESSAGES" != "1" ]]; then
1062
1063          GetSenderAddress
1064          if [[ -n "$SENDER_ADDRESS" ]]; then
1065
1066            # the anti-replay mechanism detected a repeated message
1067            MESSAGE_BODY="\
1068 It was not possible to process this message. This list
1069 is configured to discard replayed messages as an attack
1070 protection measure. It looks like your message has been
1071 sent to the list before and so it was discarded. Contact
1072 the list administrator if you have any questions."
1073            ComposeAndSendBounceMessage
1074          fi
1075        fi
1076      fi
1077    # else, message wasn't encrypted at all
1078    # send a bounce, if possible
1079    else
1080
1081      # if bounce processing is enabled, continue
1082      if [[ "$SILENTLY_DISCARD_INVALID_MESSAGES" != "1" ]]; then
1083
1084        GetSenderAddress
1085        if [[ -n "$SENDER_ADDRESS" ]]; then
1086
1087          # this is the body of the message to be sent, so no indentation here
1088          MESSAGE_BODY="\
1089 It was not possible to process this message. Message was
1090 not encrypted. Contact the list administrator if you have
1091 have any questions."
1092          ComposeAndSendBounceMessage
1093        fi
1094      fi
1095    fi
1096
1097  # else, message could not be read from STDIN
1098  else
1099    return_code=1
1100  fi
1101
1102  return $return_code
1103}
1104
1105
1106function NewList {
1107  #-------------------------------------------------------------
1108  # create a list if it doesn't exist yet
1109  #
1110  # parameter(s): none
1111  # depends on function(s): DeclareGpgVars
1112  # returns: 0 on success, 1 if list already exists or cannot be created
1113  #-------------------------------------------------------------
1114
1115  local -i return_code=0
1116  local answer admin invalid method
1117  local last_char digits_only
1118
1119  # UTF-8 is avoided in DETAILS
1120  echo "Firma will ask you some questions to setup your list."
1121  echo "Please don't use UTF-8 characters."
1122
1123  read -rep "  List keyring location: ($LIST_PATH) " LIST_HOMEDIR
1124  LIST_HOMEDIR=${LIST_HOMEDIR:-"$LIST_PATH"}
1125
1126  if [[ -d "$LIST_HOMEDIR" ]]; then
1127    echo "Cannot create list $LIST_NAME: List already exists at $LIST_HOMEDIR"
1128    return_code=1
1129  else
1130
1131    echo "Creating folder $LIST_HOMEDIR..."
1132    mkdir -p $LIST_HOMEDIR
1133
1134    if [[ -d "$LIST_HOMEDIR" ]]; then
1135
1136      # list address
1137      while true; do
1138        read -rep "  List email address or 'quit' to exit: " LIST_ADDRESS
1139        if [[ "$LIST_ADDRESS" == "quit" ]]; then
1140          echo "Deleting folder $LIST_HOMEDIR..."
1141          rm -rf $LIST_HOMEDIR
1142          echo "List creation aborted."
1143          return_code=1
1144          break
1145        elif CheckValidEmail $LIST_ADDRESS; then
1146          break
1147        elif [[ -n "$LIST_ADDRESS" ]]; then
1148          echo "  Invalid email address: $LIST_ADDRESS"
1149        fi
1150      done
1151
1152      # admin emails
1153      if [[ "$return_code" == "0" ]]; then
1154        while true; do
1155          read -rep "  List administrator(s) email address(es) (space delimited) or 'quit' to exit: " LIST_ADMIN
1156          if [[ "$LIST_ADMIN" == "quit" ]]; then
1157            echo "Deleting folder $LIST_HOMEDIR..."
1158            rm -rf $LIST_HOMEDIR
1159            echo "List creation aborted."
1160            return_code=1
1161            break
1162          elif [[ -n "$LIST_ADMIN" ]]; then
1163            for admin in $LIST_ADMIN; do
1164              if ! CheckValidEmail $admin; then
1165                invalid="$(echo $invalid $admin | sed -e 's/  / /')"
1166              fi
1167            done
1168            if [[ -n "$invalid" ]]; then
1169              echo "  Invalid email address: $invalid"
1170              invalid=""
1171            else
1172              break
1173            fi
1174          fi
1175        done
1176      fi
1177
1178      # list description, passphrase and key size
1179      if [[ "$return_code" == "0" ]]; then
1180        read -rep "  List description (optional): " KEY_DESCRIPTION
1181        if [[ ! -z "$KEY_DESCRIPTION" ]]; then
1182          KEY_DESCRIPTION="Name-Real: $KEY_DESCRIPTION"
1183        fi
1184        while true; do
1185          read -rep "  Automatically create a passphrase for the list pubkey? (Y/n) " answer
1186          answer="$(echo $answer | tr '[:lower:]' '[:upper:]')"
1187          if [[ -z "$answer" || "$answer" == "Y" || "$answer" == "YES" ]]; then
1188            PASSPHRASE="$(RandomString 62)"
1189            while ! CheckPassphrase; do
1190              PASSPHRASE="$(RandomString 62)"
1191            done
1192            break
1193          elif [[ "$answer" == "N" || "$answer" == "NO" ]]; then
1194            read -resp "  Passphrase to protect the list's secret key (you'll type it once): " PASSPHRASE
1195            while ! CheckPassphrase; do
1196              echo ""
1197              read -resp "  Passphrase doesn't fit all the requirements, please choose another: " PASSPHRASE
1198            done
1199            break
1200          else
1201            echo "  Please answer either yes or no."
1202          fi
1203        done
1204
1205        while true; do
1206          echo "  Please choose a key size:"
1207          echo "    1 - 1024"
1208          echo "    2 - 2048"
1209          echo "    3 - 4096 (default)"
1210          read -rep "  Please choose a key size or 'quit' to exit:  " answer
1211          answer="$(echo $answer | tr '[:lower:]' '[:upper:]')"
1212          if [[ "$answer" == "QUIT" ]]; then
1213            echo "Deleting folder $LIST_HOMEDIR..."
1214            rm -rf $LIST_HOMEDIR
1215            echo "List creation aborted."
1216            return_code=1
1217            break
1218          elif [[ "$answer" == "1" || "$answer" == "1024" ]]; then
1219            KEY_SIZE="1024"
1220            break
1221          elif [[ "$answer" == "2" || "$answer" == "2048" ]]; then
1222            KEY_SIZE="2048"
1223            break
1224          elif [[ -z "$answer" || "$answer" == "3" || "$answer" == "4096" ]]; then
1225            KEY_SIZE="4096"
1226            break
1227          else
1228            echo "  Invalid answer."
1229          fi
1230        done
1231      fi
1232
1233      # key expiration
1234      if [[ "$return_code" == "0" ]]; then
1235        echo "  Choose a key validity:"
1236        echo "      0 = key does not expire (default)"
1237        echo "   <n>  = key expires in n days"
1238        echo "   <n>w = key expires in n weeks"
1239        echo "   <n>m = key expires in n months"
1240        echo "   <n>y = key expires in n years"
1241
1242        while true; do
1243          read -rep "  Please enter the key expiration time or 'quit' to exit: " KEY_EXPIRATION
1244          KEY_EXPIRATION="$(echo $KEY_EXPIRATION | tr '[:upper:]' '[:lower:]')"
1245          last_char="$(echo "$KEY_EXPIRATION" | grep -o '[wmy]$')"
1246          digits_only="$(echo "$KEY_EXPIRATION" | sed -e "s/${last_char}$//")"
1247          if [[ -z "$KEY_EXPIRATION" ]]; then
1248            KEY_EXPIRATION="0"
1249            break
1250          elif [[ "$KEY_EXPIRATION" == "quit" ]]; then
1251            echo "Deleting folder $LIST_HOMEDIR..."
1252            rm -rf $LIST_HOMEDIR
1253            echo "List creation aborted."
1254            return_code=1
1255            break
1256          elif [[ -z "$(echo $digits_only | sed -e 's/[0-9]//g')" ]]; then
1257            break
1258          else
1259            echo "  Invalid key expiration time."
1260          fi
1261        done
1262      fi
1263
1264      # config file creation
1265      if [[ "$return_code" == "0" ]]; then
1266        echo "Creating your config..."
1267        touch $LIST_CONFIG_FILE 2> /dev/null
1268        chmod 600 $LIST_CONFIG_FILE 2> /dev/null
1269        chown $FIRMA_USER:$FIRMA_GROUP $LIST_CONFIG_FILE 2> /dev/null
1270        if [[ -f "$LIST_CONFIG_FILE" ]]; then
1271          DeclareGpgVars
1272          echo -e "LIST_HOMEDIR='$LIST_HOMEDIR'\nLIST_ADDRESS='$LIST_ADDRESS'\nLIST_ADMIN='$LIST_ADMIN'\nPASSPHRASE='$PASSPHRASE'" > $LIST_CONFIG_FILE
1273          echo -e "KEY_SIZE='$KEY_SIZE'\nKEY_DESCRIPTION='$KEY_DESCRIPTION'" >> $LIST_CONFIG_FILE
1274          echo "Now generating your keyring..."
1275
1276          $GPG --gen-key <<EOF
1277
1278            Key-Type: DSA
1279            Key-Length: $KEY_SIZE
1280            Subkey-Type: ELG-E
1281            Subkey-Length: $KEY_SIZE
1282
1283            $KEY_DESCRIPTION
1284            Name-Email: $LIST_ADDRESS
1285
1286            Expire-Date: $KEY_EXPIRATION
1287            Passphrase: $PASSPHRASE
1288            %commit
1289
1290EOF
1291
1292          # import admins pubkeys
1293          while true; do
1294            read -rep "  Import list admins' pubkeys? (Y/n) " answer
1295            answer="$(echo $answer | tr '[:lower:]' '[:upper:]')"
1296            if [[ -z "$answer" || "$answer" == "Y" || "$answer" == "YES" ]]; then
1297
1298              echo "  Please choose a key import method:"
1299              echo "    1 - Fetch the keys from a keyserver"
1300              echo "    2 - Key material stored in a file"
1301
1302              while true; do
1303                read -rep "  Please enter your choice: " answer
1304                if [[ "$answer" == "1" ]]; then
1305                  read -rep "  Please enter the keyserver address (defaults to $KEYSERVER): " answer
1306                  method="keyserver $answer"
1307                  break
1308                elif [[ "$answer" == "2" ]]; then
1309                  method="file"
1310                  break
1311                else
1312                  echo "  Invalid answer. Choose either 1 or 2."
1313                fi
1314              done
1315
1316              SubscribeUsers $method $LIST_ADMIN
1317
1318              # send list pubkey to admins
1319              if [[ "$?" == "0" ]]; then
1320                while true; do
1321                  read -rep "  Send list public key to list admins? (Y/n) " answer
1322                  answer="$(echo $answer | tr '[:lower:]' '[:upper:]')"
1323                  if [[ -z "$answer" || "$answer" == "Y" || "$answer" == "YES" ]]; then
1324                    SourceListConfig
1325                    CheckListConfigFile
1326                    SendListPubkey $LIST_ADMIN
1327                    break
1328                  elif [[ "$answer" == "N" || "$answer" == "NO" ]]; then
1329                    echo "  Not sending public key from list to admins. Do it manually."
1330                    break
1331                  else
1332                    echo "  Please answer either yes or no."
1333                  fi
1334                done
1335              fi
1336
1337              break
1338            elif [[ "$answer" == "N" || "$answer" == "NO" ]]; then
1339              echo "  Not sending public key from list to admins. Do it manually."
1340              break
1341            else
1342              echo "  Please answer either yes or no."
1343            fi
1344          done
1345
1346          # fix permissions
1347          chown -R $FIRMA_USER:$FIRMA_GROUP $LIST_HOMEDIR 2> /dev/null
1348
1349          echo "Your list was created. Now check its configuration at $LIST_CONFIG_FILE."
1350          echo "To see a list of optional config parameters, type firma --help config."
1351        fi
1352      fi
1353    else
1354      echo "Could not create list homedir $LIST_HOMEDIR."
1355      return_code=1
1356    fi
1357  fi
1358
1359  # list creation should be atomic
1360  if [[ "$return_code" == "0" ]]; then
1361    echo "List creation complete."
1362  fi
1363
1364  return $return_code
1365}
1366
1367
1368function AdminHelp {
1369  #-------------------------------------------------------------
1370  # display help on admin commands
1371  #
1372  # parameter(s): none
1373  # depends on function(s): none
1374  # returns: 0
1375  #-------------------------------------------------------------
1376
1377  # this will be printed to STDOUT, so no indentation here
1378  echo "
1379  quit                             quit the admin prompt
1380  help                             show this help
1381  list                             show list subscribers
1382  listinfo                         show list information
1383  config                           list configuration
1384  info EMAIL-ADDRESS               show info of a given subscriber
1385  sendkey SUBSCRIBER               send list pubkey to subscriber
1386  sub|subscribe [..]               subscribe users ('subscribe help' for options)
1387  unsub|unsubscribe EMAIL-ADDRESS  unsubscribe an email from the list
1388  use EMAIL-ADDRESS                use the given address for message delivery instead
1389                                   of the primary address on key
1390"
1391}
1392
1393
1394function ListAdministration {
1395  #-------------------------------------------------------------
1396  # process administrative tasks
1397  #
1398  # parameter(s): task to be performed (plus its argument(s))
1399  # depends on function(s): ChooseUid, CheckValidEmail, UbsubscribeUser
1400  #                         SubscribeUsers, SendListPubkey
1401  # returns: 0 if task is executed successfully,
1402  #          1 if task can't be executed (command not found, too many/missing arguments, etc.),
1403  #          3 if a quit command is entered
1404  #-------------------------------------------------------------
1405
1406  local -i return_code=0
1407  local subscribers
1408
1409  # NOTE: when adding a new admin command, dont forget to
1410  #       - add a summary on AdminHelp function
1411  #       - if needed, add a "help" option to show more detailed help
1412
1413  case $# in
1414    1)
1415      case $1 in
1416        help)
1417          AdminHelp
1418          ;;
1419        quit)
1420          return_code=3
1421          ;;
1422        use)
1423          AdminLog "$1: missing arguments (try \"help\")"
1424          return_code=1
1425          ;;
1426        unsub|unsubscribe)
1427          AdminLog "$1: missing arguments (try \"help\")"
1428          return_code=1
1429          ;;
1430        list)
1431          GetSubscribersList
1432          for subscriber in $SUBSCRIBERS_LIST; do
1433            AdminLog $subscriber"
1434          done
1435          ;;
1436        sub|subscribe)
1437          AdminLog "$1: missing arguments (try \"$1 help\")"
1438          return_code=1
1439          ;;
1440        sendkey)
1441          AdminLog "$1: missing arguments (try \"$1 help\")."
1442          return_code=1
1443          ;;
1444        info)
1445          AdminLog "$1: missing arguments (try \"help\")."
1446          return_code=1
1447          ;;
1448        listinfo)
1449          GetSubscribersInfo $LIST_ADDRESS
1450          ;;
1451        config)
1452          AdminLog "$1: missing arguments (try \"$1 help\")."
1453          return_code=1
1454          ;;
1455        *)
1456          AdminLog "Command not found -- $1 (try \"help\")"
1457          return_code=1
1458          ;;
1459      esac
1460      ;;
1461    2)
1462      case $1 in
1463        use)
1464          # check if argument is an email address
1465          if CheckValidEmail $2; then
1466            ChooseUid $2
1467          else
1468            AdminLog "$1: invalid argument -- $2 (try \"help\")"
1469            return_code=1
1470          fi
1471          ;;
1472        unsub|unsubscribe)
1473          UnsubscribeUser $2
1474          return_code=$?
1475          ;;
1476        sub|subscribe)
1477          SubscribeUsers $2
1478          return_code=$?
1479          ;;
1480        sendkey)
1481          SendListPubkey $2
1482          return_code=$?
1483          ;;
1484        info)
1485          GetSubscribersInfo $2
1486          return_code=$?
1487          ;;
1488        listinfo)
1489          AdminLog "$1: too many arguments -- $@ (try \"help\")"
1490          return_code=1
1491          ;;
1492        config)
1493          if [[ "$2" == "help" ]]; then
1494            ConfigHelp
1495            return_code=$?
1496          else
1497            AdminLog "$1: too many arguments -- $@ (try \"$1 help\")"
1498            return_code=1
1499          fi
1500          ;;
1501        help|quit)
1502          AdminLog "$1: too many arguments -- $@ (try \"help\")"
1503          return_code=1
1504          ;;
1505        *)
1506          AdminLog "Command not found -- $1 (try \"help\")"
1507          return_code=1
1508          ;;
1509      esac
1510      ;;
1511    *)
1512      case $1 in
1513        help|quit|use)
1514          AdminLog "$1: too many arguments -- $@ (try \"help\")"
1515          return_code=1
1516          ;;
1517        sub|subscribe)
1518          shift
1519          SubscribeUsers $*
1520          return_code=$?
1521          ;;
1522        sendkey)
1523          shift
1524          SendListPubkey $*
1525          return_code=$?
1526          ;;
1527        info)
1528          shift
1529          GetSubscribersInfo $*
1530          return_code=$?
1531          ;;
1532        listinfo)
1533          AdminLog "$1: too many arguments -- $@ (try \"help\")"
1534          return_code=1
1535          ;;
1536        *)
1537          AdminLog "Command not found -- $1 (try \"help\")"
1538          return_code=1
1539          ;;
1540      esac
1541      ;;
1542  esac
1543
1544  return $return_code
1545}
1546
1547
1548function ChooseUid {
1549  #-------------------------------------------------------------
1550  # choose which UID of a public key should be used for message delivery,
1551  #+deleting all other UIDs on the key
1552  #
1553  # parameter(s): chosen email address
1554  # depends on function(s): DeclareGpgVars
1555  # returns: 0 on success,
1556  #          1 if task can't be executed (public key not found, only one UID on key, etc.)
1557  #-------------------------------------------------------------
1558
1559  local -i return_code=0
1560  local keyid="$($GPG_LIST_KEYS --with-fingerprint $1 2> /dev/null | grep ^fpr | cut -d : -f 10)"
1561  local uid_count="$($GPG_LIST_KEYS --fixed-list-mode $keyid 2> /dev/null | grep ^uid | wc -l)"
1562  local chosen_uid_number="$($GPG_LIST_KEYS --fixed-list-mode $keyid 2> /dev/null | grep ^uid | grep -ni "$1" | cut -d : -f 1)"
1563
1564  # check if supplied address is associated with a public key
1565  if [[ -z "$($GPG_LIST_KEYS --fixed-list-mode "<$1>" 2> /dev/null | grep -v '^tru:')" ]]; then
1566    AdminLog "use: \"$1\" is not associated with any public key on this keyring."
1567    return_code=1
1568  # then check if there's more than one UID on this public key
1569  elif (( "$($GPG_LIST_KEYS --fixed-list-mode $1 2> /dev/null | grep ^uid | wc -l)" == "1" )); then
1570    AdminLog "use: \"$1\" is part of the only UID on public key ${keyid:32}."
1571    return_code=1
1572  # and then check if there's only one public key associated with this address
1573  elif (( "$($GPG_LIST_KEYS --fixed-list-mode $1 2> /dev/null | grep -i "<$1>:$" | wc -l)" > 1 )); then
1574    AdminLog "use: \"$1\" is listed in more than one UID on this keyring."
1575    AdminLog "Delete all but one of the public keys or UIDs associated with this email address."
1576    return_code=1
1577  fi
1578
1579  # if all checks are OK, run the expect script bellow
1580  if (( $return_code == "0" )); then
1581    expect -nN -- << EOF
1582      # no output to STDOUT
1583      log_user 0
1584      # set a 5 seconds timeout in case anything goes wrong
1585      set timeout 5
1586
1587      # call gpg with the "--edit-key" option
1588      eval spawn -noecho $GPG --with-colons --command-fd 0 --status-fd 1 --edit-key $keyid
1589      expect "GET_LINE keyedit.prompt" {
1590        # select for deletion all UIDs other than the chosen one
1591        set uid 1
1592        while { \$uid <= $uid_count } {
1593          if { \$uid != $chosen_uid_number } {
1594            send "uid \$uid\n"
1595            expect "GET_LINE keyedit.prompt"
1596          }
1597          set uid [incr uid]
1598        }
1599        # delete selected UIDs
1600        send "deluid\n"
1601        # confirm deletion
1602        expect "GET_BOOL keyedit.remove.uid.okay" {send "yes\n"}
1603        # save and exit
1604        expect "GET_LINE keyedit.prompt" {send "save\n"}
1605        expect "GOT_IT"
1606      }
1607
1608      # delay until the process above terminates
1609      wait
1610      # send following message to user
1611      # send_user "use: \"$1\" chosen for message delivery. [ expr $uid_count - 1 ] UID(s) deleted from public key ${keyid:32}.\n"
1612      exit
1613EOF
1614  fi
1615
1616  if [[ "$return_code" == "0" || "$?" == "0" ]]; then
1617    AdminLog "use: $1 chosen for message delivery. $(($uid_count - 1)) UID(s) deleted from public key ${keyid:32}."
1618  else
1619    return_code=1
1620  fi
1621
1622  FixListOwnership
1623  return $return_code
1624}
1625
1626
1627function CheckPermission {
1628  #-------------------------------------------------------------
1629  # check if file has correct permissions (600) and also
1630  #+if the file is owned by $FIRMA_USER
1631  #+got the idea for this function from backupninja
1632  #
1633  # parameter(s): file name
1634  # depends on function(s): none
1635  # returns: 0 if file has correct permissions
1636  #          1 if not, and also print a warning message
1637  #-------------------------------------------------------------
1638
1639  local file="$1"
1640  local perms="$(ls -ld "$file")"
1641
1642  perms="${perms:4:6}"
1643  if [[ "$perms" != "------" ]]; then
1644    LogMessage "WARNING: Configuration files must not be group or world writable/readable! Wrong permission for file $file"
1645    return 1
1646  fi
1647
1648  if [[ $(ls -ld $file | cut -d " " -f 3) != "$FIRMA_USER" ]]; then
1649    LogMessage "WARNING: Configuration files must be owned by $FIRMA_USER! Wrong ownership for file $file"
1650    return 1
1651  fi
1652
1653  return 0
1654}
1655
1656
1657function CheckListPermissions {
1658  #-------------------------------------------------------------
1659  # check if list files has correct permissions (600) and also
1660  #+if the files are owned by $FIRMA_USER
1661  #
1662  # parameter(s): list config file
1663  # depends on function(s): CheckPermission
1664  # returns: 0 if file has correct permissions
1665  #          1 if not, and also print a warning message
1666  #-------------------------------------------------------------
1667
1668  local file
1669  local folder
1670  local config
1671
1672  # check and fix permissions on all files from $LIST_PATH to $FIRMA_USER:$FIRMA_GROUP
1673  if [[ -n "$1" ]]; then
1674    folder="$(dirname $1)"
1675    config="$(basename $1)"
1676    for file in $config pubring.gpg pubring.gpg~ random_seed secring.gpg trustdb.gpg; do
1677      if ! CheckPermission $folder/$file; then
1678        LogMessage "Fixing permission and ownership for $folder/$file"
1679        chmod 600 $folder/$file 2> /dev/null
1680        chown $FIRMA_USER:$FIRMA_GROUP $folder/$file 2> /dev/null
1681      fi
1682    done
1683  fi
1684}
1685
1686
1687function CheckValidEmail {
1688  #-------------------------------------------------------------
1689  # check if argument is a valid email address
1690  #
1691  # parameter(s): string
1692  # depends on function(s): none
1693  # returns: 0 if string represents a valid email address
1694  #          1 if not
1695  #-------------------------------------------------------------
1696
1697  local local_part='[[:alnum:]][[:alnum:]._+-]*[[:alnum:]]'
1698  local domain='[[:alnum:]][[:alnum:].-]*[[:alnum:]]'
1699  local tld='[[:alpha:]]\{2,6\}'
1700
1701  if ! echo "$1" | grep -q "^${local_part}@${domain}\.${tld}$"; then
1702    return 1
1703  else
1704    return 0
1705  fi
1706}
1707
1708
1709function UnsubscribeUser {
1710  #-------------------------------------------------------------
1711  # unsubscribe a user and remove its pubkey from
1712  #+the list keyring
1713  #
1714  # parameter(s): chosen email address
1715  # depends on function(s): DeclareGpgVars
1716  # returns: 0 on success,
1717  #          1 if task can't be executed (public key not found, etc.)
1718  #-------------------------------------------------------------
1719
1720  local key
1721  local -i return_code=0
1722  local keyid="$($GPG_LIST_KEYS --with-fingerprint $1 2> /dev/null | grep ^fpr | cut -d : -f 10)"
1723
1724  # check if its a valid email
1725  if ! CheckValidEmail $1; then
1726    AdminLog "unsub: \"$1\" is not an email address."
1727    return_code=1
1728  # check if user is trying to unsubscribe the list key
1729  elif [[ "$1" == "$LIST_ADDRESS" ]]; then
1730    AdminLog "unsub: can't delete the list pubkey."
1731    return_code=1
1732  # check if supplied address is associated with a public key
1733  elif [[ -z "$($GPG_LIST_KEYS --fixed-list-mode "<$1>" 2> /dev/null | grep -v '^tru:')" ]]; then
1734    AdminLog "unsub: \"$1\" is not associated with any public key on this keyring."
1735    return_code=1
1736  else
1737    for key in $keyid; do
1738      $GPG --batch --delete-key --yes $key
1739      if [[ "$?" == "0" ]]; then
1740        AdminLog "deleted key id $key for $1"
1741        # now just update the trust db
1742        $GPG_LIST_KEYS &> /dev/null
1743      else
1744        AdminLog "unsub: error deleting key id $key for $1"
1745        return_code=1
1746      fi
1747    done
1748  fi
1749
1750  FixListOwnership
1751  return $return_code
1752}
1753
1754
1755function LogMessage {
1756  #-------------------------------------------------------------
1757  # write a log message to STDOUT or to syslog
1758  #
1759  # parameter(s): string
1760  # depends on function(s): none
1761  # returns: 0
1762  #-------------------------------------------------------------
1763
1764  local error_message="$*"
1765  local line
1766
1767  if [[ "$LOG_TO_SYSLOG" == "1" ]]; then
1768    echo "$error_message" | $LOGGER_BINARY -p "$SYSLOG_PRIORITY" -t "$BASENAME"
1769  else
1770
1771    echo "$error_message" | while read line; do
1772      echo >&2 "$BASENAME: $line"
1773    done
1774
1775  fi
1776
1777  return 0
1778}
1779
1780
1781function SubscribeUsers {
1782  #-------------------------------------------------------------
1783  # subscribe users to the list importing their pubkeys
1784  #
1785  # parameter(s): $1: help, stdin, keyserver or file
1786  #               $2: where to fetch the pubkeys
1787  #               $3: keyid (keyserver only)
1788  # depends on function(s): none
1789  # returns: 0 on success
1790  #          1 on failure
1791  #-------------------------------------------------------------
1792
1793  local -i return_code=0
1794  local keyserver
1795  local method
1796
1797  if [[ "$1" == "help" ]]; then
1798    AdminLog "
1799  help                                 show this help
1800  stdin                                waits for key material from stdin (interactive mode only)
1801  file <file-name>                     import pubkeys from file (interactive mode only)
1802  keyserver [server-address] <key-ids> import <key-ids> from <server-address>
1803                                       (default keyserver: $KEYSERVER)
1804"
1805  elif [[ "$1" == "stdin" ]]; then
1806    if [[ "$MODE" == "admin-interactive" ]]; then
1807      echo "Please enter the key material here, finishing with Ctrl-D sequence..."
1808      $GPG_NOBATCH --import
1809      return_code=$?
1810      if [[ "$return_code" == "0" ]]; then
1811        AdminLog "subscription: success"
1812      fi
1813    else
1814      AdminLog "subscribe: stdin option only valid in the interactive (command-line) mode"
1815      return_code=1
1816    fi
1817  elif [[ "$1" == "file" ]]; then
1818    if [[ -n "$2" ]]; then
1819      if [[ -f "$2" ]]; then
1820        $GPG --import < $2
1821        return_code=$?
1822        if [[ "$return_code" == "0" ]]; then
1823          AdminLog "subscription: success"
1824        fi
1825      else
1826        echo >&2 "subscribe: cant add subscribers from $1: no such file or directory"
1827        return_code=1
1828      fi
1829    else
1830      echo >&2 "subscribe: missing parameters: subscribe file requires a file name"
1831      return_code=1
1832    fi
1833  elif [[ "$1" == "keyserver" ]]; then
1834    if [[ -n "$2" ]]; then
1835      if [[ -z "$3" ]]; then
1836        keyserver="$KEYSERVER"
1837      else
1838        if ! CheckValidEmail $2; then
1839          keyserver="$2"
1840          shift
1841        else
1842          keyserver="$KEYSERVER"
1843        fi
1844      fi
1845      if CheckValidEmail $2; then
1846        method="--search-keys"
1847      else
1848        method="--recv-keys"
1849      fi
1850      if [[ "$return_code" == "0" ]]; then
1851        shift
1852        $GPG_NOBATCH --keyserver $keyserver $method $*
1853        return_code=$?
1854        if [[ "$return_code" == "0" ]]; then
1855          AdminLog "subscription: success"
1856        fi
1857      fi
1858    else
1859      AdminLog "subscribe: missing parameters: type subscribe help"
1860      return_code=1
1861    fi
1862  else
1863    AdminLog "subscribe: wrong option: type subscribe help"
1864    return_code=1
1865  fi
1866
1867  FixListOwnership
1868  return $return_code
1869}
1870
1871
1872function SendListPubkey {
1873  #-------------------------------------------------------------
1874  # send list pubkey to a given subscriber list
1875  #
1876  # parameter(s): subscribers' emails
1877  # depends on function(s): GetMessage, GetSenderAddress, AssembleMessage
1878  # returns: 0 on success
1879  #          1 on failure
1880  #-------------------------------------------------------------
1881
1882  local key
1883  local keys
1884  local keyid
1885  local keyboundary
1886
1887  if [[ "$1" == "help" ]]; then
1888    AdminLog "usage: sendkey [all|email|help]"
1889    AdminLog "supported arguments: all (for all subscribers) or a space-separated subscriber emails."
1890    return 0
1891  elif [[ "$1" == "all" ]]; then
1892    GetSubscribersList
1893    keys="$SUBSCRIBERS_LIST"
1894  else
1895    keys="$*"
1896  fi
1897
1898  for key in $keys; do
1899    keyid="$($GPG_LIST_KEYS --with-fingerprint $1 2> /dev/null | grep ^fpr | cut -d : -f 10)"
1900
1901    if [[ -z "$key" ]]; then
1902      AdminLog "sendkey: missing argument: subscriber email address."
1903      return 1
1904    elif ! CheckValidEmail $key; then
1905      AdminLog "sendkey: \"$key\" is not an email address."
1906      return 1
1907    elif [[ -z "$($GPG_LIST_KEYS --fixed-list-mode "<$key>" 2> /dev/null | grep -v '^tru:')" ]]; then
1908      # check if supplied address is associated with a public key
1909      AdminLog "sendkey: \"$key\" is not associated with any public key on this keyring."
1910      return 1
1911    fi
1912
1913    RECIPIENTS="$key"
1914    SUBJECT="mailing list public key"
1915    keyboundary="$(RandomString 15)"
1916
1917    # this is the body of the message to be sent, so no indentation here
1918    MESSAGE_BODY="$($GPG --armor --export $LIST_ADDRESS)"
1919    MESSAGE_BODY="\
1920Content-Type: multipart/mixed; boundary=\"$keyboundary\"
1921Content-Disposition: inline
1922
1923--$keyboundary
1924Content-Type: application/pgp-keys
1925Content-Disposition: attachment; filename=${LIST_NAME}_pubkey.asc
1926Content-Description: PGP Key for $LIST_ADDRESS
1927
1928$MESSAGE_BODY
1929
1930--${keyboundary}--"
1931
1932    # compose the message
1933    MimeWrapMessage
1934
1935    # send message
1936    echo "$MESSAGE" | $MAIL_AGENT $MAIL_AGENT_ARGS $RECIPIENTS
1937  done
1938
1939  AdminLog "List pubkey sent to $keys."
1940  FixListOwnership
1941
1942}
1943
1944
1945function GetSubscribersInfo {
1946  #-------------------------------------------------------------
1947  # get info on a subscriber pubkey
1948  #
1949  # parameter(s): subscribers' emails
1950  # depends on function(s): none
1951  # returns: 0 on success
1952  #          1 on failure
1953  #-------------------------------------------------------------
1954
1955  local key
1956  local keys
1957  local keyid
1958  local output
1959
1960  if [[ "$1" == "help" ]]; then
1961    AdminLog "usage: info [all|emails|help]"
1962    AdminLog "supported arguments: all (for all subscribers) or a space-separated subscribers' emails."
1963    return 0
1964  elif [[ "$1" == "all" ]]; then
1965    GetSubscribersList
1966    keys="$SUBSCRIBERS_LIST"
1967  else
1968    keys="$*"
1969  fi
1970
1971  for key in $keys; do
1972    keyid="$($GPG_LIST_KEYS --with-fingerprint $1 2> /dev/null | grep ^fpr | cut -d : -f 10)"
1973    if [[ -n "$keyid" ]]; then
1974      output="$($GPG --fingeprint $key)"
1975      AdminLog "$output"
1976    fi
1977  done
1978
1979  FixListOwnership
1980  return $?
1981}
1982
1983
1984function FixListOwnership {
1985  #-------------------------------------------------------------
1986  # fix list ownership
1987  #
1988  # parameter(s): none
1989  # depends on function(s): none
1990  # returns: 0 on success
1991  #          1 on failure
1992  #-------------------------------------------------------------
1993
1994  if [[ -d "$LIST_PATH" ]]; then
1995    chown -R $FIRMA_USER:$FIRMA_GROUP $LIST_PATH 2> /dev/null
1996  fi
1997  return $?
1998}
1999
2000
2001function RandomString {
2002  #-------------------------------------------------------------
2003  # print a random string
2004  #+got it from http://funcoeszz.net/
2005  #
2006  # parameter(s): string size (max 62)
2007  # depends on function(s): none
2008  # returns: 0
2009  #          1 if string size is greater than 62
2010  #-------------------------------------------------------------
2011
2012  local n alpha="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.,;:?!"
2013
2014  n="$(( 10#$(echo "$1" | tr -dc '[:digit:]') ))"
2015  if [[ "$n" == "0" ]]; then
2016    n="6"
2017  fi
2018
2019  if [[ "$n" -gt "62" ]]; then
2020    return 1
2021  fi
2022
2023  while [[ "$n" != "0" ]]; do n="$((n-1))" ; pos="$((RANDOM%${#alpha}+1))"
2024    echo -n "$alpha" | sed "s/\(.\)\{$pos\}.*/\1/"
2025    alpha="$(echo $alpha | sed "s/.//$pos")"
2026  done | tr -d '\012' ; echo
2027
2028  return 0
2029}
2030
2031
2032function AdminLog {
2033  #-------------------------------------------------------------
2034  # check whether admin is made via command line
2035  #+or email and then log a message according to the
2036  #+display mode
2037  #
2038  # parameter(s): string
2039  # depends on function(s): none
2040  # returns: 0
2041  #-------------------------------------------------------------
2042
2043  if [[ "$MODE" == "admin-interactive" ]]; then
2044    echo >&2 "$*"
2045  else
2046    echo "$*"
2047  fi
2048}
2049
2050
2051function EmailListAdministration {
2052  #-------------------------------------------------------------
2053  # parse and execute admin tasks via email
2054  #
2055  # parameter(s): none
2056  # depends on function(s): ProcessMessage should be called first
2057  # returns: 0 on success  :)
2058  #          1 on failure  :/
2059  #-------------------------------------------------------------
2060
2061  local sender found
2062  local command
2063
2064  found="0"
2065  for sender in $LIST_ADMIN; do
2066    if [[ "$sender" == "$SENDER_ADDRESS" ]]; then
2067      found="1"
2068      break
2069    fi
2070  done
2071  if [[ "$found" == "1" ]]; then
2072    # message was sent by an admin
2073    #+then, parse and process admin tasks
2074    MESSAGE_BODY="$(echo -e "$DECRYPTED_MESSAGE" | while read command; do
2075      if [[ -n "$command" ]] && echo $command | grep -q -v -e "^Content"; then
2076        AdminLog "Command> $command"
2077        ListAdministration $command
2078      fi
2079    done)"
2080    # send a message back to the administrator
2081    RECIPIENTS="$SENDER_ADDRESS"
2082    SUBJECT="admin request results"
2083    CreateMessageBodyPart
2084    MimeWrapMessage
2085    echo "$MESSAGE" | $MAIL_AGENT $MAIL_AGENT_ARGS $SENDER_ADDRESS
2086  else
2087    # message was sent by a normal subscriber
2088    # this is the body of the message to be sent, so no indentation here
2089    MESSAGE_BODY="\
2090 It was not possible to process this message. Message was
2091 not sent by a list administrator."
2092    ComposeAndSendBounceMessage
2093  fi
2094
2095  return $?
2096}
2097
2098
2099function AllowMessageProcessing {
2100  #-------------------------------------------------------------
2101  # check if the message has sufficient rights to be processed
2102  #
2103  # parameter(s): none
2104  # depends on function(s): ParseGpgDecryptStderr
2105  # returns: 0 if message has rights to be processed
2106  #          1 if not
2107  #-------------------------------------------------------------
2108
2109  local -i return_code=0
2110
2111  if [[ "$MODE" == "admin-non-interactive" ]]; then
2112    REQUIRE_SIGNATURE="1"
2113  fi
2114
2115  if [[ "$ENCRYPTED_TO_LIST" == "1" ]]; then
2116    if [[ "$REQUIRE_SIGNATURE" == "1" ]]; then
2117      if [[ "$GOOD_SIGNATURE" == "1" && "$SIGNATURE_MADE_BY_SENDER" == "1" ]]; then
2118        return_code=0
2119      else
2120        return_code=1
2121      fi
2122    else
2123      return_code=0
2124    fi
2125  else
2126    return_code=1
2127  fi
2128
2129  return $return_code
2130}
2131
2132
2133function MimeWrapMessage {
2134  #-------------------------------------------------------------
2135  # MIME wrap message to be sent, adding needed headers and body parts
2136  #
2137  # parameter(s): none
2138  # depends on variable(s): RECIPIENTS, SUBJECT, GPG_MESSAGE
2139  #
2140  # returns: 0
2141  #-------------------------------------------------------------
2142
2143  local boundary
2144
2145  boundary="$(RandomString 15)"
2146
2147  # these are the headers of the message to be sent, so no indentation here
2148  MESSAGE_HEADERS="\
2149From: $LIST_REQUEST_ADDRESS
2150To: ${RECIPIENTS}
2151Reply-To: $LIST_REQUEST_ADDRESS
2152Subject: ${SUBJECT_PREFIX}${SUBJECT}
2153MIME-Version: 1.0
2154Content-Type: multipart/encrypted;
2155  protocol=\"application/pgp-encrypted\";
2156  boundary=\"${boundary}\"
2157Content-Disposition: inline"
2158
2159  GPG_MESSAGE="$(echo -e "${PASSPHRASE}\n${MESSAGE_BODY}" | $GPG_ENCRYPT --recipient $RECIPIENTS)"
2160
2161  # this is the body of the message to be sent, so no indentation here
2162  MESSAGE_BODY="\
2163This is an OpenPGP/MIME encrypted message (RFC 2440 and 3156)
2164--$boundary
2165Content-Type: application/pgp-encrypted
2166Content-Description: PGP/MIME version identification
2167
2168Version: 1
2169
2170--$boundary
2171Content-Type: application/octet-stream; name=\"encrypted.asc\"
2172Content-Disposition: inline; filename=\"encrypted.asc\"
2173
2174${GPG_MESSAGE}
2175
2176--${boundary}--"
2177
2178  # assemble entire message
2179  MESSAGE="\
2180${MESSAGE_HEADERS}
2181
2182${MESSAGE_BODY}"
2183}
2184
2185
2186function CreateMessageBodyPart {
2187  #-------------------------------------------------------------
2188  # create a message body part
2189  #
2190  # parameter(s): none
2191  # depends on variable(s): MESSAGE_BODY
2192  #
2193  # returns: 0
2194  #-------------------------------------------------------------
2195
2196  # this is the body of the message to be sent, so no indentation here
2197  MESSAGE_BODY="\
2198Content-Type: text/plain; charset=iso-8859-1
2199Content-Disposition: inline
2200
2201$MESSAGE_BODY"
2202}
2203
2204
2205function EvalConfigParameter {
2206  #-------------------------------------------------------------
2207  # eval parameters from a config file
2208  #
2209  # parameter(s): <config-file> <parameter>
2210  # depends on function(s): none
2211  # returns: 0 on success
2212  #          1 if config file not found or missing parameter
2213  #-------------------------------------------------------------
2214
2215  if [[ "$#" != "2" ]]; then
2216    echo "WARNING: missing parameters on EvalConfigParameters."
2217    return 1
2218  elif [[ ! -f "$1" ]]; then
2219    echo "WARNING: file not found: $1"
2220    return 1
2221  fi
2222
2223  echo "$(grep "^$2=" $1 | sed -e "s/^$2='//" -e "s/'$//" | tail -n 1)"
2224
2225}
2226
2227
2228function SourceFirmaConfig {
2229  #-------------------------------------------------------------
2230  # load firma.conf and set up global variables
2231  #
2232  # parameter(s): none for evaluation, help to show all config parameters
2233  # depends on function(s): none
2234  # returns: 0
2235  #-------------------------------------------------------------
2236
2237  [[ "$1" == "help" ]] && echo -e "\nMandatory global firma config parameters\n"
2238
2239  [[ "$1" == "help" ]] && echo -e "\tGPG_BINARY= path to the GnuPG binary." || \
2240  GPG_BINARY="$(EvalConfigParameter $FIRMA_CONFIG_FILE GPG_BINARY)"
2241
2242  [[ "$1" == "help" ]] && echo -e "\tMAIL_AGENT= path to the mail transport agent to be used (e.g., sendmail)." || \
2243  MAIL_AGENT="$(EvalConfigParameter $FIRMA_CONFIG_FILE MAIL_AGENT)"
2244
2245  [[ "$1" == "help" ]] && echo -e "\tMAIL_AGENT_ARGS= command-line arguments to be passed to the command above." || \
2246  MAIL_AGENT_ARGS="$(EvalConfigParameter $FIRMA_CONFIG_FILE MAIL_AGENT_ARGS)"
2247
2248  [[ "$1" == "help" ]] && echo -e "\tLISTS_DIR= path to the mailing lists directory." || \
2249  LISTS_DIR="$(EvalConfigParameter $FIRMA_CONFIG_FILE LISTS_DIR)"
2250
2251  [[ "$1" == "help" ]] && echo -e "\nOptional global firma config parameters\n"
2252
2253  [[ "$1" == "help" ]] && echo -e "\tFIRMA_USER= user that runs firma (usually the same as your MTA user);
2254\t      defaults to \"nobody\"; you can also specify this parameter
2255\t      in each mailing list config file if you plan to have one
2256\t      user per mailing list." || \
2257  FIRMA_USER="$(EvalConfigParameter $FIRMA_CONFIG_FILE FIRMA_USER)"
2258
2259  [[ "$1" == "help" ]] && echo -e "\tFIRMA_GROUP= group that runs firma (usually the same as your MTA group);
2260\t       defaults to \"nobody\"; you can also specify this parameter
2261\t       in each mailing list config file if you plan to have one
2262\t       group per mailing list." || \
2263  FIRMA_GROUP="$(EvalConfigParameter $FIRMA_CONFIG_FILE FIRMA_GROUP)"
2264
2265  [[ "$1" == "help" ]] && echo -e "\tLOG_TO_SYSLOG= set to "1" to log errors and warnings to syslog, else firma
2266\t               will print errors to STDERR." || \
2267  LOG_TO_SYSLOG="$(EvalConfigParameter $FIRMA_CONFIG_FILE LOG_TO_SYSLOG)"
2268
2269  [[ "$1" == "help" ]] && echo -e "\tLOGGER_BINARY= if logging to syslog, set the path to logger's binary." || \
2270  LOGGER_BINARY="$(EvalConfigParameter $FIRMA_CONFIG_FILE LOGGER_BINARY)"
2271
2272  [[ "$1" == "help" ]] && echo -e "\tSYSLOG_PRIORITY= if logging to syslog, set a priority for the error messages
2273\t                 (defaults to "user.err")." || \
2274  SYSLOG_PRIORITY="$(EvalConfigParameter $FIRMA_CONFIG_FILE SYSLOG_PRIORITY)"
2275
2276  [[ "$1" == "help" ]] && echo -e "\tUSE_GPG_HIDDEN_RECIPIENT_OPTION= set to '1' to use GnuPG's --hidden-recipient
2277\t                                 option, available from version 1.4.0 onwards
2278\t                                 (try 'man gpg' for more information)." || \
2279  USE_GPG_HIDDEN_RECIPIENT_OPTION="$(EvalConfigParameter $FIRMA_CONFIG_FILE USE_GPG_HIDDEN_RECIPIENT_OPTION)"
2280
2281  [[ "$1" == "help" ]] && echo -e "\tREMOVE_THESE_HEADERS_ON_ALL_LISTS= headers that should be stripped from list
2282\t                                   messages on all lists running under firma
2283\t                                   (space separated case-insensitive entries)
2284\t                                   (may include regexps (e.g., X-.*)." || \
2285  REMOVE_THESE_HEADERS_ON_ALL_LISTS="$(EvalConfigParameter $FIRMA_CONFIG_FILE REMOVE_THESE_HEADERS_ON_ALL_LISTS)"
2286
2287  [[ "$1" == "help" ]] && echo -e "\tKEYSERVER= default keyserver to import/export keys
2288\t           (defaults to keys.indymedia.org)." || \
2289  KEYSERVER="$(EvalConfigParameter $FIRMA_CONFIG_FILE KEYSERVER)"
2290}
2291
2292
2293function SourceListConfig {
2294  #-------------------------------------------------------------
2295  # load list.conf and set up global variables
2296  #
2297  # parameter(s): none for evaluation, help to show all config parameters
2298  # depends on function(s): none
2299  # returns: 0
2300  #-------------------------------------------------------------
2301
2302  local firma_user firma_group keyserver
2303
2304  [[ "$1" == "help" ]] && echo -e "\nMandatory list config parameters\n"
2305
2306  [[ "$1" == "help" ]] && echo -e "\tLIST_ADDRESS= list's email address." || \
2307  LIST_ADDRESS="$(EvalConfigParameter $LIST_CONFIG_FILE LIST_ADDRESS)"
2308
2309  [[ "$1" == "help" ]] && echo -e "\tLIST_REQUEST_ADDRESS= list's email address for administrative
2310\t                      requests (defaults to listname-request@domain." || \
2311  LIST_REQUEST_ADDRESS="$(EvalConfigParameter $LIST_CONFIG_FILE LIST_REQUEST_ADDRESS)"
2312
2313  [[ "$1" == "help" ]] && echo -e "\tLIST_ADMIN= list's administrators email addresses (space separated)." || \
2314  LIST_ADMIN="$(EvalConfigParameter $LIST_CONFIG_FILE LIST_ADMIN)"
2315
2316  [[ "$1" == "help" ]] && echo -e "\tLIST_HOMEDIR= list's GnuPG homedir, where the list's keyrings are located." || \
2317  LIST_HOMEDIR="$(EvalConfigParameter $LIST_CONFIG_FILE LIST_HOMEDIR)"
2318
2319  [[ "$1" == "help" ]] && echo -e "\tFIRMA_USER= user that runs firma (usually the same as your MTA user);
2320\t      defaults to \"nobody\"; you can also specify this parameter
2321\t      in each mailing list config file if you plan to have one
2322\t      user per mailing list." || \
2323  firma_user="$(EvalConfigParameter $LIST_CONFIG_FILE FIRMA_USER)"
2324  [[ -n "$firma_user" ]] && FIRMA_USER="$firma_user"
2325
2326  [[ "$1" == "help" ]] && echo -e "\tFIRMA_GROUP= group that runs firma (usually the same as your MTA group);
2327\t       defaults to \"nobody\"; you can also specify this parameter
2328\t       in each mailing list config file if you plan to have one
2329\t       group per mailing list." || \
2330  firma_group="$(EvalConfigParameter $LIST_CONFIG_FILE FIRMA_GROUP)"
2331  [[ -n "$firma_group" ]] && FIRMA_GROUP="$firma_group"
2332
2333  [[ "$1" == "help" ]] && echo -e "\tPASSPHRASE= passphrase for the list's private keyring\n
2334\tNOTE: The passphrase _has_ to be enclosed in single quotes and _cannot_
2335\tcontain any additional single quote as part of itself. It has to be at least
2336\t25 characters long, combining numbers, upper and lower case letters and at
2337\tleast 1 special characters. Also, no character can be sequentially repeated
2338\tmore than 4 times." || \
2339  PASSPHRASE="$(EvalConfigParameter $LIST_CONFIG_FILE PASSPHRASE)"
2340
2341  [[ "$1" == "help" ]] && echo -e "\nOptional list config parameters\n"
2342
2343  [[ "$1" == "help" ]] && echo -e "\tSUBJECT_PREFIX= prefix to be included in the subject of list messages." || \
2344  SUBJECT_PREFIX="$(EvalConfigParameter $LIST_CONFIG_FILE SUBJECT_PREFIX)"
2345
2346  [[ "$1" == "help" ]] && echo -e "\tREMOVE_THESE_HEADERS= headers that should be stripped from list messages
2347\t                      (space separated case-insensitive entries)
2348\t                      (may include regexps (e.g., X-.*)." || \
2349  REMOVE_THESE_HEADERS="$(EvalConfigParameter $LIST_CONFIG_FILE REMOVE_THESE_HEADERS)"
2350
2351  [[ "$1" == "help" ]] && echo -e "\tREPLIES_SHOULD_GO_TO_LIST= set to '1' to add a Reply-To header containing the list address." || \
2352  REPLIES_SHOULD_GO_TO_LIST="$(EvalConfigParameter $LIST_CONFIG_FILE REPLIES_SHOULD_GO_TO_LIST)"
2353
2354  [[ "$1" == "help" ]] && echo -e "\tHIDE_SENDER= set to '1' to add replace the sender address with list address on list messages." || \
2355  HIDE_SENDER="$(EvalConfigParameter $LIST_CONFIG_FILE HIDE_SENDER)"
2356
2357  [[ "$1" == "help" ]] && echo -e "\tSILENTLY_DISCARD_INVALID_MESSAGES= set to '1' to silently discard invalid
2358\t                                   messages (message not signed/encrypted,
2359\t                                   sender not subscribed to the list, etc.)
2360\t                                   instead of sending bounces back to sender." || \
2361  SILENTLY_DISCARD_INVALID_MESSAGES="$(EvalConfigParameter $LIST_CONFIG_FILE SILENTLY_DISCARD_INVALID_MESSAGES)"
2362
2363  [[ "$1" == "help" ]] && echo -e "\tKEYSERVER= default keyserver to import/export keys
2364\t           (defaults to keys.indymedia.org)." || \
2365  keyserver="$(EvalConfigParameter $LIST_CONFIG_FILE KEYSERVER)"
2366  [[ -n "$keyserver" ]] && KEYSERVER="$keyserver"
2367
2368  [[ "$1" == "help" ]] && echo -e "\tREQUIRE_SIGNATURE= whether messages sent to the list should be (1) or dont
2369\t                   need to be (0) signed to be processed; defaults to '1';
2370\t                   this doesnt affect the way email administration works,
2371\t                   when signature is mandatory." || \
2372  REQUIRE_SIGNATURE="$(EvalConfigParameter $LIST_CONFIG_FILE REQUIRE_SIGNATURE)"
2373
2374  [[ "$1" == "help" ]] && echo -e "\tDELIVERY_RANDOMIZATION= if non-zero, set a random delay between 0 and N seconds
2375\t                        between each messsage delivery; if you run firma with
2376\t                        a TLS-enabled MTA and mostly of the list messages are
2377\t                        sent to others TLS-enabled MTAs, then this option will
2378\t                        make harder to a sniffer detect the traffic of you mailing
2379\t                        list, specially if your MTA already sends a lot of messages
2380\t                        or if you're going to have a lot of encrypted mailing lists,
2381\t                        all randomizing its delivery." || \
2382  DELIVERY_RANDOMIZATION="$(EvalConfigParameter $LIST_CONFIG_FILE DELIVERY_RANDOMIZATION)"
2383
2384  [[ "$1" == "help" ]] && echo -e "\tREPLAY_PROTECTION= when set to '1', stores sha1sums
2385\t                   of the last REPLAY_COUNT received messages; then,
2386\t                   if some message with an already stored sha1sum, then
2387\t                   its bounced back to the sender and considered as an attempt
2388\t                   of replay attack." || \
2389  REPLAY_PROTECTION="$(EvalConfigParameter $LIST_CONFIG_FILE REPLAY_PROTECTION)"
2390
2391  [[ "$1" == "help" ]] && echo -e "\tREPLAY_COUNT= number of messages to store sha1sums;
2392\t              defaults to 150 and only used when
2393\t              REPLAY_PROTECTION is set to '1'." || \
2394  REPLAY_COUNT="$(EvalConfigParameter $LIST_CONFIG_FILE REPLAY_COUNT)"
2395
2396  [[ "$1" == "help" ]] && echo -e "\tREPLAY_FILE= file to store sha1sums of messages;
2397\t             only used when REPLAY_PROTECTION is set to '1';
2398\t             defaults to \"/var/log/firma/replay.db\"." || \
2399  REPLAY_FILE="$(EvalConfigParameter $LIST_CONFIG_FILE REPLAY_FILE)"
2400}
2401
2402
2403function ConfigHelp {
2404  #-------------------------------------------------------------
2405  # display help on configuration file parameters
2406  #
2407  # parameter(s): none
2408  # depends on function(s): SourceFirmaConfig, SourceListConfig
2409  # returns: 0
2410  #-------------------------------------------------------------
2411
2412  echo "All firma parameters are passed through two different configuration files:"
2413  echo "firma.conf, containing general parameters needed to run the script, and a list"
2414  echo "specific file, containing its address, administrator(s), etc. In both files"
2415  echo "you should enter PARAMETER='value' (with value between single quotes, without"
2416  echo "spaces before or after the equal sign and nothing after the second quote)."
2417
2418  SourceFirmaConfig help
2419  SourceListConfig help
2420}
2421
2422
2423function DeliveryRandomization {
2424  #-------------------------------------------------------------
2425  # sleep according $DELIVERY_RANDOMIZATION
2426  #
2427  # parameter(s): none
2428  # depends on function(s): none
2429  # returns: 0
2430  #-------------------------------------------------------------
2431
2432  local n
2433
2434  if [[ "$DELIVERY_RANDOMIZATION" != "0" ]]; then
2435    n="$RANDOM"
2436    let "n %= $DELIVERY_RANDOMIZATION"
2437    sleep $n
2438  fi
2439}
2440
2441
2442function ReplayProtectionFlush {
2443  #-------------------------------------------------------------
2444  # flushes the replay database file
2445  #
2446  # parameter(s): none
2447  # depends on function(s): none
2448  # returns: 0
2449  #-------------------------------------------------------------
2450
2451  if [[ "$REPLAY_PROTECTION" == "1" ]]; then
2452    if [[ -f "$REPLAY_FILE" ]]; then
2453      if [[ "$(wc -l $REPLAY_FILE | cut -d " " -f 1)" -gt "$REPLAY_COUNT" ]]; then
2454        sed -i -e '1d' $REPLAY_FILE
2455      fi
2456    else
2457      touch $REPLAY_FILE 2> /dev/null
2458      chown $FIRMA_USER:$FIRMA_GROUP $REPLAY_FILE 2> /dev/null
2459      chmod 600 $REPLAY_FILE 2> /dev/null
2460    fi
2461  fi
2462
2463  return 0
2464}
2465
2466
2467function ReplayProtectionCheck {
2468  #-------------------------------------------------------------
2469  # check if message was already received and stores it
2470  #+in the database
2471  #
2472  # parameter(s): GetGpgMessage, ReplayProtectionFlush
2473  # depends on function(s): none
2474  # returns: 0 if message's sha1sum is not in replay database
2475  #          1 if message's sha1sum is in the database
2476  #-------------------------------------------------------------
2477
2478  local -i return_code=0
2479  local sha1
2480
2481  if [[ "$REPLAY_PROTECTION" == "1" && -n "$ORIG_GPG_MESSAGE" ]]; then
2482    ReplayProtectionFlush
2483    sha1="$(echo "$ORIG_GPG_MESSAGE" | sha1sum | cut -d " " -f 1)"
2484    if grep -q "^$sha1$" $REPLAY_FILE; then
2485      sed -i -e "/^$sha1$/d" $REPLAY_FILE
2486      return_code=1
2487    fi
2488    echo "$sha1" >> $REPLAY_FILE
2489  fi
2490
2491  return $return_code
2492}
2493
2494#-------------------------------------------------------------
2495# main()
2496#-------------------------------------------------------------
2497
2498# hardcode path to firma.conf, firma version and program name
2499declare -r \
2500  FIRMA_CONFIG_FILE="/var/lib/firma/firma.conf" \
2501  VERSION="0.4-git" \
2502  BASENAME="$(basename $0)"
2503
2504# set environmental variables and options
2505export LANG=en_US
2506umask 0077
2507
2508# set initial exit code
2509EXIT_CODE=0
2510
2511# define the number of command line arguments, the option
2512#+being run and its argument, if any
2513declare -r \
2514  NUM_OF_ARGS="$#" \
2515  OPTION="$1" \
2516  ARG="$2"
2517
2518# check command line arguments for errors
2519#+(missing arguments, invalid option, etc)
2520case "$NUM_OF_ARGS" in
2521  0)
2522
2523    echo >&2 "$BASENAME: missing arguments"
2524    Usage
2525    EXIT_CODE=1
2526    ;;
2527
2528  1)
2529    case "$OPTION" in
2530
2531      # valid short option
2532      -h|-v)
2533        ;;
2534
2535      # valid long option
2536      --help|--version)
2537        ;;
2538
2539      # valid short option called without its required argument
2540      -a|-c|-e|-p)
2541        echo >&2 "$BASENAME: missing arguments"
2542        Usage
2543        EXIT_CODE=1
2544        ;;
2545
2546      # valid long option called without its required argument
2547      --admin-task|--create-newlist|--email-admin-task|--process-message)
2548        echo >&2 "$BASENAME: missing arguments"
2549        Usage
2550        EXIT_CODE=1
2551        ;;
2552
2553      # invalid option
2554      *)
2555        echo >&2 "$BASENAME: invalid option -- $OPTION"
2556        Usage
2557        EXIT_CODE=1
2558        ;;
2559
2560    esac
2561    ;;
2562  2)
2563    case "$OPTION" in
2564
2565      # valid short option
2566      -a|-c|-e|-p|-h)
2567        ;;
2568
2569      # valid long option
2570      --admin-task|--create-newlist|--email-admin-task|--process-message|--help)
2571        ;;
2572
2573      # valid short option called with too many arguments
2574      -v)
2575        echo >&2 "$BASENAME: too many arguments -- $@"
2576        Usage
2577        EXIT_CODE=1
2578        ;;
2579
2580      # valid long option called with too many arguments
2581      --version)
2582        echo >&2 "$BASENAME: too many arguments -- $@"
2583        Usage
2584        EXIT_CODE=1
2585        ;;
2586
2587      # invalid option
2588      *)
2589        echo >&2 "$BASENAME: invalid option -- $OPTION"
2590        Usage
2591        EXIT_CODE=1
2592        ;;
2593
2594    esac
2595    ;;
2596  *)
2597    case "$OPTION" in
2598
2599      # valid short option called with too many arguments
2600      -h|-v|-a|-c|-e|-p)
2601        echo >&2 "$BASENAME: too many arguments -- $@"
2602        Usage
2603        EXIT_CODE=1
2604        ;;
2605
2606      # valid long option called with too many arguments
2607      --help|--version|--admin-task|--create-newlist|--email-admin-task|--process-message)
2608        echo >&2 "$BASENAME: too many arguments -- $@"
2609        Usage
2610        EXIT_CODE=1
2611        ;;
2612
2613      # invalid option
2614      *)
2615        echo >&2 "$BASENAME: invalid option -- $OPTION"
2616        Usage
2617        EXIT_CODE=1
2618        ;;
2619
2620    esac
2621    ;;
2622esac
2623
2624# parse valid command line arguments
2625if [[ "$EXIT_CODE" == "0" ]]; then
2626
2627  case "$NUM_OF_ARGS" in
2628
2629    1)
2630      case "$OPTION" in
2631
2632        -h|--help)
2633          Usage
2634          EXIT_CODE=0
2635          ;;
2636
2637        -v|--version)
2638          Version
2639          EXIT_CODE=0
2640          ;;
2641
2642      esac
2643      ;;
2644    2)
2645      # help options
2646      if [ "$OPTION" == "--help" ] || [ "$OPTION" == "-h" ]; then
2647
2648        if [ "$ARG" == "config" ]; then
2649
2650          ConfigHelp
2651          EXIT_CODE=0
2652
2653        else
2654
2655          echo >&2 "$BASENAME: invalid help section -- $ARG"
2656          Usage
2657          EXIT_CODE=1
2658
2659        fi
2660
2661      # if firma.conf exists and can be read
2662      elif [[ ! -r "$FIRMA_CONFIG_FILE" ]]; then
2663
2664        LogMessage "FATAL: Cannot source \`$FIRMA_CONFIG_FILE': No such file or directory"
2665        EXIT_CODE=1
2666
2667      # then source and evaluate its parameters
2668      elif ! { SourceFirmaConfig && \
2669               CheckFirmaConfigFile && \
2670               CheckPermission "$FIRMA_CONFIG_FILE"
2671             }; then
2672
2673        EXIT_CODE=1
2674
2675      else
2676
2677        LIST_NAME="$ARG"
2678        LIST_PATH="$LISTS_DIR/$LIST_NAME"
2679        LIST_CONFIG_FILE="$LIST_PATH/$LIST_NAME.conf"
2680
2681        case "$OPTION" in
2682
2683          -c|--create-newlist)
2684            NewList
2685            EXIT_CODE=$?
2686            ;;
2687
2688          # options that depend on the list configuration file
2689          -a|--admin-task|-e|--email-admin-task|-p|--process-message)
2690
2691            # if list config file exists and can be read
2692            if [[ ! -r "$LIST_CONFIG_FILE" ]]; then
2693
2694              LogMessage "FATAL: Cannot source \`$LIST_CONFIG_FILE': No such file or directory"
2695              EXIT_CODE=1
2696
2697            # then source and evaluate its parameters
2698            elif ! { SourceListConfig && \
2699                     DeclareGpgVars && \
2700                     CheckListPermissions "$LIST_CONFIG_FILE" && \
2701                     CheckListConfigFile
2702                   }; then
2703
2704              EXIT_CODE=1
2705
2706            else
2707
2708              case "$OPTION" in
2709
2710                -a|--admin-task)
2711
2712                  MODE="admin-interactive"
2713                  # while a "quit" command isn't entered (returns 3), read STDIN
2714                  while (( $EXIT_CODE != 3 )) && read -rep "Command> " STDIN; do
2715                    # if line is not empty or commented, process command
2716                    if [[ -n "$STDIN" && "$STDIN" != "#"* ]]; then
2717                      ListAdministration $STDIN
2718                      EXIT_CODE=$?
2719                    fi
2720                  done
2721
2722                  # since quit was entered, exit without error
2723                  EXIT_CODE=0
2724
2725                  ;;
2726
2727                -p|--process-message)
2728                  MODE="list-message"
2729                  ProcessMessage
2730                  EXIT_CODE=$?
2731                  ;;
2732                -e|--email-admin-task)
2733                  MODE="admin-non-interactive"
2734                  ProcessMessage
2735                  EXIT_CODE=$?
2736                  ;;
2737
2738              esac
2739
2740            fi
2741            ;;
2742
2743        esac
2744
2745      fi
2746      ;;
2747
2748  esac
2749fi
2750
2751# exit
2752exit $EXIT_CODE
2753
Note: See TracBrowser for help on using the repository browser.