#!/bin/sh # mcom [TO] - compose mail NL=' ' commajoin() { awk 'NR==1 {l=$0; next} {l=l", "$0} END {print l}' } notmine() { mine="$(maddr -a -h local-mailbox:alternate-mailboxes: "$MBLAZE/profile")" grep -Fv -e "$mine" } replyfrom() { addrs="$(maddr -a -h reply-from: "$MBLAZE/profile")" [ -z "$addrs" ] && addrs="$(maddr -a -h alternate-mailboxes: "$MBLAZE/profile")" grep -F -e "$addrs" } ouniq() { awk '!seen[$0]++' } reffmt() { sed 's/^[^<]*//g;s/[^>]*$//g;s/>[^<]*\ /dev/null | sed 's/^/Message-Id: /' } stampdate() { if ! mhdr -h date "$1" >/dev/null; then tmp=$(mktemp -t mcom.XXXXXX) { printf 'Date: ' mdate cat "$1" } >"$tmp" && mv "$tmp" "$1" fi } checksensible() { awk ' /^$/ { seenheader=1 exit } !(/^[^ \t][^ \t]*[ \t]*:/ || /^[ \t]/) { bad=1 print "invalid header line: "$0 >"/dev/stderr" } END { if (!seenheader) { print "warning: message does not contain an empty line between headers and body." >"/dev/stderr" exit 1 } if (bad) exit 1 } ' "$1" } stripempty() { tmp=$(mktemp -t mcom.XXXXXX) msed 's/^[ \t]*$//d' "$1" >"$tmp" mv "$tmp" "$1" } needs_multipart() { mhdr -h attach "$1" >/dev/null || grep -qE '^#[a-zA-Z]+/[a-zA-Z0-9+.;=#-]+ ' "$1" } do_mime() { if needs_multipart "$draft"; then ( IFS=$NL msed '/attach/d' "$draft" for f in $(mhdr -M -h attach "$draft"); do printf '#%s %s\n' \ "$(file -Lb --mime "$f" | sed 's/ //g')" \ "$f" done ) | mmime >"$draftmime" else mmime -r <"$draft" >"$draftmime" fi if [ $? -ne 0 ]; then printf 'mcom: fix above errors before continuing\n' 1>&2 rm -f "$draftmime" fi } MBLAZE=${MBLAZE:-$HOME/.mblaze} sendmail=$(mhdr -h sendmail "$MBLAZE/profile") sendmail_args=$(mhdr -h sendmail-args "$MBLAZE/profile") sendmail="${sendmail:-sendmail} ${sendmail_args:--t}" default_from=$(mhdr -h local-mailbox "$MBLAZE/profile") senthook=$(mhdr -h senthook "$MBLAZE/profile") defaultc=e hdrs= resume= noquote= case "$0" in *mcom*) hdr=to while [ $# -gt 0 ]; do case "$1" in --) # -- is like -to, really shift hdr=to ;; -r) shift resume=1 if [ "$#" -gt 0 ]; then case "$1" in /*|./*) draft="$1";; *) draft="./$1";; esac if ! [ -f "$draft" ]; then printf 'mcom: no such draft %s\n' \ "$draft" 1>&2 exit 1 fi echo "using draft $draft" shift fi ;; -send) defaultc=justsend shift;; -??*) hdr=${1#-} shift;; [!-]*) hdrs="$hdrs$NL$(printf '%s: %s\n' "${hdr}" "$1")" shift;; *) printf 'mcom: invalid argument %s\n' "$1" 1>&2 exit 1;; esac done ;; *mfwd*) hdr= raw= while [ $# -gt 0 ]; do case "$1" in --) shift break;; -r) shift raw=1;; -send) defaultc=justsend shift;; -??*) hdr=${1#-} shift;; [!-]*) [ -z "$hdr" ] && break hdrs="$hdrs$NL$(printf '%s: %s\n' "${hdr}" "$1")" shift;; *) printf 'mfwd: invalid argument %s\n' "$1" 1>&2 exit 1;; esac done [ "$#" -eq 0 ] && set -- . ;; *mbnc*) hdr= while [ $# -gt 0 ]; do case "$1" in --) shift break;; -send) defaultc=justsend shift;; -??*) hdr=${1#-} shift;; [!-]*) [ -z "$hdr" ] && break hdrs="$hdrs$NL$(printf '%s: %s\n' "${hdr}" "$1")" shift;; *) printf 'mbnc: invalid argument %s\n' "$1" 1>&2 exit 1;; esac done [ "$#" -eq 0 ] && set -- . ;; *mrep*) hdr= while [ $# -gt 0 ]; do case "$1" in --) shift break;; -send) defaultc=justsend shift;; -noquote) noquote=1 shift;; -??*) hdr=${1#-} shift;; [!-]*) [ -z "$hdr" ] && break hdrs="$hdrs$NL$(printf '%s: %s\n' "${hdr}" "$1")" shift;; *) printf 'mrep: invalid argument %s\n' "$1" 1>&2 exit 1;; esac done [ "$#" -eq 0 ] && set -- . ;; esac hdrs="$(printf '%s\n' "${hdrs#$NL}" | mhdr -)" outbox=$(mhdr -h outbox "$MBLAZE/profile" | sed "s:^~/:$HOME/:") draftbox=$(mhdr -h drafts "$MBLAZE/profile" | sed "s:^~/:$HOME/:") draftbox="${draftbox:-$outbox}" if [ -z "$draftbox" ]; then if [ -z "$resume" ]; then i=0 while [ -f "snd.$i" ]; do i=$((i+1)) done draft="./snd.$i" elif [ -z "$draft" ]; then draft=$(ls -1t ./snd.*[0-9] | sed 1q) fi draftmime="$draft.mime" else if [ -z "$resume" ]; then draft="$(true | mdeliver -v -c -XD "$draftbox")" if [ -z "$draft" ]; then printf '%s\n' "$0: failed to create draft in outbox $draftbox." 1>&2 exit 1 fi elif [ -z "$draft" ]; then draft=$(mlist -D "$draftbox" | msort -r -M | sed 1q) fi draftmime="$(printf '%s\n' "$draft" | sed 's,\(.*\)/cur/,\1/tmp/mime-,')" fi if [ -n "$resume" ] && [ -z "$draft" ]; then echo "mcom: no draft found" exit 1 fi [ -z "$resume" ] && { case "$0" in *mcom*) { printf '%s' "$hdrs" | mhdr -M -h to - | commajoin | sed 's/^/To: /' printf '%s' "$hdrs" | mhdr -M -h cc - | commajoin | sed 's/^/Cc: /' printf '%s' "$hdrs" | mhdr -M -h bcc - | commajoin | sed 's/^/Bcc: /' printf '%s\n' "$hdrs" | awk '{ print }' | msed "/to/d; /cc/d; /bcc/d; /body/d" - } | msed "/cc/a//; /bcc/a//; /subject/a//; /from/a/$default_from/" - | sed '/^$/d' msgid museragent cat "$MBLAZE/headers" 2>/dev/null printf '\n' ( IFS=$NL cat -- /dev/null $(printf '%s' "$hdrs" | mhdr -M -h body -) ) printf '\n' ;; *mfwd*) { printf '%s' "$hdrs" | mhdr -M -h to - | commajoin | sed 's/^/To: /' printf '%s' "$hdrs" | mhdr -M -h cc - | commajoin | sed 's/^/Cc: /' printf '%s' "$hdrs" | mhdr -M -h bcc - | commajoin | sed 's/^/Bcc: /' COLUMNS=10000 mscan -f 'Subject: Fwd: [%f] %s' "$@" 2>/dev/null | sed 1q printf '%s\n' "$hdrs" | awk '{ print }' | msed "/to/d; /cc/d; /bcc/d" - } | msed "/cc/a//; /bcc/a//; /from/a/$default_from/" - | sed '/^$/d' msgid museragent cat "$MBLAZE/headers" 2>/dev/null if [ -n "$raw" ]; then prev=$(pwd) attachdir=$(mktemp -d) cd $attachdir trap 'rm -r "$attachdir"' EXIT mseq -r "$@" \ | xargs -I {} mshow -Bx {} \ | xargs -I {} realpath {} \ | sed '/^$/d; s/^/Attach: /' cd $prev fi printf '\n\n' if [ -z "$raw" ]; then mseq -r "$@" | sed 's:^:#message/rfc822#inline :; s:$:>:' else ( SEP=----- IFS=$NL for f in $(mseq -r "$@"); do printf '%s Forwarded message from %s %s\n\n' \ $SEP "$(mhdr -d -A -h from "$f")" $SEP DISPLAY= mshow -n -N "$f" &2 exit 1 fi { printf '%s' "$hdrs" | mhdr -M -h resent-to - | commajoin | sed 's/^/Resent-To: /' printf '%s' "$hdrs" | mhdr -M -h resent-cc - | commajoin | sed 's/^/Resent-Cc: /' printf '%s\n' "$hdrs" | awk '{ print }' | msed "/resent-to/d; /resent-cc/d" - } | msed "/resent-to/a//; /resent-from/a/$default_from/" - | sed '/^$/d' msgid | sed 's/^/Resent-/' printf 'Resent-Date: %s\n' "$(mdate)" ( IFS=$NL cat $(mseq -r -- "$@") ) ;; *mrep*) old_ifs="$IFS" IFS=$NL set -- $(mseq -r -- "$@") IFS="$old_ifs" if [ "$#" -ne 1 ]; then printf 'mrep: needs exactly one mail to reply to\n' 1>&2 exit 1 fi ng=$(mhdr -h newsgroups "$1") if [ "$ng" ]; then printf 'Newsgroups: %s\n' "$ng" else to=$(mhdr -d -h reply-to "$1") [ -z "$to" ] && to=$(mhdr -d -A -h from "$1") printf 'To: %s\n' "$to" printf 'Cc: %s\n' \ "$(mhdr -d -A -h to:cc: "$1" | notmine |grep -Fv -e "$to" | ouniq |commajoin)" printf 'Bcc: \n' printf '%s\n' "$hdrs" | awk '{ print }' | msed "/body/d" - fi | sed '/^$/d' printf 'Subject: Re: %s\n' "$(COLUMNS=10000 mscan -f '%S' "$1")" if ! printf '%s\n' "$hdrs" | awk '{ print }' | mhdr -h from: - >/dev/null; then addr=$(maddr -a -h delivered-to:to:cc:bcc: "$1" | replyfrom | head -n1) [ -n "$addr" ] && from=$(maddr -h reply-from "$MBLAZE/profile" | grep -Fi "<$addr>" | head -n1) [ -n "$addr" ] && [ -z "$from" ] && from=$(maddr -h alternate-mailboxes "$MBLAZE/profile" | grep -Fi "<$addr>" | head -n1) [ -z "$from" ] && from=$(mhdr -h local-mailbox "$MBLAZE/profile") [ -n "$from" ] && printf 'From: %s\n' "$from" fi mid=$(mhdr -h message-id "$1") if [ "$mid" ]; then printf 'References:' { mhdr -h references "$1" printf '%s\n' "$mid" } | reffmt printf 'In-Reply-To: %s\n' "$mid" fi msgid museragent cat "$MBLAZE/headers" 2>/dev/null printf '\n' if [ -z "$noquote" ]; then mquote "$1" printf '\n' fi ( IFS=$NL cat -- /dev/null $(printf '%s' "$hdrs" | mhdr -M -h body -) ) printf '\n' ;; esac case "$0" in *mbnc*) ;; *) if [ -f "$MBLAZE/signature" ]; then SIGNATURE="$MBLAZE/signature" elif [ -f ~/.signature ]; then SIGNATURE="$HOME/.signature" fi if [ -n "$SIGNATURE" ]; then printf '%s\n' '-- ' cat "$SIGNATURE" fi esac } >"$draft" automime= c=$defaultc while :; do case "$c" in s|send) case "$(mhdr -h newsgroups "$draft")" in *gmane.*) sendmail="mblow -s news.gmane.org";; *.*) sendmail="mblow";; esac resent="$(maddr -h resent-to "$draft")" case "$resent" in ?*) sendmail=$(mhdr -h sendmail "$MBLAZE/profile") sendmail="${sendmail:-sendmail} -- $resent" ;; esac if [ -e "$draftmime" ]; then if [ "$draft" -ot "$draftmime" ] || [ "$automime" = 1 ]; then stampdate "$draftmime" if $sendmail <"$draftmime"; then if [ "$senthook" ]; then mv "$draftmime" "$draft" $senthook "$draft" elif [ "$outbox" ]; then mv "$draftmime" "$draft" mrefile "$(mflag -d -S "$draft")" "$outbox" else rm "$draft" "$draftmime" fi else printf '%s\n' "mcom: $sendmail failed, kept draft $draft" exit 2 fi else printf 'mcom: re-run mmime first.\n' c= continue fi else if mmime -c <"$draft" && ! [ "$automime" = 1 ]; then stampdate "$draft" if $sendmail <"$draft"; then if [ "$senthook" ]; then $senthook "$draft" elif [ "$outbox" ]; then mrefile "$(mflag -d -S "$draft")" "$outbox" else rm "$draft" fi else printf '%s\n' "mcom: $sendmail failed, kept draft $draft" exit 2 fi else printf '%s\n' "mcom: message needs to be MIME-encoded first." c= continue fi fi case "$0" in *mrep*) mflag -R -- "$1" ;; *mbnc*) mflag -P -- "$1" ;; *mfwd*) mflag -P -- "$@" ;; esac case "$0" in *mrep*|*mbnc*|*mfwd*) mseq -f | mseq -S ;; esac exit 0 ;; c|cancel) stampdate "$draft" printf '%s\n' "mcom: cancelled draft $draft" exit 1 ;; m|mime) do_mime mshow -t "$draftmime" c= ;; e|edit) c= if ! ${VISUAL:-${EDITOR:-vi}} "$draft"; then c=d else if checksensible "$draft"; then stripempty "$draft" if mmime -c <"$draft" && ! needs_multipart "$draft"; then automime= else automime=1 do_mime fi else printf '\n' fi fi ;; justsend) stripempty "$draft" if mmime -c <"$draft" && ! needs_multipart "$draft"; then automime= else automime=1 do_mime fi c=send ;; d|delete) rm -i "$draft" if ! [ -f "$draft" ]; then rm -f "$draftmime" printf '%s\n' "mcom: deleted draft $draft" exit 0 fi c= ;; sign) msign "$draft" >"$draftmime" mshow -t "$draftmime" c= ;; encrypt) mencrypt "$draft" >"$draftmime" mshow -t "$draftmime" c= ;; p|preview|show) if [ -e "$draftmime" ]; then mshow "$draftmime" else mshow "$draft" fi c= ;; *) printf 'What now? (%s[s]end, [c]ancel, [d]elete, [e]dit, [m]ime, [p]review, sign, encrypt) ' "${automime:+mime and }" read -r c ;; esac done