#! /bin/sh -
## $Id: mhsign,v 1.1.0.9 2007/05/30 14:48:40 rickert Exp $

local_domain=cs.niu.edu
# rsakey=
# dsskey=
## allow preferred keys to be defined in environment.

backup="#"
keytype=d

### file to keep recipient key info for usual recipients (encrypt only)
keyfile=${GNUPGHOME:-$HOME/.gnupg}/.pgpkeys
if [ ! -r $keyfile ] ; then
	keyfile=`mhpath +`/.pgpkeys
fi
if [ ! -r $keyfile ] ; then
	keyfile=/dev/null
fi

if [ "$rsakey" = "" ] ; then
	rsakey=`sed -n	-e '/^[# 	]/d' \
			-e '/[ 	]my_rsakey$/s/[ 	].*//p' $keyfile`
fi
if [ "$dsskey" = "" ] ; then
	dsskey=`sed -n	-e '/^[# 	]/d' \
			-e '/[ 	]my_dsskey$/s/[ 	].*//p' $keyfile`
fi

### MHSIGN
# -r sign with RSA key
# -d sign with DSS key
# -R force pgp2 compatibility -- for encryption, will fail unless all
#    recipients are RSA
# -e encrypt to recipients of message
# -m use MIME pgp standard.  For sign, trailing blanks will be removed
#    and any "From " line will be indented for best compatibility.
# -b don't strip trailing blanks or indent "From ".

USAGE="mhsign [-rRdemb] messagefile"
## typical use from whatnow prompt: edit mhsign -d

# defaults
mimefixbody=y
usemime=
function=sign

while getopts rdRemb op
do
  case "$op" in
   d|r|R) keytype=$op ;;
   m)	usemime=y ;;
   b)	mimefixbody= ;;
   e)	function=encrypt ;;
   *)	echo "$USAGE" >&2
	exit 1 ;;
  esac
done

shift `expr $OPTIND - 1 || :`   ## sigh! expr gives status 1 if answer=0

## sanity check

if [ "$rsakey" = "" ] ; then keytype=d ; fi
if [ "$keytype" = d -a "$dsskey" = "" ] ; then keytype=r ; fi

case "$keytype" in
 d)	userid=$dsskey
	pgp2opts=
	micalg=sha1 ;;
 r)	userid=$rsakey
	pgp2opts=
	micalg=sha1 ;;
 R)	userid=$rsakey
	pgp2opts="--pgp2"
	micalg=md5 ;;
esac

if [ "$userid" = "" ] ;  then
	echo "No user signing key defined" >&2
	exit 1
fi

TEMP=/tmp/mhsign.$$
umask 077
mkdir $TEMP || exit 1
trap "rm -rf $TEMP" 0 1 2 15

	### reciplist -- print current recipient list
	reciplist(){
		if whom > $TEMP/whom 2>/dev/null ; then
			sed -e '/--.*ecipient.*--/d' \
				-e 's=\[BCC\].*==' \
				-e 's/^[ 	][ 	]*//' \
				-e 's/[ 	][ 	]*$//' \
				-e '/^$/d' \
				-e "s/$/@$local_domain/" \
				-e 's/  *at  */@/' \
				-e "s/@\(.*\)@$local_domain/@\1/" $TEMP/whom
			return
		else
			echo 'Cmd only valid if called from "whatnow"' >&2
			exit 1
		fi
	}

	### lookupkeyfile address -- lookup one address in our database
	lookupkeyfile(){
		key=`grep -i "^[^# 	].*[ 	]$1" $keyfile 2>/dev/null`
		if [ $? = 0 ] ; then
			echo "$key" | sed -e 's/[ 	].*//' | head -1
			return 0
		else
			return 1
		fi
	}

	### lookupkeyring address -- lookup one address in keyring
	lookupkeyring(){
		key=`gpg --list-keys "$1" 2>/dev/null`
		if [ $? = 0 ] ; then
			echo "$key" | sed -n -e "/^pub/s=^[^/]*/\([^ 	]*\).*=0x\1=p" |
				head -1
				return 0
		else
			return 1
		fi
	}

	### lookupkeys -- set $KL to list of recipient keys
	lookupkeys(){
		KL=
		status=0
		for i in `reciplist`
		do
			if k=`lookupkeyfile $i` ; then
				KL="$KL $k"
			elif k=`lookupkeyring $i` ; then
				KL="$KL $k"
			else
				echo "Could not find key for $i" >&2
				status=1
			fi
		done
		return $status
	}

	### getheader headername msgfile
	getheader(){
		HDR=`sed -n -e '/^-*$/q' -e 's/^\([^ 	:]*\):.*/\1/p' $2 |
			grep -i '^'"$1"'$' | head -1`
		if [ "$HDR" = "" ] ; then return 1 ; fi
		sed -n -e ':a
			/^-*$/q
		 	/^'"$HDR"':/b x
			d
			b a
			:x
			p
			n
			/^[ 	]/b x
			b a' $2
			return 0
	}

	### headbody msgfile # separate msgfile into $TEMP/head $TEMP/body
	headbody(){
		sed -n '1,/^\-*$/p' "$1" > $TEMP/head
		sed '1,/^-*$/d' "$1" > $TEMP/body
	}

	### fixheaders -- remove Content headers, add newheaders
	fixheaders(){
		sed -n ':a
			/^-*$/q
			/^[Cc][Oo][Nn][Tt][Ee][Nn][Tt]-/b r
			p
			n
			b a
			:r
			n
			/^[ 	]/b r
			b a' $TEMP/head
		cat $TEMP/newheaders
		grep "^-" $TEMP/head || echo ""
	}

	### canonfile msgbody  -- set canonical line endings on file
	canonfile(){
		 sed -e 's/$/
/' "$1"
	}

	### newboundary -- output a suitable boundary marker
	newboundary(){
		b=$$_`date|sed 's/[ :	]/_/g'`
		for i in 0 x '=' _ + , Z 9 4
		do
		    if grep "^--$b" $TEMP/body >/dev/null 2>&1
		    then	## oops, bad boundary -- try again
			b=`echo $i$b | tr \
   'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456780=+,_' \
   '3Ba+c98bdACmzXpqR,tTuMDSs_hLkwZ0ef7PQrW=2x5l6E14ZKivIVgOjoJnGNUyHF'`
		    else
			echo "$b"
			return 0
		    fi
		done
		echo "Failed to generate unique mime boundary" >&2
		exit 1
	}
	### detachsign -- sign $TEMP/body, output in $TEMP/body.asc
	detachsign(){
		case "$mimefixbody" in
		 y)
			gpg -u "$userid" --armor $pgp2opts --textmode \
			 --detach-sign < $TEMP/body > $TEMP/body.asc
			;;
		 *)
			gpg -u "$userid" --armor $pgp2opts --no-textmode \
			 --detach-sign < $TEMP/body > $TEMP/body.asc
			;;
		esac
	}

	### sign --- inline signature for $TEMP/body, output in $TEMP/body.asc
	sign(){
		gpg -u "$userid" --armor $pgp2opts --textmode --clearsign \
			< $TEMP/body > $TEMP/body.asc
	}

	### encrypt recipients -- encrypt $TEMP/body to recipients
	encrypt(){
		R=
		for i in $KL
		do
			R="$R -r $i"
		done

		if [ "$keytype" = R ] ; then 	## pgp2 compatible
		 status=1
		 gpg --no-options --no-secmem-warning --pgp2 -b \
			-u "$userid" $TEMP/body
		 gpg --store -z 0 --output $TEMP/body.lit $TEMP/body
		 cat $TEMP/body.sig $TEMP/body.lit |
		  gpg --no-options --no-literal --store \
		   --no-secmem-warning --compress-algo 1 \
		   --output $TEMP/body.z
		 gpg --no-encrypt-to --pgp2 --cipher-algo idea --no-literal \
		  --always-trust --encrypt -r "$userid" $R \
		  -o $TEMP/body.asc --armor $TEMP/body.z
		 status=$?
		else
		 gpg --no-encrypt-to --pgp6 -u "$userid" --armor --textmode \
		  --always-trust --output $TEMP/body.asc -r "$userid" $R \
		  --sign --encrypt $TEMP/body
		 status=$?
		fi
		return $status
	}

### Mainline processing

FILE="$1"	## we assume a disk file
if [ -r "$FILE" ] ; then
	:	## that's fine
else
	echo "cannot read $FILE" >&2
	exit 1
fi

if [ "$function" = encrypt ] ; then
	lookupkeys || exit 1
fi

outfile="$FILE"
bkup=`echo $FILE | sed 's=\([^/]*\)$='"$backup"'\1='`
cp "$FILE" "$bkup" || exit 1

headbody "$FILE"

CT=""

if grep -i "^mime-version:" $TEMP/head >/dev/null 2>&2
then
    > $TEMP/newheaders
    if CT=`getheader content-type $TEMP/head` ; then
	echo "$CT" > $TEMP/newbody
	if grep -i multipart $TEMP/newbody > /dev/null 2>&1 ; then
		usemime=y	## force mime if already multi-part
	fi
	getheader content-transfer-encoding $TEMP/head >> $TEMP/newbody || :
    else
	CT=""
    fi
else
    echo "Mime-Version: 1.0" > $TEMP/newheaders
fi

if [ "$usemime" != y ] ; then
    case "$function" in
     sign) sign || exit 1 ;;
     encrypt) encrypt $* || exit 1;
    esac
    cat $TEMP/head $TEMP/body.asc > $outfile || exit 1
    exit 0 ## should be done with non-mime case.
fi

BDRY="`newboundary`"

if [ "$CT" = "" ] ; then
	echo "Content-Type: text/plain; charset=us-ascii" > $TEMP/newbody
fi

echo "" >> $TEMP/newbody

case $function in
 sign)	if [ "$mimefixbody" = y ] ; then
	   sed -e 's/^From / &/' -e 's/[ 
	]*$//' $TEMP/body >> $TEMP/newbody
	   if grep "^From " $TEMP/body > /dev/null 2>&1 ; then
		echo 'Warning: "From " lines in message body have been indented' >&2
	   fi
	   if grep "[ 
	]$" $TEMP/body > /dev/null 2>&1 ; then
		echo 'Warning: trailing blanks removed form message body' >&2
	   fi
	else
	   sed -e 's/

*$//' $TEMP/body > $TEMP/newbody ## remove CR
	fi
	echo 'Content-Type: multipart/signed; protocol="application/pgp-signature";' >> $TEMP/newheaders
	echo "  micalg=pgp-$micalg"'; boundary="'"$BDRY"'"' >> $TEMP/newheaders
	;;
 encrypt) cat $TEMP/body >> $TEMP/newbody
	echo 'Content-Type: multipart/encrypted; protocol="application/pgp-encrypted";' >> $TEMP/newheaders
	echo '	boundary="'"$BDRY"'";' >> $TEMP/newheaders
	;;
esac

case $function in
 sign)	canonfile $TEMP/newbody > $TEMP/body
	detachsign || exit 1
	echo "--$BDRY" > $TEMP/body
	cat $TEMP/newbody >> $TEMP/body
	echo "" >> $TEMP/body
	echo "--$BDRY" >> $TEMP/body
	echo "Content-Type: application/pgp-signature" >> $TEMP/body
	echo "" >> $TEMP/body
	cat $TEMP/body.asc >> $TEMP/body
	echo "" >> $TEMP/body
	echo "--${BDRY}--" >> $TEMP/body
	echo "" >> $TEMP/body
	;;
 encrypt) mv $TEMP/newbody $TEMP/body || exit 1
	encrypt $* || exit 1
	echo "--$BDRY" > $TEMP/body
	echo "Content-Type: application/pgp-encrypted" >> $TEMP/body
	echo "" >> $TEMP/body
	echo "Version: 1" >> $TEMP/body
	echo "" >> $TEMP/body
	echo "--$BDRY" >> $TEMP/body
	echo "Content-Type: application/octet-stream" >> $TEMP/body
	echo "" >> $TEMP/body
	cat $TEMP/body.asc >> $TEMP/body
	echo "" >> $TEMP/body
	echo "--${BDRY}--" >> $TEMP/body
	echo "" >> $TEMP/body
	;;
esac
fixheaders > $TEMP/finalheaders

cat $TEMP/finalheaders $TEMP/body > "$outfile"

