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