wiki:ConfigTricks/CheckAttach

Version 5 (modified by brisant, 4 years ago) (diff)

--

This script is provided by MuttChannel user "rev_"(?) to remind you of missing attachments after you finish the msg and are ready to send it, but before it actually is being sent out by the system (sendmail).

You get the idea how it works to implement it in any shell/ code language you want or do other control stuff. Principle:

  1. mutt hands over finished full msg with headers as STDIN to $sendmail, muttrc: set sendmail="this-script"
  2. it applies various user-definable checks on the msg,
  3. if they fail, ask user for confirmation:
    • return if something is wrong
    • continue when you want it to go as it is now.

Read '#'-comments to better understand.


#!/bin/bash

SENDMAIL=/usr/sbin/sendmail

###
# this script uses a GUI app called "zenity" to ask for confirmation,
# if not in GUI, just let it pass. If you want TUI (not GUI) query,
# which means you replace "zenity" with something else, then
# drop this DISPLAY check.
###
if [ -z "$DISPLAY" ]; then
    exec $SENDMAIL "$@"
fi

###
# save msg in file to re-use it for multiple tests
###
t=`mktemp -t mutt.XXXXXX` || exit 2
cat > $t

###
# define tests for: anything you can think of can be done.
# the return code of last exec'ed cmd is used in final decision below
###
function multipart {
    grep -q '^Content-Type: multipart' $t
}

function word-attach {
    grep -v '^>' $t | egrep -q 'attach\|bijgevoegd\|bijvoeg\|patch'
}

###
# query function must return "true" to let the msg pass.
###
function ask {
    zenity --question --title 'mutt' --text 'Did you include the attachment (if you wanted to)?'
}

###
# FINAL DECISION:
# chain series of functions, use ! || && for logic connections,
# see "man $SHELL" for more possibilities like grouping, dependencies.
###
if multipart || ! word-attach || ask; then
    $SENDMAIL "$@" < $t
    status=$?
else
    status=1
fi
rm -f $t
exit $status

The above script uses zenity, which only works with X, so it's not much use in console mode. The script below is for console mode users. Maybe one day I make a hybrid solution.

Unfortunately it's not possible to query the user from the script in console mode (at least I haven't found a way) because stdin is already redirected. So instead this version simply aborts sending the message if it senses there is an attachment missing. If really no attachment was intended then the user must include "X-attached: none" in the mail headers. TODO: someone please add a link to the necessary configuration to edit mail headers as part of the mail composition. (Footnote: It would have been nice to let the user add a blank header, e.g. just "X-attached:", but it seems mutt removes such headers before sending.)

Save the script as: /usr/local/bin/mutt_check_attachment_before_send.sh

Edit muttrc to have this line:

set sendmail = "/usr/local/bin/mutt_check_attachment_before_send.sh /usr/lib/sendmail -oem -oi"

#!/bin/bash

##
## Script: /usr/local/bin/mutt_check_attachment_before_send.sh
##
## Source: http://wiki.mutt.org/?ConfigTricks/CheckAttach
##
## Edit muttrc to have this line:
## set sendmail = "/usr/local/bin/mutt_check_attachment_before_send.sh /usr/lib/sendmail -oem -oi"
##

## Attachment keywords that the message body will be searched for:
KEYWORDS='attach|bijgevoegd|bijvoeg|patch'

## Check that sendmail or other program is supplied as first argument.
if [ ! -x "$1" ]; then
    echo "Usage: $0 </path/to/mailprog> <args> ..."
    echo "e.g.: $0 /usr/sbin/sendmail -oem -oi"
    exit 2
fi

## Save msg in file to re-use it for multiple tests.
TMPFILE=`mktemp -t mutt_checkattach.XXXXXX` || exit 2
cat > "$TMPFILE"

## Define test for multipart message.
function multipart {
    grep -q '^Content-Type: multipart' "$TMPFILE"
}

## Define test for keyword search.
function word-attach {
    grep -v '^>' "$TMPFILE" | grep -E -i -q "$KEYWORDS"
}

## Header override.
function header-override {
    grep -i -E "^X-attached: *none *$" "$TMPFILE"
}

## FINAL DECISION:
if multipart || ! word-attach || header-override; then
    "$@" < "$TMPFILE"
    EXIT_STATUS=$?
else
    echo "No file was attached but a search of the message text suggests there should be one.  Add a header \"X-attached: none\" to override this check if no attachment is intended."
    EXIT_STATUS=1
fi

## Delete the temporary file.
rm -f "$TMPFILE"

## That's all folks.
exit $EXIT_STATUS

Another simple script was posted to mutt-dev http://marc.info/?l=mutt-dev&m=110663305206264&w=2


I have changed the above script to make it look like beeing interactive in mutt. When the checks detect attchments are missing it will print a question at the status line of mutt and the user can answer how to proceed:

Pro: No X needed, No extra header needed (but still available). Con: One of the parent processes need to have a valid pts, tty executable must be available

Snippets are taken from a mairix script, which integrates mairix search in a similar way.

#!/bin/zsh
##
## Script: ~/.mutt/sendmail_wrapper.zsh
##
## Source: http://wiki.mutt.org/?ConfigTricks/CheckAttach
##
## muttrc should most probably contain this line:
## set sendmail = "~/.mutt/sendmail_wrapper.zsh /usr/bin/sendmail -oem -oi"

## Attachment keywords that the message body will be searched for:
KEYWORDS='attach|anhang|anhaenge|anhänge|angehaengt|angehangen|anbei|angehängt'
ERRORMESSAGE="Attachment missing!? (Use \"X-attached: none\" to override) Send mail anyway? "

## Check that sendmail or other program is supplied as first argument.
if [ ! -x "$1" ]
then
    echo "Usage: $0 </path/to/mailprog> <args> ..."
    echo "e.g.: $0 /usr/sbin/sendmail -oem -oi"
    exit 2
fi

## Save msg in file to re-use it for multiple tests.
TMPFILE=`mktemp -t mutt_checkattach.XXXXXX` || exit 2
cat > "$TMPFILE"

## Define test for multipart message.
function multipart {
    grep -q '^Content-Type: multipart' "$TMPFILE"
}

## Define test for keyword search.
function word-attach {
    grep -v '^>' "$TMPFILE" | grep -E -i -q "$KEYWORDS"
}

## Header override.
function header-override {
    grep -i -E "^X-attached: none$" "$TMPFILE"
}

function interactive-ask {
  # disable globbing
  set -f
  # try to get stdin/stdout/stderr from parent process
  PID_cur=$$
  TTY_cur=$(ps h -o tty -p ${PID_cur})
  while ! tty > /dev/null
  do
    PID_cur=$(cut -f4 -d' ' /proc/${PID_cur}/stat)
    TTY_cur=$(ps h -o tty -p ${PID_cur})
    TTY_use="/dev/${TTY_cur}"
    if [ -e ${TTY_use} ]
    then
      exec 0</dev/${TTY_cur} 1> /dev/${TTY_cur} 2>/dev/null
    fi
  done
  # reset terminal settings to current state when exit
  saved_tty_settings=$(stty -g)
  trap '
  printf "\r"; tput ed; tput rc
  printf "/" >&3
  stty "$saved_tty_settings"
  exit
  ' INT TERM

  # put the terminal in cooked mode. Set eof to <return> so that pressing
  # <return> doesn't move the cursor to the next line. Disable <Ctrl-Z>
  stty icanon echo -ctlecho crterase eof '^M' intr '^G' susp ''

  set $(stty size) # retrieve the size of the screen
  tput sc          # save cursor position
  tput cup "$1" 0  # go to last line of the screen
  tput ed          # clear and write prompt
  tput sgr0

  # print message
  printf ${ERRORMESSAGE}

  # read from the terminal. We can't use "read" because, there won't be
  # any NL in the input as <return> is eof.
  answer=$(dd count=1 2>/dev/null)

  # fix the terminal
  printf '\r'; tput ed; tput rc
  stty "${saved_tty_settings}"

  # apply return code
  if [ "${answer}" = "y" ]
  then
    return 0
  else
    return 1
  fi  
}

## FINAL DECISION:
if multipart || ! word-attach || header-override || interactive-ask; then
    "$@" < "$TMPFILE"
    EXIT_STATUS=$?
else
  EXIT_STATUS=1
fi

## Delete the temporary file.
rm -f "$TMPFILE"

## That's all folks.
exit $EXIT_STATUS