#! /bin/sh
#
# mhmail -- simple mail program
#
# This code is Copyright (c) 2012, by the authors of nmh.  See the
# COPYRIGHT file in the root directory of the nmh distribution for
# complete copyright information.
#
# Emulation of compiled mhmail(1), with these differences:
# * Adds -send/-nosend, -headerfield, and -attach options.
# * Adds optional -to switch for recipient addresses.
# * Supports all post(8) (by default, without -profile) or send(1)
#   (with -profile) options.
# * Optionally (with -profile) obeys the users profile, including
#   AliasFile and send entries.
# * Instead of silently not sending an empty message, notifies user
#   "mhmail: empty message not sent, use -body '' to force."
# * The compiled mhmail dropped a trailing newline from the -body argument.
# * Drops support for undocumented -queue option.

usage='Usage: mhmail [-t(o)] addrs ... [switches]
  switches are:
  -at(tach) file [-at(tach) file] ...
  -b(ody) text
  -c(c) addrs ...
  -f(rom) addr
  -hea(derfield) name:value [-hea(derfield) name:value] ...
  -su(bject) text
  -r(esent)
  -pr(ofile)
  -se(nd)
  -nose(nd)
  -v(ersion)
  -hel(p)
  and all post(8)/send(1) switches
  mhmail with no arguments is equivalent to inc'


#### Find location of a program.  Bourne shell just puts the name in
#### $0 if it's found from the PATH, so search that if necessary.
finddir() {
  case $1 in
    */*) dirname "$1" ;;
    *  ) IFS=:
         for d in $PATH; do
           [ -f "${d:=.}/$1"  -a  -x "$d/$1" ]  &&  printf %s "$d"  &&  break
         done ;;
  esac
}

help() {
  printf '%s\n' "$usage"
  #### Print the nmh intro text.
  ${nmhbindir}/mhparam -help | sed -n -e '/^$/,$p'
  exit
}

#### Convenience function to allow concise code below.
die() {
    [ $# -gt 0 ]  &&  printf '%s\n' "$1" >&2
    exit 1
}


bindir=`finddir $0`
nmhbindir=`cd "$bindir" && pwd`
nmhlibexecdir=`$nmhbindir/mhparam libexecdir`
case `printf 'OK\n' | tail -n 1 2>&1` in
  OK) tail='tail -n ' ;;
  *)  tail='tail -' ;;
esac


#### Checks for missing mandatory arguments.
checkforargs() {
  if [ $attacharg -eq 1 ]; then
    die 'mhmail: missing argument to -attach'
  elif [ $bodyarg -eq 1 ]; then
    die 'mhmail: missing argument to -body'
  elif [ $ccarg -eq 1  -a  "$cclist"x = x ]; then
    die 'mhmail: missing argument to -cc'
  elif [ $fromarg -eq 1 ]; then
    die 'mhmail: missing argument to -from'
  elif [ $headerfieldarg -eq 1 ]; then
    die 'mhmail: missing argument to -headerfield'
  elif [ $subjectarg -eq 1 ]; then
    die 'mhmail: missing argument to -subject'
  elif [ $toarg -eq 1 ]; then
    die 'mhmail: missing argument to -to'
  fi
}

if [ $# -eq 0 ]; then
  #### Emulate mhmail for reading mail.
  exec "$nmhbindir"/inc
fi

#### Go through all the switches so we can build the draft.
tolist=                     ## To: addresses
toarg=0                     ## whether currently handling -to
attacharg=0                 ## whether currently handling -attach
attachind=Attach            ## attachment indicator
body=                       ## contents of the message body
bodyarg=0                   ## whether currently handling -body
cclist=                     ## Cc: addresses
ccarg=0                     ## whether currently handling -cc
from=                       ## From: contents
fromarg=0                   ## whether currently handling -from
headerfieldlist=            ## header fields to be added to draft
headerfieldarg=0            ## whether currently handling -headerfield
mhmailswitch=0              ## whether currently handling any mhmail switch
subject=                    ## Subject: contents
subjectarg=0                ## whether currently handling -subject
resent=0                    ## whether resending
postsendargs=               ## switches to pass on to post or send
post_send_switch_arg=0      ## whether currently handling a post/send switch
use_send=0                  ## use post (default) or send (-profile)
sendsw=1                    ## to send (default) or not to send
for arg in "$@"; do
  case $arg in
    #### Post and send won't accept -f -or -s because they'd be
    #### ambiguous, so no conflicts with them.  And they don't have
    #### -b, -c, -r, -t.  For the new switches that compiled mhmail
    #### didn't have:  let -p indicate mhmail -profile, not send
    #### -port.  -send masks the send(1) -send switch.  -attach
    #### masks the send(1) -attach switch.
    -at|-att|-atta|-attac|-attach)
       mhmailswitch=1
       attacharg=1
       use_send=1
       ;;
    -b|-bo|-bod|-body) mhmailswitch=1; bodyarg=1 ;;
    -c|-cc) mhmailswitch=1; ccarg=1 ;;
    -f|-fr|-fro|-from) mhmailswitch=1; fromarg=1 ;;
    -hea|-head|-heade|-header|-headerf|-headerfi|-headerfie|-headerfiel|\
-headerfield) mhmailswitch=1; headerfieldarg=1 ;;
    -hel|-help) help ;;
    -nose|-nosen|-nosend) mhmailswitch=1; sendsw=0 ;;
    -p|-pr|-pro|-prof|-profi|-profil|-profile) mhmailswitch=1; use_send=1 ;;
    -resend) die 'mhmail: did you mean -resent instead of -resend?' ;;
    -r|-re|-res|-rese|-resen|-resent) mhmailswitch=1; resent=1 ;;
    -se|-sen|-send) mhmailswitch=1; sendsw=1 ;;
    -su|-sub|-subj|-subje|-subjec|-subject) mhmailswitch=1; subjectarg=1 ;;
    -t|-to) toarg=1; ccarg=0 ;;
    -v|-ve|-ver|-vers|-versi|-versio|-version)
       #### Cheat instead of using autoconf and make to fill in the version.
       "$nmhbindir"/mhpath -v | sed 's/mhpath/mhmail/'; exit ;;
    -*) if [ $mhmailswitch -eq 1 ]; then
          checkforargs
          mhmailswitch=0
        fi
        post_send_switch_arg=1
        postsendargs="${postsendargs:+$postsendargs }$arg" ;;
    *) mhmailswitch=0
       if [ $bodyarg -eq 1 ]; then
         body="$arg
"
         bodyarg=0
         #### Allow -body "" by using just a newline for the body.
         [ "$body"x = x ]  &&  body='
'
       elif [ $fromarg -eq 1 ]; then
         from="$arg"
         fromarg=0
       elif [ $subjectarg -eq 1 ]; then
         subject="$arg"
         subjectarg=0
       elif [ $attacharg -eq 1 ]; then
         headerfieldlist="${headerfieldlist:+$headerfieldlist}$attachind: $arg
"
         attacharg=0
       elif [ $headerfieldarg -eq 1 ]; then
         #### It's not strictly necessary to have one space after
         #### the : that separates the header field name from the
         #### body, but do it to avoid surprising someone.
         #### Solaris sed wants the trailing newline in its input.
         add=`printf '%s\n' "$arg" | sed -e 's/:/: /' -e 's/:  /: /'`
         headerfieldlist="${headerfieldlist:+$headerfieldlist}$add
"
         headerfieldarg=0
       elif [ $post_send_switch_arg -eq 1 ]; then
         postsendargs="${postsendargs:+$postsendargs }$arg"
       elif [ $ccarg -eq 1 ]; then
         #### ccarg can only be reset to 0 by -to.
         cclist="${cclist:+$cclist, }$arg"
       else
         #### An address.
         tolist="${tolist:+$tolist, }$arg"
         toarg=0
       fi ;;
  esac
done

#### Check for at least one address and -from.
if [ "$tolist"x = x ]; then
  die 'Usage: mhmail [-t(o)] addrs ... [switches]'
fi
if [ "$from"x = x ]; then
  from=`${nmhlibexecdir}/ap -format '%(localmbox)' 0`
fi

#### Check for missing mandatory arguments.
checkforargs

#### Build header.
[ $resent -eq 0 ]  &&  prefix=  ||  prefix='Resent-'
header="${prefix}To: $tolist
"
[ "$cclist"x = x ]  ||  header="$header${prefix}Cc: $cclist
"
[ "$subject"x = x ]  ||  header="$header${prefix}Subject: $subject
"
[ "$from"x = x ]  ||  header="$header${prefix}From: $from
"

if [ "$headerfieldlist" ]; then
  header="$header$headerfieldlist"
fi

#### Set up a file to supply as a draft to post/send.  And set a trap
#### to remove it.  send moves the file to a backup and can create a
#### .orig file, so it will remove them, too.
umask 077
tmpdir="${MHTMPDIR:-${TMPDIR:-`$nmhbindir/mhpath +`}}"
tmpfilename=`cd "$tmpdir"  &&  "${nmhlibexecdir}/mkstemp" -p mhmail`
[ $? -ne 0 ]  &&  die "mhmail: failed to create temporary file in $tmpdir"
tmpfile="$tmpdir/$tmpfilename"
backup_char=`"$nmhbindir"/mhparam sbackup`
tmpfilebackup="$tmpdir/${backup_char}${tmpfilename}*"
tmpfileresent=

message_file=
if [ $resent -eq 0 ]; then
  #### Add blank line after header if not resending.
  header="$header
"
  message_file="$tmpfile"
else
  if [ $use_send -eq 0 ]; then
    postsendargs="${postsendargs:+$postsendargs }-dist"
    message_file="$tmpfile"
  else
    #### When resending with send, tmpfile will just contain the
    #### Resent- header fields.  "$tmpfileresent" will contain
    #### the message that is being resent.
    tmpfileresent=`"${nmhlibexecdir}/mkstemp" -d "$tmpdir" -p mhmail-resent`
    [ $? -ne 0 ]  &&  die "mhmail: failed to create temporary file in $tmpdir"
    mhdist=1; export mhdist
    mhaltmsg=$tmpfileresent; export mhaltmsg
    message_file="$tmpfileresent"
    printf '' >"$message_file"  || exit 2
  fi
fi

trap "rm -f '$tmpfile' $tmpfilebackup ${tmpfileresent:+'$tmpfileresent'}" 0

if [ "$body"x = x ]; then
  #### First put message header in the file.
  printf %s "$header" >"$tmpfile" || exit 2

  tmpfile_size_before=`wc -c "$message_file"`
  #### Now grab the body from stdin.  cat >> handles blank lines
  #### better than body=`cat`.
  cat >>"$message_file" || exit 2
  tmpfile_size_after=`wc -c "$message_file"`

  #### Don't allow an empty body (from stdin).  Use string
  #### comparison so we don't have to strip the filename, etc.
  if [ "$tmpfile_size_before" = "$tmpfile_size_after" ]; then
    die 'mhmail: empty message not sent, use -body '"''"' to force.'
  fi

  #### Add trailing newline to body if it doesn't have one.
  if [ `${tail}1 "$message_file" | wc -l` -ne 1 ]; then
    printf '\n' >>"$message_file" || exit 2
  fi
else
  #### Add trailing newline to body if it doesn't have one.
  [ `printf %s "$body" | ${tail}1 | wc -l` -ne 1 ]  &&  body="$body
"

  if [ "$tmpfileresent" ]; then
    #### Put just the new message header in the file.
    printf %s "$header" >"$tmpfile" || exit 2
    #### and the body in the file to resend.
    printf %s "$body" >"$tmpfileresent" || exit 2
  else
    #### Put message header and body in the file.
    printf %s "$header$body" >"$tmpfile" || exit 2
  fi
fi

if [ $sendsw -eq 0 ]; then
  cat "$tmpfile"
else
  if [ $use_send -eq 0 ]; then
    post_or_send=`$nmhbindir/mhparam postproc`
  else
    post_or_send="$nmhbindir/send"
  fi

  if "$post_or_send" $postsendargs "$tmpfile"; then
    exit
  else
    status=$?
    mv -f "$tmpfile" dead.letter
    printf 'Letter saved in dead.letter\n' >&2
    exit $status
  fi
fi
