mblaze/mcom
2021-04-21 15:10:28 +02:00

555 lines
11 KiB
Bash
Executable File

#!/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/>[^<]*</>\
</g' | uniq | sed 's/^/ /'
}
msgid() {
mgenmid 2>/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")
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/:")
if [ -z "$outbox" ]; 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 "$outbox")"
if [ -z "$draft" ]; then
printf '%s\n' "$0: failed to create draft in outbox $outbox." 1>&2
exit 1
fi
elif [ -z "$draft" ]; then
draft=$(mlist -D "$outbox" | 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
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 -h from "$f")" $SEP
DISPLAY= mshow -n -N "$f" </dev/null |
sed 's/^-/- &/' # RFC 934
printf '\n%s %s %s\n\n' \
$SEP 'End forwarded message' $SEP
done
) fi
;;
*mbnc*)
set -- $(mseq -- "$@")
if [ "$#" -ne 1 ]; then
printf 'mbnc: needs exactly one mail to bounce\n' 1>&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 -- "$@")
)
;;
*mrep*)
set -- $(mseq -- "$@")
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 -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 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 [ "$outbox" ]; then
mv "$draftmime" "$draft"
mflag -d -S "$draft"
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 [ "$outbox" ]; then
mflag -d -S "$draft"
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
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