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