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