msmtp icon indicating copy to clipboard operation
msmtp copied to clipboard

msmtpq: added support of config file for msmtpq script.

Open flyinggreenfrog opened this issue 2 years ago • 7 comments

To be able to use a config file ~/.msmtpqrc for msmtpq.

flyinggreenfrog avatar Feb 18 '23 07:02 flyinggreenfrog

Since I have not yet received feedback about my pull request, this is to friendly ask about it's status. Can you please have a look at it @marlam ?

flyinggreenfrog avatar Mar 04 '23 10:03 flyinggreenfrog

Hi, sorry I did not yet look at it. The msmtpq script is currently without a maintainer and I really have no opinion on whether it makes sense to have a configuration file for it or not. Can someone else comment on this please?

marlam avatar Mar 05 '23 15:03 marlam

Hi. Thanks for your response. Of course configuration of msmtpq script can be done via environment variable, but I prefer to have a configuration file similar to msmtp itself. If and only if the configuration file is found, it will be used, so nothing breaks. At the moment I patch the msmtpq script to enable me using the config file, but of course after every new version of msmtp I have to patch it again. Hence I would prefer, if my pull request would be accepted, and have it upstream. Hopefully it can be considered.

flyinggreenfrog avatar Mar 05 '23 15:03 flyinggreenfrog

fwiw, i'd +1 this idea. the current configuration is via modifying the script itself. very suckless, but hard (or, inconvenient) if people are, e.g, installing from a package manager. also, when upgrading, trying to pull one's changes along can be a pain.

in terms of the code, i worry about sourceing a config file (mainly, because of possible security issues), rather than some (hopefully simple) parsing.

greg-minshall avatar Mar 28 '23 12:03 greg-minshall

OK, can you update this to also support $XDG_CONFIG_HOME/msmtpq/config in addition to (or instead of) ~/.msmtpqrc? To make the freedesktop.org people happy and allow for a cleaner $HOME.

marlam avatar Apr 22 '23 16:04 marlam

hi. since i was unhappy about sourcing a config file, i translated some python code i have been working on to bash. "it slices, it dices, it's oversized". (BUT DON'T USE -- I'M STILL DEVELOPING, NOT EVEN USING IT YET MYSELF!!!!)

config-file.patch
diff --git a/scripts/msmtpq/README.msmtpq b/scripts/msmtpq/README.msmtpq
index ee271d4..1d08ea1 100644
--- a/scripts/msmtpq/README.msmtpq
+++ b/scripts/msmtpq/README.msmtpq
@@ -32,7 +32,7 @@ copy msmtpq and msmtp-queue to whatever location is best for you
 (I use /usr/local/bin) ; the directory chosen should be on the path
 
 replace the msmtp invocation in your mail client with msmtpq ; e.g. for
-mutt : 'set sendmail = /path/to/msmtpq'
+mutt : 'set sendmail = /path/to/msmtpq' (but, see below)
 
 msmtpq will then take care of the management and routing of outgoing
 mail ; normally sending a mail is nearly instantaneous, but a very long
@@ -55,7 +55,17 @@ from msmtpq
 Configuration :
 -------------
 
-all config is done by exporting these environment variables
+the msmtpq script defines defaults for the configuration variables.
+these values can then be overwritten by environment variables, a
+system configuration file, and/or a user-specific configuration file
+(each succeeding one taking precedence over the preceding)
+
+user-specific configuration can be done by a config file, or with
+environment variables
+
+Environment Variables :
+
+config can done by exporting these environment variables
 
   set the MSMTP var to point to the location of the msmtp executable
     (set this only if necessary ; if it's not on the path)
@@ -68,14 +78,38 @@ circumstances, such as embedded systems, etc. ; otherwise, if you are
 running a normal Linux distribution you can leave it as is ; msmtp will
 by default be on the execution path
 
-the MSMTPQ_Q variable should have the location of the queue directory ;
-please note that it's preferable to create the queue directory (with
-0700 permissions) before using these routines ; the Q variable is a
-deprecated alias of MSMTPQ_Q
+the MSMTPQ_Q variable should have the location of the queue directory
+; please note that it's preferable to create the queue directory (with
+0700 permissions) before using these routines ; the Q environment
+variable is a deprecated alias of MSMTPQ_Q
+
+if MSMTPQ_Q is not specified, it is first looked for in its *legacy*
+location, i.e., "~/.msmtp.queue".  if that is found, and is a
+directory, that is used.  otherwise, if the environment variable
+XDG_DATA_HOME exists, "${XDG_DATA_HOME}/msmtp/msmtp.queue"
 
 the MSMTPQ_LOG variable should have the desired name & location of the
-queue log ; set it to an empty string (export MSMTPQ_LOG="") to disable
-logging ; the LOG variable is a deprecated alias of MSMTPQ_LOG
+queue log ; set it to an empty string (export MSMTPQ_LOG="") to
+disable logging ; the LOG environment variable is a deprecated alias
+of MSMTPQ_LOG
+
+Config File :
+
+each line (leading blanks are ignored) of a config file should either
+- begin with a hash mark ('#'), indicating a comment to be ignored
+- be blank, also ignored
+- start with the name of a configuration variable (MSMTP, MSMTPQ_Q, etc.), then
+  a blank, then a value for the speciried configuration variable
+
+if the system configuration file -- by default /etc/msmtp/msmtp.config
+-- exists, it is parsed
+
+then a user-specific config file is looked for in
+${XDG_CONFIG_HOME}/msmtp, if the XDG_CONFIG_HOME environment variable
+exists, or in ${HOME}/.config/msmtp.
+
+Logging :
+-------
 
 note that the default msmtpq set up creates a separate log for queue
 operations ; all operations which modify the queue in any way are logged
@@ -94,71 +128,19 @@ msmtp docs & man page.
 
 to use msmtpq with additional options :
 
-the vars : EMAIL_CONN_NOTEST (to test for a net connection or not
-                              before attempting to send a mail)
-
-                             (please note that the above var is deprecated
-                              and will be removed in a future version of msmtpq ;
-                              replace it with EMAIL_CONN_TEST=x
-                              to perform the same function)
-
+the vars :
            EMAIL_CONN_TEST   (which test for net connection to use)
            EMAIL_QUEUE_QUIET (suppress unnecessary queue 'chatter')
 
-see below for their values
-
-there are two methods of setting options -
-
-
-- set the vars in the msmtpq script (near the top)
-
-  see the script, just beneath the the three setup vars mentioned above
-
-- set the vars externally to the script
-
-    define the variables as environment vars, set on the command line
-      invoking msmtp
-
-set sendmail = "/path/to/msmtpq"
-                 # normal config for mutt to use the queue
-                 # (by default the ping connection test is enabled)
-
-set sendmail = "EMAIL_CONN_TEST=x /path/to/msmtpq"
-                 # use queue without a net connection test
-set sendmail = "EMAIL_CONN_TEST=p /path/to/msmtpq"
-                 # use queue with ping connection test (the default)
-set sendmail = "EMAIL_CONN_TEST=P /path/to/msmtpq"
-                 # use queue with faster ping connection test
-                 # (to an IP address, without a DNS lookup)
-set sendmail = "EMAIL_CONN_TEST=n /path/to/msmtpq"
-                 # use queue with netcat connection test
-                 # (netcat must be installed on user's system)
-set sendmail = "EMAIL_CONN_TEST=s /path/to/msmtpq"
-                 # use queue with sh sockets connection test
-                 # (this does *not* work on Debian systems ;
-                 #  socket use is not compiled into bash for
-                 #  security reasons on multiuser systems - it
-                 #  may not be a serious consideration, however, on
-                 #  single user workstations - e.g. on laptops -
-                 #  or in embedded systems)
-
-set sendmail = "EMAIL_QUEUE_QUIET=t /path/to/msmtpq"
-                 # use queue ; suppress messages and 'chatter'
-                 # good, apparently, for use w/emacs MUAs
-
-set sendmail = "EMAIL_CONN_TEST=? EMAIL_QUEUE_QUIET=t /path/to/msmtpq"
-                 # use queue w/connection test spec &
-                 # suppression of messages and chatter
-
-externally multiple environmental vars may be used on the command
-  line, space separated, before the msmtpq invocation ; internally
-  to the script multiple vars may be set
+EMAIL_CONN_TEST, if specified, should be one of:
+-- x :: will suppress any testing for a connection
+-- p :: will use a ping test (debian.org) for a connection
+        (this is the default if not otherwise set)
+-- P :: will use a fast ping test (8.8.8.8) for a connection
+-- n :: will use netcat (nc) to test for a connection
+-- s :: will use bash sockets to test for a connection
 
-note that vars set internally will take precedence over vars set
-  externally
 
-if no option is specified, by either method, the net connection test
-  is made using the ping test by default
 
 
 in summary :
@@ -168,8 +150,7 @@ in summary :
   create the queue dir (0700) if necessary
     ( mkdir -p /path/to/queue/dir )
     ( chmod 0700 /path/to/queue/dir )
-  enter or confirm the values of the three config setup vars in msmtpq
-  enter or confirm the values of the three option vars in msmtpq - if desired
+  create system and/or user-specific config files
   for mutt users -
     set sendmail="/path/to/msmtpq"   (or "ANY_ENV_VARS_HERE=t /path/to/msmtpq")
     set sendmail_wait = -1
diff --git a/scripts/msmtpq/msmtpq b/scripts/msmtpq/msmtpq
index bcb384e..2371202 100755
--- a/scripts/msmtpq/msmtpq
+++ b/scripts/msmtpq/msmtpq
@@ -1,5 +1,58 @@
 #!/usr/bin/env bash
 
+# XXX todos
+#
+# make sure only one, of the possible that we "by default" use, Q
+# directory and LOG file exist
+#
+# for Q and LOG paths, we do some checking and creating when
+# processing the config file, then again when processing options
+#
+# don't create Q and `dirname LOG` paths if in --q-mgmt
+#
+# xdg wants xdg_data_dirs to be next behind xdg_data_home
+
+
+# incompatibilities, behavior changes
+#
+# removed EMAIL_CONN_NOTEST
+#
+# errors (err()) now go to stderr
+
+
+# design questions
+#
+# if XDG_DATA_HOME does not exist, and if ~/.local/share does not
+# exist, where do we default the queue directory and log file?  this
+# is particularly thorny since if one day we use ~/.msmtpq.queue and
+# the next day ~/.local/share shows up and we create our subdirectory
+# there, we may end up with messages sitting "forever" in the former.
+# possibly we should look in all places, and complain if we find more
+# than one candidate?
+
+
+# enhancements for future?
+#
+# work on, clean up, command line argument processing code
+#
+# separate scripts for "queue management" and actually adding a
+# message?  probably requires a makefile (to share config code, at
+# least)
+#
+# error-report option to return permanent errors sending e-mail (to
+# user, whatever).  this sort of wants msmtp to signal a temporary
+# failure when DNS fails as a result of network connection.  on the
+# command line, the name of the sending user, maybe the SYSEXIT_ code,
+# the file name of the message that had the error (only valid until
+# error reporter exits); on stdin, the error message from msmtp to be
+# sent
+#
+# --identifier IDENTIFIER : to use when logging messages.  my idea
+# would be to have an MUA invoke msmtpq (in "sendmail" mode) with the
+# message ID of the outgoing message.  to correlate message in my
+# outbox (sent messages) with logs (including on next-hop SMTP
+# servers).  if msmtp itself would accept that parameter, pass it on.
+
 ##--------------------------------------------------------------
 ##
 ##  msmtpq : queue funtions to both use & manage the msmtp queue,
@@ -46,77 +99,113 @@
 ##   if more than one mail per second is sent
 ##--------------------------------------------------------------
 
-
+## SYS_CONFIG_FILE is where we expect to find the system config file.
+## SYS_CONFIG_FILE can be null, or point to an invalid path, to
+## disable this functionality.  packagers can, e.g., sed this to
+## whatever value is convenient e.g., something like
+# sed '/^SYS_CONFIG_FILE=/s|/etc/msmtp/msmtpq.conf|/usr/share/msmtp/msmtp.conf|'
+SYS_CONFIG_FILE=/etc/msmtp/msmtpq.conf
+
+## more for packagers: default name of user config file, default paths
+## to look for it.
+USER_CONFIG_FILE_NAME=msmtpq
+## path name of a directory under XDG_CONFIG_HOME (or, under
+## ${HOME}/.config, if unset) to find UPSTREAM_USER_CONFIG_FILE.  we
+## live in the msmtp directory.
+USER_CONFIG_FILE_XDG_SUBDIRECTORY=msmtp
+## *file name* look for if XDG isn't set, or we can't find a config
+## file there.  we append ".conf" to help the user understand what it
+## is.
+USER_CONFIG_FILE_NON_XDG="${HOME}/.${USER_CONFIG_FILE_NAME}.conf"
+
+
+
+# display a message using the arguments to dsp(); null arguments (like
+# "''") cause a blank line to be printed.
 log_later() { LOG_LATER_ARGS=( "$@" ) ; }
 dsp() { local L ; for L ; do [ -n "$L" ] && echo "  $L" || echo ; done ; }
-err() { dsp '' "$@" '' ; exit 1 ; }
+err() { dsp '' "$@" '' > /dev/stderr ; exit 1 ; }
+
+
+## these are the program default values; possibly modified by some
+## environment variables, system-wide defaults, user defaults, command
+## line options (if any implemented).  this array is also what we use
+## to decide what option names in a configuration file are legal
+declare -A defaults=(
+    ## only if necessary (in unusual circumstances - e.g. embedded systems),
+    ##   enter the location of the msmtp executable  (no quotes !!)
+    ##   e.g. ( MSMTP=/path/to/msmtp )
+    ##   and uncomment the test for its existence
+    [MSMTP]=msmtp
+    ## set the queue var to the location of the msmtp queue directory
+    ##   if the queue dir doesn't yet exist, create it (0700)
+    ##     before using this script
+    ##       e.g. ( mkdir msmtp.queue      )
+    ##            ( chmod 0700 msmtp.queue )
+    ##
+    ## the queue dir - modify this to reflect where you'd like it to
+    ## be (no quotes !!)
+    [MSMTPQ_Q]=~/.msmtp.queue
+    ## set the queue log file var to the location of the msmtp queue log file
+    ##   where it is or where you'd like it to be
+    ##     ( note that the LOG setting could be the same as the )
+    ##     ( 'logfile' setting in .msmtprc - but there may be   )
+    ##     ( some advantage in keeping the two logs separate    )
+    ##   if you don't want the log at all unset (comment out) this var
+    ##     LOG=~/log/msmtp.queue.log  -->  #LOG=~/log/msmtp.queue.log
+    ##     (doing so would be inadvisable under most conditions, however)
+    ##
+    ## the queue log file - modify (or comment out) to taste (but no
+    ## quotes !!)
+    [MSMTPQ_LOG]=~/log/msmtp.queue.log
+    ## msmtpq can use the following environment variables :
+    ##   EMAIL_CONN_TEST     if =x will suppress any testing for a connection
+    ##                       if =p or unset will use a ping test
+    ##                       (debian.org) for a connection
+    ##                       if =P will use a fast ping test (8.8.8.8) for a connection
+    ##                       if =n will use netcat (nc) to test for a connection
+    ##                       if =s will use bash sockets to test for a connection
+    [EMAIL_CONN_TEST]=n
+    ##   EMAIL_QUEUE_QUIET if set will cause suppression of messages
+    ##                         and 'chatter' (perhaps useful for some
+    ##                         of the emacs mail clients)
+    [EMAIL_QUEUE_QUIET]=""       # i.e., default is "unset"
+)
+
+
+
+declare -A config_options=(
+    [--no-config]=1
+    [--no-sys-config]=1
+    [--config-file]=1
+    [--sys-config-file]=1
+)
 
-## ======================================================================================
-##      !!!          please define or confirm the following three vars           !!!
-##      !!!           before using the msmtpq or msmtp-queue scripts             !!!
-## ======================================================================================
 ##
-## only if necessary (in unusual circumstances - e.g. embedded systems),
-##   export the location of the msmtp executable before running this script  (no quotes !!)
-##   e.g. ( export MSMTP=/path/to/msmtp )
-MSMTP="${MSMTP:-msmtp}"
-"$MSMTP" --version >/dev/null 2>&1 || \
-  log_later -e 1 "msmtpq : can't run the msmtp executable [ $MSMTP ]"   # if not found - complain ; quit
+## these are environment variables we will import if they are defined
 ##
-## set the queue var to the location of the msmtp queue directory
-##   if the queue dir doesn't yet exist, create it (0700)
-##     before using this script
-##       e.g. ( mkdir msmtp.queue      )
-##            ( chmod 0700 msmtp.queue )
-##
-## the queue dir - export this variable to reflect where you'd like it to be  (no quotes !!)
-MSMTPQ_Q=${MSMTPQ_Q:-${Q:-~/.msmtp.queue}}
-[ -d "$MSMTPQ_Q" ] || mkdir -m 0700 -p "$MSMTPQ_Q" || \
-  err '' "msmtpq : can't find or create msmtp queue directory [ $MSMTPQ_Q ]" ''     # if not present - complain ; quit
-##
-## set the queue log file var to the location of the msmtp queue log file
-##   where it is or where you'd like it to be
-##     ( note that the MSMTPQ_LOG setting could be the same as the )
-##     ( 'logfile' setting in .msmtprc - but there may be   )
-##     ( some advantage in keeping the two logs separate    )
-##   if you don't want the log at all set the var to an empty string
-##     (doing so would be inadvisable under most conditions, however)
-##
-## the queue log file - export this variable to change where logs are stored  (but no quotes !!)
-##                      Set it to "" (empty string) to disable logging.
-if [ ! -v MSMTPQ_LOG ] ; then
-  if [ -v LOG ] ; then
-    MSMTPQ_LOG="$LOG"
-  else
-    MSMTPQ_LOG=~/log/msmtp.queue.log
-  fi
-fi
-[ -d "$(dirname "$MSMTPQ_LOG")" ] || mkdir -p "$(dirname "$MSMTPQ_LOG")"
-## ======================================================================================
-
-## msmtpq can use the following environment variables :
-##   EMAIL_CONN_NOTEST   if set will suppress any testing for a connection
-##                         (the above var is deprecated & will be removed ; use the var below)
-##   EMAIL_CONN_TEST     if =x will suppress any testing for a connection
-##                       if =p or unset will use a ping test (debian.org) for a connection
-##                       if =P will use a fast ping test (8.8.8.8) for a connection
-##                       if =n will use netcat (nc) to test for a connection
-##                       if =s will use bash sockets to test for a connection
-##   EMAIL_QUEUE_QUIET   if set will cause suppression of messages and 'chatter'
-##                         (perhaps useful for some of the emacs mail clients)
-##
-## ======================================================================================
-##      !!!      define or confirm the following vars if you wish to set         !!!
-##      !!!      these properties here in the script - the same properties       !!!
-##      !!!      may be set externally, by means of environment variables        !!!
-##      !!!      note the internal variables, if set, will take precedence       !!!
-##      !!!      over properties set via environment variables                   !!!
-## ======================================================================================
-##
-#EMAIL_CONN_NOTEST=y                 # deprecated ; use below var
-#EMAIL_CONN_TEST={x| |p|P|n|s}       # see settings above for EMAIL_CONN_TEST
-#EMAIL_QUEUE_QUIET=t
-## ======================================================================================
+declare -A environs=(
+    [MSMTP]=MSMTP
+    [MSMTPQ_Q]=MSMTPQ_Q
+    [Q]=MSMTPQ_Q
+    [MSMTPQ_LOG]=MSMTPQ_LOG
+    [LOG]=MSMTPQ_LOG
+)
+
+
+
+
+# for each option in options, where did it come from (defaults, config
+# file name)
+declare -Ag sources
+# the options we determine.  options from any user config file take
+# precedence over any system config file, which in turn takes
+# precendence over program defaults. later, these are passed off to
+# various scalar variables (MSMTP, etc.)
+declare -Ag options
+
+# are we being verbose?
+declare -g verbose
 
 umask 077                            # set secure permissions on created directories and files
 
@@ -137,6 +226,195 @@ on_exit() {                          # unlock the queue on exit if the lock was
 ## ----------------------------------- (msmtpq & msmtp-queue)
 #
 
+# helper function for setdefaults(): create, if needed, places for
+# things relative to xdg_data_home
+#
+# inputs:
+#
+# $1: the tag in the defaults[] array
+#
+# $2: the legacy location of the directory
+#
+# $3: relative path to the directory in which it should live (relative
+# to XDG)
+#
+# $4: (optional, only if file, rather than directory, is wanted) the
+# file name to live in the directory $2 (in order to set defaults to
+# the path to the actual file, not the path to the directory
+#
+# update:
+#
+# the defaults[] array
+doxdgdata() {
+    if [  -d "$2" ]; then
+        directory="$2"
+    else
+        # it doesn't exist in the legacy place.  put whatever it is
+        # somewhere XDG likes.  first, make sure we have the XDG data
+        # directory
+        local xdgdata=${XDG_DATA_HOME:-"${HOME}/.local/share"} # XXX hard coded, but from spec
+        local directory="${xdgdata}/$3"
+        if [ ! -d "${xdgdata}" ]; then
+            # shellcheck disable=SC2174
+            mkdir -p -m 0700 "${xdgdata}" # try to get the right file mode here
+        fi
+        if [ ! -d "${directory}" ]; then # XXX hard coded file name
+            # shellcheck disable=SC2174
+            mkdir -p -m 0700 "${directory}"
+        fi
+    fi
+    if [ -n "$4" ]; then
+        defaults["$1"]="${directory}/$4"
+    else
+        defaults["$1"]="${directory}"
+    fi
+}
+
+# now, *if* the old, historic, directory/file doesn't exit, re-think
+# MSMTPQ_Q and MSMTPQ_LOG based on XDG_DATA_HOME.  if it is defined,
+# and *that* directory exists, then use that with the relevant
+# subdirectory.
+setdefaults() {
+    doxdgdata MSMTPQ_Q "${defaults[MSMTPQ_Q]}" msmtp/msmtpq.queue
+    doxdgdata MSMTPQ_LOG "$(dirname "${defaults[MSMTPQ_LOG]}")" msmtp msmtp.queue.log
+}
+
+## process the defaults, putting them in options (and setting sources
+## to refect their origin)
+eatdefaults() {
+    for var in "${!defaults[@]}"; do
+        options[${var}]=${defaults[${var}]}
+        sources[${var}]=defaults
+    done
+}
+
+## look for any environment variables
+eatenvirons() {
+    declare -A setted
+
+    for env in "${!environs[@]}"; do
+        index="${environs[${env}]}" # where it goes in array
+        # x=$(eval echo $`echo "${yyy}"`)
+        val=$(eval echo $`echo "${env}"`) # value of environmental variable
+        if [ -n "${val}" ]; then
+            if [ -n "${setted[${index}]}" ]; then
+                err "${index} set by two environment variables: ${env} and ${setted[${index}]}"
+            fi
+            setted[${index}]=${env} # for error checking, keep track of what we've set
+            options[${index}]="${val}"
+            sources[${index}]=environment
+        fi
+    done
+
+    # now, backwards compatibility requires
+    if [ -n "${Q}" ]; then
+        options[MSMTPQ_Q]="${Q}"
+        sources[MSMQPQ_Q]=environment
+    fi
+    if [ -n "${LOG}" ]; then
+        options[MSMTPQ_LOG]="${LOG}"
+        sources[MSMTPQ_LOG]=environment
+    fi
+}
+
+## process the contents of a configuration file.  some helps:
+## https://danielfgray.gitlab.io/computers/bash-config/
+## https://askubuntu.com/a/743641
+eatconfig() {
+
+    # dealing with missing terminating newline:
+    # https://stackoverflow.com/a/12919766/1527747
+    while read -r var value || [ -n "${var}" ]; do
+        if [ -z "${var}" ] || [ "${var:0:1}" = "#" ]; then
+            continue
+        fi
+        if [[ ${verbose} -gt 1 ]]; then
+            printf "var %s: %s\n" "${var}" "${value}"
+        fi
+        if [[ ! -v defaults[${var}] ]]; then
+            err "config file \"${1}\": invalid option \"${var}\""
+        fi
+        # if verbose, then if we are replacing something from *one*
+        # configuration file (presumably, the system one) with a
+        # different value from a *different* configuration file
+        # (presumably, the user's), issue a warning
+        if [ -n "${verbose}" ] &&
+               [ -n "${options[${var}]}" ] &&
+               [ ! "${sources[${var}]}" = default ] &&
+               [ ! "${value}" = "${options[${var}]}" ]; then
+            echo "file \"${1}\" overrides \"${var}\" set in \"${sources[${var}]}\""
+        fi
+        sources[${var}]="${1}"
+        options[${var}]=$(eval "$(printf "echo %s" "${value}")")
+    done < "${1}"
+}
+
+
+## using the defaults and config file, set configuration parameters
+## from the options array (which, at this point, has an entry for
+## every possible option, though maybe an "unset" entry for some)
+##
+## then, check some to make sure they are valid
+setoptions() {
+    MSMTP=${options[MSMTP]}
+    if ! "$MSMTP" --version >/dev/null 2>&1; then
+        # if not found, or not executable, complain ; quit
+        log -e 1 "msmtpq : can't run the msmtp executable [ $MSMTP ]"
+    fi
+
+    MSMTPQ_Q=${options[MSMTPQ_Q]}
+    # if not present and can't create - complain ; quit
+    if [ ! -d "$MSMTPQ_Q" ]; then
+        # shellcheck disable=SC2174
+        if ! mkdir -m 0700 -p "$MSMTPQ_Q"; then
+            # if not present and can't create - complain ; quit
+            err '' "msmtpq : can't find or create msmtp queue directory [ $MSMTPQ_Q ]" ''
+        fi
+    fi
+
+    MSMTPQ_LOG=${options[MSMTPQ_LOG]}
+    if [ ! -d "$(dirname "$MSMTPQ_LOG")" ]; then
+        # shellcheck disable=SC2174
+        if ! mkdir -p "$(dirname "$MSMTPQ_LOG")"; then
+            err '' "msmtpq : can't find msmtpq log and" ''\
+                "can't create msmtpq log directory ($(dirname "${MSMTPQ_LOG}"))"
+        fi
+    fi
+    ## ======================================================================================
+
+    ## ======================================================================================
+    ##      !!!      define or confirm the following vars if you wish to set         !!!
+    ##      !!!      these properties here in the script - the same properties       !!!
+    ##      !!!      may be set externally, by means of environment variables        !!!
+    ##      !!!      note the internal variables, if set, will take precedence       !!!
+    ##      !!!      over properties set via environment variables                   !!!
+    ## ======================================================================================
+    ##
+    #EMAIL_CONN_TEST={x| |p|P|n|s}       # see settings above for EMAIL_CONN_TEST
+    EMAIL_CONN_TEST=${options[EMAIL_CONN_TEST]}
+    # test for a valid value
+    if [ -n "${EMAIL_CONN_TEST}" ]; then
+        if [ ${#EMAIL_CONN_TEST} -ne 1 ] ||
+               [ -n "${EMAIL_CONN_TEST/[xpPns]/}" ]; then
+            err '' "msmtpq: invalid EMAIL_CONN_TEST option \"${EMAIL_CONN_TEST}\" (from \"${sources[EMAIL_CONN_TEST]}\"); must be one of: n p P s x"
+        fi
+    fi
+    EMAIL_QUEUE_QUIET=${options[EMAIL_QUEUE_QUIET]}
+    if [ -n "${EMAIL_QUEUE_QUIET}" ] && [ ! "${EMAIL_QUEUE_QUIET}" = "y" ]; then
+        err '' "msmtpq: illegal EMAIL_QUEUE_QUIET option \"${EMAIL_QUEUE_QUIET}\" (set in \"${sources[EMAIL_QUEUE_QUIET]}\") should be unset or set to 'y'"
+    fi
+}
+
+## dump the options, with their sources
+dumptions() {
+    for opt in "${!options[@]}"; do
+        if [ -z "${sources[${opt}]}" ]; then
+            err '' "program error: no source for option \"${opt}\""
+        fi
+        printf "%s: %s (%s)\n" "${opt}" "${options[${opt}]}" "${sources[${opt}]}"
+    done
+}
+
 ## make an entry to the queue log file, possibly an error
 ##   (log queue changes only ; not interactive chatter)
 ## usage : log [ -e errcode ] msg [ msg ... ]
@@ -240,22 +518,42 @@ connect_test() {
 ## show queue maintenance functions
 ##
 usage() {        # <-- error msg
-  dsp ''\
-      'usage : msmtp-queue functions' ''\
-      '        msmtp-queue < op >'\
-      '        ops : -r   run (flush) mail queue - all mail in queue'\
-      '              -R   send selected individual mail(s) in queue'\
-      '              -d   display (list) queue contents   (<-- default)'\
-      '              -p   purge individual mail(s) from queue'\
-      '              -a   purge all mail in queue'\
-      '              -h   this helpful blurt' ''\
-      '        ( one op only ; any others ignored )' ''
-  if [ -z "$1" ]; then
-    exit 0;
-  else
-    dsp "$@" '';
-    exit 1;
-  fi
+    dsp ''\
+        'usage : msmtp-queue functions' ''\
+        '        msmtp-queue { verbose } { config } < op >'\
+        '        verbose : '\
+        '              -v   be more verbose (can be repeated)' ''\
+        '        config : at most one each of system and user'\
+        '              --no-config'\
+        '                do not process a user config file'\
+        '              --config-file PATH'\
+        '                use PATH as the user config file'\
+        '              --no-sys-config'\
+        '                do not process a system config file'\
+        '              --sys-config-file PATH'\
+        '                use PATH as the system config file' ''\
+        '        ops : -r   run (flush) mail queue - all mail in queue'\
+        '              -R   send selected individual mail(s) in queue'\
+        '              -d   display (list) queue contents   (<-- default)'\
+        '              -p   purge individual mail(s) from queue'\
+        '              -a   purge all mail in queue'\
+        '              -h   this helpful blurt' ''\
+        '        ( one op only ; any others ignored )' ''
+
+    if [ $((verbose)) -gt 0 ]; then
+        dsp ''\
+            'unless the \"--no-config\" option is specified,'\
+            'a system configuration file is looked for, and'\
+            'processed if found, in /etc/msmtp.config and in'\
+            '/etc/msmtp/msmtp.config (only the first found is processed)'
+    fi
+
+    if [ -z "$1" ]; then
+        exit 0;
+    else
+        dsp "$@" '';
+        exit 1;
+    fi
 }
 
 ## get user [y/n] acknowledgement
@@ -287,7 +585,7 @@ send_queued_mail() {   # <-- mail id
 
   if [ -f "${FQP}.msmtp" ] ; then    # corresponding .msmtp file found
     [ "$EMAIL_CONN_TEST" != 'x' ] && \
-    [ -z "$EMAIL_CONN_NOTEST" ] && { # do connection test
+    { # do connection test
       connect_test || {
         log "mail [ $2 ] [ $1 ] from queue ; couldn't be sent - host not connected"
         return
@@ -507,7 +805,7 @@ enqueue_mail() { # <-- all mail args ; mail text via TMP
 ##
 send_mail() {    # <-- all mail args ; mail text via TMP
   [ "$EMAIL_CONN_TEST" != 'x' ] && \
-  [ -z "$EMAIL_CONN_NOTEST" ] && {   # do connection test
+  {   # do connection test
     connect_test || {
       log "mail for [ $* ] : couldn't be sent - host not connected"
       enqueue_mail "$@"              # enqueue the mail
@@ -530,7 +828,123 @@ send_mail() {    # <-- all mail args ; mail text via TMP
 #
 
 [ -v LOG_LATER_ARGS ] && log "${LOG_LATER_ARGS[@]}"
-if [ ! "$1" = '--q-mgmt' ] ; then    # msmtpq - sendmail mode
+
+# verbosity level -- must come first!
+while [ "$1" = "-v" ]; do
+    verbose=$((verbose+1))
+    shift
+done
+
+if [[ ${verbose} -gt 3 ]]; then
+    set -x
+fi
+
+# now, in what mode are we running?
+if [ "$1" = '--q-mgmt' ] ; then    # msmtpq - queue enquiry mode
+    qmgmt=true
+    shift                       # consume this argument
+else
+    qmgmt=""
+fi
+
+# find any configuration files, then compose running configuration
+# (from program defaults, system config, per-user config
+
+# don't assign a value here.  we look for it later, as it might be
+# assigned to be unassigned, and we want to notice that
+declare configfile
+declare sysconfig="${SYS_CONFIG_FILE}"
+
+# do any processing needed on defaults
+setdefaults
+
+# set defaults into options
+eatdefaults
+# then, environment variables into options
+eatenvirons
+
+# now, try to find the appropriate config files (system and user)
+
+while [ -n "$1" ] && [ "${config_options[$1]}" ]; do
+    if [ "$1" = "--no-config" ]; then # don't process configuration file(s)
+        if [ -n "${useroptions}" ]; then
+            err 'can only specify one of "--no-config" and "--config-file"'
+        fi
+        useroptions=$((useroptions++))
+        nouserconfig=1
+        shift
+    elif [ "$1" = "--config-file" ]; then # set
+        if [ -n "${useroptions}" ]; then
+            err 'can only specify one of "--no-config" and "--config-file"'
+        fi
+        useroptions=$((useroptions++))
+        userconfig="$2"
+        if [ -d "${userconfig}" ]; then
+            err "--config-file: \"${userconfig}\" is a directory"
+        fi
+        if [ ! -r "${userconfig}" ]; then
+            err "--config-file: \"${userconfig}\" does not exist"
+        fi
+        shift 2
+    elif [ "$1" = "--no-sys-config" ]; then
+        if [ -n "${sysoptions}" ]; then
+                err 'can only specify one of "--no-sys-config" and "--sys-config-file"'
+        fi
+        sysoptions=$((sysoptions++))
+        nosysconfig=1
+        shift
+    elif [ "$1" = "--sys-config-file" ]; then # set
+        if [ -n "${sysoptions}" ]; then
+            err 'can only specify one of "--no-sys-config" and "--sys-config-file"'
+        fi
+        sysoptions=$((sysoptions++))
+        sysconfig="$2"
+        if [ -d "${sysconfig}" ]; then
+            err "--sys-config-file: \"${sysconfig}\" is a directory"
+        fi
+        if [ ! -r "${sysconfig}" ]; then
+            err "--config-file: \"${sysconfig}\" does not exist"
+        fi
+        shift 2
+    fi
+done
+
+if [ -z "${nosysconfig}" ]; then
+    # maybe *not* `-f ${sysconfig}`, if it wasn't specified on the
+    # command line (and does not, in fact, exit): as opposed to the
+    # user configuration file, we look only in SYS_CONFIG_FILE
+    if [ ! -d "${sysconfig}" ] && [ -r "${sysconfig}" ]; then
+        eatconfig "${sysconfig}"
+    fi
+fi
+
+if [ -z "${nouserconfig}" ]; then
+    # if --config-file *not* given on the command line, try to
+    # find it one of the typical places
+    if [ ! -v "userconfig" ]; then
+        # where we should find XDG-compliant file
+        declare xch="${XDG_CONFIG_HOME:-${HOME}/.config}/${USER_CONFIG_FILE_XDG_SUBDIRECTORY}"
+        for configfile in "${xch}/${USER_CONFIG_FILE_NAME}" "${USER_CONFIG_FILE_NON_XDG}"; do
+            if [ ! -d "${configfile}" ] &&  [ -r "${configfile}" ]; then
+                break;          # found one
+            fi
+        done
+    fi
+    # now, like with sys config file, if we *don't* find it, that will
+    # be because it was *not* specified on the command line, and
+    # doesn't exist in the default locations: no problem.
+    if [ ! -d "${userconfig}" ] &&  [ -r "${userconfig}" ]; then
+        eatconfig "${userconfig}"
+    fi
+fi
+
+setoptions
+
+if [[ ${verbose} -gt 2 ]]; then
+    dumptions
+fi
+
+if [ ! "${qmgmt}" ]; then    # msmtpq - sendmail mode
   lock_queue                         # lock here
   make_id                            # make base queue filename id for this mail
   # write mail body text to queue .mail file
@@ -543,7 +957,6 @@ if [ ! "$1" = '--q-mgmt' ] ; then    # msmtpq - sendmail mode
   send_mail "$@"                     # send the mail if possible, queue it if not
   lock_queue -u                      # unlock here
 else                                 # msmtp-queue - queue management mode
-  shift                              # trim off first (--q-mgmt) arg
   OP=${1:1}                          # trim off first char of OP arg
   case "$OP" in                      # sort ops ; run according to spec
     r)    lock_queue
@@ -554,6 +967,7 @@ else                                 # msmtp-queue - queue management mode
     p)    select_mail purge   ;;     # purge individual mail(s) from queue
     a)    purge_queue         ;;     # purge all mail in queue
     h)    usage               ;;     # show help
+    # otherwise
     *)    usage "[ -$OP ] is an unknown msmtp-queue option" ;;
   esac
 fi
diff --git a/scripts/msmtpq/tests/configs/bad-email-conn-test b/scripts/msmtpq/tests/configs/bad-email-conn-test
new file mode 100644
index 0000000..67e7377
--- /dev/null
+++ b/scripts/msmtpq/tests/configs/bad-email-conn-test
@@ -0,0 +1 @@
+EMAIL_CONN_TEST 8
\ No newline at end of file
diff --git a/scripts/msmtpq/tests/configs/bad-email-queue-quiet b/scripts/msmtpq/tests/configs/bad-email-queue-quiet
new file mode 100644
index 0000000..a31f458
--- /dev/null
+++ b/scripts/msmtpq/tests/configs/bad-email-queue-quiet
@@ -0,0 +1 @@
+EMAIL_QUEUE_QUIET n
\ No newline at end of file
diff --git a/scripts/msmtpq/tests/configs/bad.conf b/scripts/msmtpq/tests/configs/bad.conf
new file mode 100644
index 0000000..b7d1dd5
--- /dev/null
+++ b/scripts/msmtpq/tests/configs/bad.conf
@@ -0,0 +1 @@
+BADOPT bad bad bad
\ No newline at end of file
diff --git a/scripts/msmtpq/tests/configs/comments-and-blanks b/scripts/msmtpq/tests/configs/comments-and-blanks
new file mode 100644
index 0000000..e9f96ac
--- /dev/null
+++ b/scripts/msmtpq/tests/configs/comments-and-blanks
@@ -0,0 +1,13 @@
+# this is a comment
+
+# to keep from getting different values depending on where we are
+# printed out by dumptions(), specify these two
+
+MSMTPQ_Q ~/.local/share/msmtp/msmtp.queue
+MSMTPQ_LOG ~/.local/share/msmtp/msmtp.queue.log
+
+# after some blank lines
+
+  # here is another comment
+
+MSMTP echo
diff --git a/scripts/msmtpq/tests/configs/good.conf b/scripts/msmtpq/tests/configs/good.conf
new file mode 100644
index 0000000..95dd3fc
--- /dev/null
+++ b/scripts/msmtpq/tests/configs/good.conf
@@ -0,0 +1,2 @@
+MSMTPQ_Q ./tests/queue
+MSMTPQ_LOG ./tests/log
diff --git a/scripts/msmtpq/tests/configs/no-newline b/scripts/msmtpq/tests/configs/no-newline
new file mode 100644
index 0000000..e4f5904
--- /dev/null
+++ b/scripts/msmtpq/tests/configs/no-newline
@@ -0,0 +1,7 @@
+# to keep from getting different values depending on where we are
+# printed out by dumptions(), specify these two
+
+MSMTPQ_Q ~/.local/share/msmtp/msmtp.queue
+MSMTPQ_LOG ~/.local/share/msmtp/msmtp.queue.log
+
+MSMTP echo
\ No newline at end of file
diff --git a/scripts/msmtpq/tests/cram/tests.t b/scripts/msmtpq/tests/cram/tests.t
new file mode 100644
index 0000000..6017818
--- /dev/null
+++ b/scripts/msmtpq/tests/cram/tests.t
@@ -0,0 +1,155 @@
+these are (intended to be) optional tests that can be run if cram is
+installed.  `cram -i ...` is how to update with new tests or new
+results. see
+
+https://bitheap.org/cram/
+
+
+option processing:
+- command-line specified config file is a directory, or does not exist
+
+  $ (cd ${TESTDIR}/../.. && ./msmtpq --q-mgmt --sys-config-file ./tests 2>&1)
+  
+    --sys-config-file: "./tests" is a directory
+  
+  [1]
+
+  $ (cd ${TESTDIR}/../.. && ./msmtpq --q-mgmt --sys-config-file ./tests/DOESNOTEXIST 2>&1)
+  
+    --config-file: "./tests/DOESNOTEXIST" does not exist
+  
+  [1]
+
+
+  $ (cd ${TESTDIR}/../.. && ./msmtpq --q-mgmt --config-file ./tests 2>&1)
+  
+    --config-file: "./tests" is a directory
+  
+  [1]
+
+  $ (cd ${TESTDIR}/../.. && ./msmtpq --q-mgmt --config-file ./tests/DOESNOTEXIST 2>&1)
+  
+    --config-file: "./tests/DOESNOTEXIST" does not exist
+  
+  [1]
+
+- comments, blank lines, in the config file
+
+  $ (cd ${TESTDIR}/../.. && ./msmtpq -v -v -v --q-mgmt --config-file ./tests/configs/comments-and-blanks 2>&1)
+  var MSMTPQ_Q: ~/.local/share/msmtp/msmtp.queue
+  file "./tests/configs/comments-and-blanks" overrides "MSMTPQ_Q" set in "defaults"
+  var MSMTPQ_LOG: ~/.local/share/msmtp/msmtp.queue.log
+  file "./tests/configs/comments-and-blanks" overrides "MSMTPQ_LOG" set in "defaults"
+  var MSMTP: echo
+  file "./tests/configs/comments-and-blanks" overrides "MSMTP" set in "defaults"
+  EMAIL_CONN_TEST: n (defaults)
+  MSMTPQ_LOG: /home/minshall/.local/share/msmtp/msmtp.queue.log (./tests/configs/comments-and-blanks)
+  EMAIL_QUEUE_QUIET:  (defaults)
+  MSMTPQ_Q: /home/minshall/.local/share/msmtp/msmtp.queue (./tests/configs/comments-and-blanks)
+  MSMTP: echo (./tests/configs/comments-and-blanks)
+  
+
+- config file with no terminating newline
+
+first, make sure the newline is still missing (i.e., not deleted during development/distribution of the package)
+  $ NONEWLINE=./tests/configs/no-newline
+  $ (cd ${TESTDIR}/../.. && tail -1 "${NONEWLINE}" | tr -c -d '\n'| tr '\n' 'n' | (! grep -q n) || (echo "ERROR: a trailing newline suddenly appeared in "${NONEWLINE}"" && ./tests/scripts/exiter 1) 2>&1)
+
+now, test the terminating newline-less file
+  $ (cd ${TESTDIR}/../.. && ./msmtpq -v -v -v --q-mgmt --config-file "${NONEWLINE}" 2>&1)
+  var MSMTPQ_Q: ~/.local/share/msmtp/msmtp.queue
+  file "./tests/configs/no-newline" overrides "MSMTPQ_Q" set in "defaults"
+  var MSMTPQ_LOG: ~/.local/share/msmtp/msmtp.queue.log
+  file "./tests/configs/no-newline" overrides "MSMTPQ_LOG" set in "defaults"
+  var MSMTP: echo
+  file "./tests/configs/no-newline" overrides "MSMTP" set in "defaults"
+  EMAIL_CONN_TEST: n (defaults)
+  MSMTPQ_LOG: /home/minshall/.local/share/msmtp/msmtp.queue.log (./tests/configs/no-newline)
+  EMAIL_QUEUE_QUIET:  (defaults)
+  MSMTPQ_Q: /home/minshall/.local/share/msmtp/msmtp.queue (./tests/configs/no-newline)
+  MSMTP: echo (./tests/configs/no-newline)
+  
+
+
+- specify both --no-config and --config-file on command line (that's illegal)
+
+  $ (cd ${TESTDIR}/../.. && ./msmtpq --q-mgmt --no-sys-config --sys-config-file ./tests/DOESNOTEXIST 2>&1)
+  
+    can only specify one of "--no-sys-config" and "--sys-config-file"
+  
+  [1]
+
+
+  $ (cd ${TESTDIR}/../.. && ./msmtpq --q-mgmt --no-config --config-file ./tests/DOESNOTEXIST 2>&1)
+  
+    can only specify one of "--no-config" and "--config-file"
+  
+  [1]
+
+- good options in config file
+
+- invalid options in config file
+
+  $ (cd ${TESTDIR}/../.. && ./msmtpq --q-mgmt --config-file ./tests/data/bad.conf 2>&1)
+  
+    --config-file: "./tests/data/bad.conf" does not exist
+  
+  [1]
+
+- can't create queue directory
+- can't create directory to hold log
+
+- good values for EMAIL_CONN_TEST
+
+  $ (cd ${TESTDIR}/../.. && for val in n p P s x; do ./msmtpq --q-mgmt --config-file <(echo EMAIL_CONN_TEST ${val}); done > /dev/null)
+
+- invalid value for EMAIL_CONN_TEST
+
+  $ (cd ${TESTDIR}/../.. && ./msmtpq --q-mgmt --config-file ./tests/configs/bad-email-conn-test 2>&1)
+  
+  
+    msmtpq: invalid EMAIL_CONN_TEST option "8" (from "./tests/configs/bad-email-conn-test"); must be one of: n p P s x
+  
+  [1]
+
+- good value for EMAIL_QUEUE_QUIET
+
+  $ (cd ${TESTDIR}/../.. && ./msmtpq --q-mgmt --config-file <(echo EMAIL_QUEUE_QUIET y) > /dev/null)
+
+- invalid value for EMAIL_QUEUE_QUIET
+
+  $ (cd ${TESTDIR}/../.. && ./msmtpq --q-mgmt --config-file ./tests/configs/bad-email-queue-quiet 2>&1)
+  
+  
+    msmtpq: illegal EMAIL_QUEUE_QUIET option "n" (set in "./tests/configs/bad-email-queue-quiet") should be unset or set to 'y'
+  
+  [1]
+
+- specify both --no-config and --config-file on command line
+
+  $ (cd ${TESTDIR}/../.. && ./msmtpq --q-mgmt --no-config --config-file ./tests/data/good.conf 2>&1)
+  
+    can only specify one of "--no-config" and "--config-file"
+  
+  [1]
+
+- specify both --no-sys-config and --sys-config-file on command line
+
+  $ (cd ${TESTDIR}/../.. && ./msmtpq --q-mgmt --no-sys-config --sys-config-file ./tests/data/good.conf 2>&1)
+  
+    can only specify one of "--no-sys-config" and "--sys-config-file"
+  
+  [1]
+
+- XXX test algorithm for finding user config file (with, without XDG...)
+
+- two environment variables setting the same variable
+
+  $ (cd ${TESTDIR}/../.. && (export MSMTPQ_Q=this && export Q=that && ./msmtpq --q-mgmt) 2>&1)
+
+  $ (cd ${TESTDIR}/../.. && (export MSMTPQ_LOG=this && export LOG=that && ./msmtpq --q-mgmt) 2>&1)
+
+running:
+- XXX can't write to log file
+
+- XXX lots else!
diff --git a/scripts/msmtpq/tests/scripts/exiter b/scripts/msmtpq/tests/scripts/exiter
new file mode 100755
index 0000000..6584bf8
--- /dev/null
+++ b/scripts/msmtpq/tests/scripts/exiter
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+# for testing, sometimes, need to exit with a given exit status
+
+exit $1

it has these "features"

  • both a system config file and a per-user config file are available
  • command line options to disable either or both
  • command line options to use different config files
  • for developers, if the cram test utility (it's pretty nice) is available, there are some tests (currently just of the bits i modified)
  • it drops support for EMAIL_CONN_NOTEST (not exactly a feature)
  • errors (from err()) go to stderr
  • some of the error reporting is pretty good
  • it does try to use XDG_CONFIG_HOME and XDG_DATA_HOME

but, it's > 1000 lines (including the tests, but they're not that much, unfortunately).

fwiw, i wouldn't mind owning msmtpq for a bit. there are several things i'd like to do (under supervision/consultation, of course), plus maybe some cleanup, including of my code here.

greg-minshall avatar Apr 22 '23 16:04 greg-minshall

The msmtpq script does not have a maintainer currently, so go ahead :)

marlam avatar Apr 22 '23 17:04 marlam