1#!/bin/sh
2# Based on mhsign 1.1.0.9 2007/05/30 14:48:40 by Neil Rickert
3# Adjusted to mmh by markus schnalke <meillo@marmaro.de>, 2012-07
4
5
6# mhsign:
7#   -encrypt:  Encrypt to recipients of message. This implies signing.
8#   -mime:     Use MIME pgp standard.  For signature, trailing blanks
9#              will be removed and any "From " line will be indented for
10#              best compatibility. Enforced for multipart messages.
11
12usage="Usage: mhsign [-encrypt] [-mime] [-Version] [-help] file"
13
14# defaults
15usemime=n
16function=sign
17
18
19# find out the signing key
20userid="$MMHPGPKEY"
21if [ -z "$userid" ] ; then
22	userid="`mhparam pgpkey`"
23fi
24if [ -z "$userid" ] ; then
25	userid="`gpg --list-secret-keys --with-colons --fixed-list-mode \
26				2>/dev/null |
27			grep '^sec' | sort -t: -k3,3nr -k 6,6nr |
28			awk -F: '
29				$7=="" || $7 > "'"\`date +%s\`"'" {
30					print $5; exit;
31				}
32			'`"
33fi
34if [ -z "$userid" ] ; then
35	echo "No secret key found" >&2
36	exit 1
37fi
38
39# find out the file of recipient key exceptions (for encrypt only)
40keyfile="${MMH:-$HOME/.mmh}/pgpkeys"
41if [ ! -r "$keyfile" ] ; then
42	keyfile="${GNUPGHOME:-$HOME/.gnupg}/pgpkeys"
43	if [ ! -r "$keyfile" ] ; then
44		keyfile=/dev/null
45	fi
46fi
47
48# prepend the default options from the profile
49set -- `mhparam -nocomp ${0##*/}` "$@"
50
51while : ; do
52	case "$1" in
53	-e*)
54		function=encrypt
55		;;
56	-m*)
57		usemime=y
58		;;
59	-V*)
60		echo "mhsign has no own version number, thus this instead:"
61		folder -Version
62		exit 0
63		;;
64	-h*|-*)
65		echo "$usage" >&2
66		exit 1
67		;;
68	*)
69		break
70	esac
71	shift
72done
73
74if [ $# -ne 1 ] ; then
75	echo "$usage" >&2
76	exit 1
77fi
78
79TEMP=/tmp/${0##*/}.$$
80umask 077
81mkdir $TEMP || exit 1
82trap "rm -rf $TEMP" 0 1 2 15
83
84### lookupkeyfile address -- lookup one address in our database
85lookupkeyfile() {
86	key=`grep -i "^[^#].*[ 	]$1\$" "$keyfile" 2>/dev/null`
87	if [ $? != 0 ] ; then
88		return 1
89	fi
90	echo "$key" | sed 's/[ 	].*//;q'
91	return 0
92}
93
94### lookupkeyring address -- lookup one address in keyring
95lookupkeyring() {
96	key=`gpg --list-keys --with-colons "<$1>" 2>/dev/null`
97	if [ $? != 0 ] ; then
98		return 1
99	fi
100	echo "$key" | sed -n '/^pub:[^idre]:/{p;q;}' | cut -d: -f5
101	return 0
102}
103
104### Do a best guess at FQDN
105mh_hostname()
106{
107	hostname -f 2>/dev/null || uname -n
108}
109
110### lookupkeys file -- set $KL to list of recipient keys
111lookupkeys() {
112	KL=
113	status=0
114	if whom -ali -notocc -bcc "$1" >/dev/null ; then
115		echo "Encryption is not supported for BCCs" >&2
116		return 1
117	fi
118
119	# extract the actual address
120	format='%<{error}%{error}: %{text}%|%(addr{text})%>'
121	addresses=`whom -ali -tocc -nobcc "$1" |sed 's_$_,_'`
122	addresses=`%libdir%/ap -form "=$format" "$addresses"`
123
124	for i in $addresses ; do
125		case "$i" in
126		'|'*)	echo "Ignoring pipe address" >&2
127			continue ;;
128		*@*)	;;
129		*)	i="$i@`mh_hostname`" ;;
130		esac
131		if k=`lookupkeyfile "$i"` ; then
132			KL="$KL $k"
133		elif k=`lookupkeyring "$i"` ; then
134			KL="$KL $k"
135		else
136			echo "Could not find key for <$i>" >&2
137			status=1
138		fi
139	done
140	return $status
141}
142
143### getheader headername msgfile
144getheader() {
145	HDR=`sed -n -e '/^-*$/q' -e 's/^\([^ 	:]*\):.*/\1/p' $2 |
146		grep -i '^'"$1"'$' | head -1`
147	if [ "$HDR" = "" ] ; then return 1 ; fi
148	sed -n -e ':a
149		/^-*$/q
150		/^'"$HDR"':/b x
151		d
152		b a
153		:x
154		p
155		n
156		/^[ 	]/b x
157		b a' $2
158		return 0
159}
160
161### headbody msgfile # separate msgfile into $TEMP/head $TEMP/body
162headbody() {
163	sed -n '1,/^\-*$/p' "$1" > $TEMP/head
164	sed '1,/^-*$/d' "$1" > $TEMP/body
165}
166
167### fixheaders -- remove Content headers, add newheaders
168fixheaders() {
169	sed -n ':a
170		/^-*$/q
171		/^[Cc][Oo][Nn][Tt][Ee][Nn][Tt]-/b r
172		p
173		n
174		b a
175		:r
176		n
177		/^[ 	]/b r
178		b a' $TEMP/head
179	cat $TEMP/newheaders
180	grep "^-" $TEMP/head || echo ""
181}
182
183### newboundary -- output a suitable boundary marker
184newboundary() {
185	b=$$_`date|sed 's/[ :	]/_/g'`
186	for i in 0 x '=' _ + , Z 9 4 ; do
187		if grep "^--$b" $TEMP/body >/dev/null 2>&1 ; then
188			## oops, bad boundary -- try again
189			b=`echo $i$b | tr \
190'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456780=+,_' \
191'3Ba+c98bdACmzXpqR,tTuMDSs_hLkwZ0ef7PQrW=2x5l6E14ZKivIVgOjoJnGNUyHF'`
192		else
193			echo "$b"
194			return 0
195		fi
196	done
197	echo "Failed to generate unique mime boundary" >&2
198	exit 1
199}
200
201### detachsign -- sign $TEMP/body, output in $TEMP/body.asc
202detachsign() {
203	gpg -u "$userid" --armor --textmode --detach-sign \
204			<$TEMP/body >$TEMP/body.asc
205}
206
207### sign --- inline signature for $TEMP/body, output in $TEMP/body.asc
208sign() {
209	gpg -u "$userid" --armor --textmode --clearsign \
210		<$TEMP/body >$TEMP/body.asc
211}
212
213### encrypt recipients -- encrypt $TEMP/body to recipients
214encrypt() {
215	R=
216	for i in $KL ; do
217		R="$R -r $i"
218	done
219	gpg --no-encrypt-to -u "$userid" --armor --textmode \
220			--always-trust --output $TEMP/body.asc \
221			-r "$userid" $R --sign --encrypt $TEMP/body
222}
223
224### Mainline processing
225
226FILE="$1"	## we assume a disk file
227if [ ! -r "$FILE" ] ; then
228	echo "cannot read $FILE" >&2
229	exit 1
230fi
231
232case "$function" in
233encrypt)
234	lookupkeys "$FILE" || exit 1
235esac
236
237cp "$FILE" "$FILE.orig"
238outfile="$FILE"
239headbody "$FILE"
240
241CT=""
242if grep -i "^mime-version:" $TEMP/head >/dev/null 2>&1 ; then
243	>$TEMP/newheaders
244	if CT=`getheader content-type $TEMP/head` ; then
245		echo "$CT" >$TEMP/newbody
246		if grep -i multipart $TEMP/newbody >/dev/null 2>&1 ; then
247			usemime=y  # Force MIME if already multi-part
248		fi
249		getheader content-transfer-encoding $TEMP/head \
250				>>$TEMP/newbody || :
251	else
252		CT=""
253	fi
254else
255	echo "Mime-Version: 1.0" >$TEMP/newheaders
256fi
257
258if [ "$usemime" = n ] ; then
259	### non-MIME ###
260	case "$function" in
261	sign)
262		sign || exit 1 ;;
263	encrypt)
264		encrypt || exit 1 ;;
265	esac
266	cat $TEMP/head $TEMP/body.asc >$outfile || exit 1
267	exit 0
268fi
269
270### MIME ###
271
272BDRY="`newboundary`"
273
274if [ "$CT" = "" ] ; then
275	echo "Content-Type: text/plain; charset=us-ascii" >$TEMP/newbody
276fi
277echo >>$TEMP/newbody
278
279case $function in
280sign)
281	sed 's/^From / &/; s/[
282	]*$//' $TEMP/body >>$TEMP/newbody
283	if grep "^From " $TEMP/body >/dev/null 2>&1 ; then
284		echo 'Warning: "From " lines in message body have been indented' >&2
285	fi
286	if grep "[
287	]$" $TEMP/body >/dev/null 2>&1 ; then
288		echo 'Warning: trailing blanks removed from message body' >&2
289	fi
290	echo 'Content-Type: multipart/signed; protocol="application/pgp-signature";' >>$TEMP/newheaders
291	echo "	micalg=pgp-sha1"'; boundary="'"$BDRY"'"' >>$TEMP/newheaders
292
293	sed -e 's/$/
294/' "$TEMP/newbody" >"$TEMP/body"
295	detachsign || exit 1
296	(
297		echo "--$BDRY"
298		cat $TEMP/newbody
299		echo
300		echo "--$BDRY"
301		echo "Content-Type: application/pgp-signature"
302		echo
303		cat $TEMP/body.asc
304		echo
305		echo "--$BDRY--"
306		echo
307	) >$TEMP/body
308	;;
309
310encrypt)
311	cat $TEMP/body >>$TEMP/newbody
312	echo 'Content-Type: multipart/encrypted; protocol="application/pgp-encrypted";' >>$TEMP/newheaders
313	echo "	boundary=\"$BDRY\"" >> $TEMP/newheaders
314
315	mv $TEMP/newbody $TEMP/body || exit 1
316	encrypt || exit 1
317	(
318		echo "--$BDRY"
319		echo "Content-Type: application/pgp-encrypted"
320		echo
321		echo "Version: 1"
322		echo
323		echo "--$BDRY"
324		echo "Content-Type: application/octet-stream"
325		echo
326		cat $TEMP/body.asc
327		echo
328		echo "--$BDRY--"
329		echo
330	) >"$TEMP/body"
331	;;
332esac
333
334fixheaders | cat - $TEMP/body >"$outfile"
335