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