#! /bin/bash
#
# -- pine-gpg-filter-0.4, 2006-07-04; Martin A. Brown <martin@linux-ip.net>
#
# -- released under the GPL
#
# -- $DESCRIPTION
#
#    Act as a pine filter which can handle multiple roles.
#
# -- ChangeLog
#
#    0.4 2006-07-04; -MAB
#        oops!  Forgot that it's supposed to be "clearsign"; fixed
#        added logic switches so that it can be called by many names
#    0.3 2006-07-04; -MAB
#        improved documentation (long_usage)
#        verify functionality needs to use "tee", "mkfifo"
#        functionizing verify, encrypt, sign and so forth
#    0.2 2006-07-04; -MAB
#        fixed quoting issues for "$PGF_COMMENT"
#        shorter default comment string
#        put recipient existence check in correct place (--encrypt only)
#        allow user to specify the (--quiet) option
#    0.1 2006-07-04; -MAB
#        initial version; great U.S. Independence Day activity
#
# -- Contributors
#
# -- BUGS/TODO
#
#    - next version needs to be able to automatically figure out the
#      local-user ID to employ when sending

# -- script global variables
#
VERBOSE="${PGF_VERBOSE:-0}"
INFORM=1
NOTICE=2
DEBUG=3

STDOUT=1
STDERR=2

VERSION=0.4
NAME=pine-gpg-filter
PGF_URL="http://linux-ip.net/sw/pine-gpg-filter/"

REQUIRED_UTILITIES="gpg mkfifo rm tee"

# -- remove these from our environment for sanity
#
unset GPG_OPT_LU            \
      GPG_OPT_RECIPIENTS    \
      PGF_RECIPIENTS        \
      PGF_MODE              \
      PGF_TO_SELF

# -- set up some global defaults, but allow for environment variables
#
 : "${PGF_COMMENT:=pgf-$VERSION ($PGF_URL)}"
 : "${PGF_STATLINES:=multi}"

# -- and set up some internal flags for the different primary actions
#
unset C S E V D
C="--clearsign"
S="--sign"
E="--encrypt"
D="--decrypt"
V="--verify"


# -- my favorite shell functions (and their offspring)
#
# - - - - - - - - - 
  gripe () {
# - - - - - - - - - 
#
  local descriptor=$1   && shift
  printf -- "%s\n" "$@" >&${!descriptor}
}

abort  () { gripe STDERR "$@"; exit 1;     }

inform () { test "$VERBOSE" -ge "$INFORM" && gripe STDERR "I: $@"; }
notice () { test "$VERBOSE" -ge "$NOTICE" && gripe STDERR "N: $@"; }
debug  () { test "$VERBOSE" -ge "$DEBUG"  && gripe STDERR "D: $@"; }

# - - - - - - - - - - -
  version  () {
# - - - - - - - - - - -
#
  gripe STDOUT "${0##*/}-${VERSION}"
  exit 0
}

# - - - - - - - - - - -
  usage  () {
# - - - - - - - - - - -
#
  local descriptor=$1   && shift
  cat >&${!descriptor} <<-EOUSAGE
Usage: ${NAME} [ options ] [ <recipient> ... ]

Options (defaults marked, or in parentheses):
  -h, --help                  Print out this handy help screen.
  -L, --long-usage            Provide help and some more hints.
  -q, --quiet                 Reset verbosity to none.
  -v, --verbose               Increase verbosity, can be used multiple times.
  -V, --version               Print version information.
  -c, --clearsign             Ask GPG to clearsign the input.
  -e, --encrypt               Ask GPG to encrypt the input.
  -s, --sign                  Ask GPG to sign the input.
  -d, --decrypt               Ask GPG to decrypt the input.
  -y, --verify                Ask GPG to verify the input.
  -o, --one-line              Compress multiline status output from GPG.
  -m, --multi-line            Allow all of the status output from GPG.
  -t, --to-self               Also encrypt-to-self (ignored if not needed).
  -n, --no-comment            Suppress any comment string.
  -C, --comment     <string>  Override the default comment string.
  -R, --resultfile  <file>    Pine reports command output via _RESULTFILE_.
  -l, --local-user  <key_id>  Specify a local username.

EOUSAGE
}

# - - - - - - - - - - -
  short_usage  () {
# - - - - - - - - - - -
#
  local descriptor="${1:-STDOUT}"  && shift
  usage $descriptor
  test "$#" -gt 0 && abort "$@"
  exit 0
}

# - - - - - - - - - - -
  long_usage  () {
# - - - - - - - - - - -
#
  local descriptor="${1:-STDOUT}"  && shift
  usage $descriptor
  cat >&${!descriptor} <<-EOUSAGE
${NAME}-${VERSION} README

Description
===========
${NAME} is almost a clone of pinepg*.  It, however,
adds the capability for specifying which user ID (or IDs) to use for
the encryption or signing.  It does absolutely no passphrase
caching.  If you need (want) passphrase caching, look into
gpg-agent, available in the "gnupg2" package, e.g., gnupg-1.9.21 as
of 2006-07-04.

This utility is intended to allow the pine user to be able to take
advantage of multiple separate roles and keys for each of those
roles within pine.  Other pine and GnuPG integration (i.e.,
pgp4pine, pinepg) utilities do not allow for selection of user ID
for a particular encryption or signing request.

Required Option(s)
==================
At least one of these first four options is required.  The sign and
encrypt options may be combined, but no other combination is
possible.  If multiple options are specified in incompatible
combinations, the last one "wins".

 --clearsign (-c)       Have GPG clearsign the message.  This is what
                        most people mean when they talk about "signed"
                        email messages.
 --encrypt (-e)         Have GPG encrypt the message.  When using
                        this option, you must specify recipient
                        addresses, as well.
 --sign (-s)            Have GPG sign the message (use with encrypt).
 --decrypt (-d)         Have GPG decrypt the message.
 --verify (-v)          Have GPG verify the message.

These options

 --to-self (-t)         This option asks GPG to employ the encrypt-to-self
                        option, where self is defined as the local
                        user <key_id> specified with the -L option.
                        (This option is simply ignored if not needed.)

 --one-line (-o)        Only print out one line of the status generated
                        by GnuPG on the STDERR (_RESULTFILE_).
 --multi-line (-m)      Produce all of the usual GnuPG status output.
 --no-comment (-n)      Suppress the default comment string.
 --comment (-C)         Override the default comment string. Default is
                        $PGF_COMMENT
 --resultfile (-R)      Pine returns the filter's STDERR via the
                        _RESULTFILE_ (cf. Examples).
 --datafile (-D)        Pine provides a session-unique filename called
                        We use it as a FIFO.  _DATAFILE_ (cf. Examples).
 --local-user (-l)      Select a specific local key identifier.  With this
                        option, you may specify a non-default key on
                        your GPG keyring.  The <key_id> may be an
                        email address or the actual key ID itself.


Examples
========
In this iteration of the tool, you are required to specify in your pine
sending-filters which key you wish to use.
  
  example 0; encrypt the message (sending-filters)
  ================================================
  /path/to/$NAME -tL <key_id> -eR _RESULTFILE_ _RECIPIENTS_

  example 1; clearsign the message (sending-filters)
  ================================================
  /path/to/$NAME -tL <key_id> -cR _RESULTFILE_


  * pinepg:  http://quantumlab.net/pine_privacy_guard/

EOUSAGE

exit 0

}

# - - - - - - - - - - -
  parse_options () {
# - - - - - - - - - - -
#
  command getopt                    \
    --unquoted                      \
    --name "${0##*/}"               \
    --options "${OPTIONS}"          \
    --longoptions "${LONGOPTIONS}"  \
    -- "$@"
}

# - - - - - - - - - - -
  verify_support  () {
# - - - - - - - - - - -
#
# -- all we want to do is make sure that all of our support
# programs exist.
#
  local program
  local programs="$@"
  local N
  local ok=0           # -- return 0, if everything is OK!
  
  for program in $programs; do
    N=$( which "$program" 2>/dev/null )
    if test -z "$N"; then
      ok=1
      gripe STDERR "Could not locate necessary support program:  $program"
    fi
  done
  return $ok
}

# - - - - - - - - - - -
  one_line_check  () {
# - - - - - - - - - - -
#
# -- test to see if the user wants (only) one-line status messages
#
  test -z "$PGF_RESULTFILE"                 && return
  test    "$PGF_RESULTFILE" = "/dev/null"   && return
  test    "$PGF_RESULTFILE" = "/dev/stderr" && return
  test    "$PGF_STATLINES"  = "multi"       && return

  LINE="$( tail -1 $PGF_RESULTFILE )"

  printf "%s\n" "$LINE" > "$PGF_RESULTFILE"

}

# - - - - - - - - - - -
  pgf_catchall () {
# - - - - - - - - - - -
#
# -- call the GnuPG utility for any general purpose
#
  gpg $GPG_OPT_ACTION "$PGF_COMMENT" $GPG_OPT_LU

  EC=$?

  one_line_check

  exit $EC

}

# - - - - - - - - - - -
  pgf_encrypt  () {
# - - - - - - - - - - -
#
# -- call the GnuPG utility to encrypt (and maybe sign) the message on STDIN
#

  # -- we have to have recipients, or else there's no work to do,
  #    and this has to be an error.
  #
  test -n "$PGF_RECIPIENTS" \
    || abort "No recipient address(es) specified."
  
  gpg $GPG_OPT_ACTION "$PGF_COMMENT" $GPG_OPT_LU $GPG_OPT_RECIPS ;

  EC=$?

  one_line_check

  exit $EC

}

# - - - - - - - - - - -
  pgf_verify  () {
# - - - - - - - - - - -
#
# -- call the GnuPG utility to verify the message on STDIN
#
  rm -f "$PGF_FIFO" \
    || abort "Could not remove $PGF_FIFO (_DATAFILE_) prior to mkfifo."

  mkfifo --mode 0700 "$PGF_FIFO" \
    || abort "Could not create FIFO $PGF_FIFO for verify option."

  # -- very ugly; have to put gpg in the background, reading
  #    from a FIFO while we use "tee" to drop the data into
  #    the FIFO and spit to STDOUT, for the user to read the
  #    message.
  #
  gpg $GPG_OPT_ACTION "$PGF_COMMENT" $GPG_OPT_LU < "$PGF_FIFO" &

  GPG_PID=$!

  tee "$PGF_FIFO"

  wait $GPG_PID

  EC=$?

  one_line_check

  exit $EC

}

# -- check to make sure that we have a few key utilities before
#    launching off the command line processing
#
verify_support which \
  || abort "${0##*/}: Cannot proceed without \"which\" utility, quitting."

verify_support getopt \
  || abort "${0##*/}: Cannot process command line options, quitting."

# - - - - - - - - - - -
# main () {
# - - - - - - - - - - -
#
# -- normalize the command-line options
#
OPTIONS="vqhLVcesdytmonC:R:D:l:"
LONGOPTIONS="verbose,quiet,help,long-usage,version"
LONGOPTIONS="$LONGOPTIONS,clearsign,encrypt,sign,decrypt,verify,to-self"
LONGOPTIONS="$LONGOPTIONS,no-comment,comment:,datafile:,resultfile:"
LONGOPTIONS="$LONGOPTIONS,multi-line,one-line,local-user:"

# -- calling parse_options twice seems silly.  It is.  It allows, us
#    however, to control the error reporting to the user a bit more
#    carefully.  All the OCD kids are doing it.
#
parse_options "$@" >/dev/null \
  || abort "Try \"${0##*/} --help\" for more information."

set -- $( parse_options "$@" )

# -- Bump up verbosity a notch if we are connected to a terminal;
#    generally, users like to know a bit more of what's going on...
#
tty --silent         && let VERBOSE=VERBOSE+1

# -- examples of options/arguments should look like this now:
# 
while test "$#" -gt "0" ; do
   case "$1" in

      -h | --h*    ) short_usage                              ;;
      -L | --long* ) long_usage                               ;;
      -V | --vers* ) version                                  ;;
      -v | --verb* ) let VERBOSE=VERBOSE+1                    ;;
      -q | --q*    ) VERBOSE=0 ; PGF_QUIET="--quiet"          ;;

      -s | --s*    ) PGF_MODE="S ${PGF_MODE}"                 ;;
      -e | --e*    ) PGF_MODE="${PGF_MODE} E"                 ;;
      -c | --clea* ) PGF_MODE="${PGF_MODE} E"                 ;;
      -d | --decr* ) PGF_MODE="D"                             ;;
      -y | --veri* ) PGF_MODE="V"                             ;;
      -l | --loca* ) PGF_LOCAL_USER="$2"    && shift          ;;
      -t | --t*    ) PGF_TO_SELF="true"                       ;;

      -m | --m*    ) PGF_STATLINES="multi"  && shift          ;;
      -o | --o*    ) PGF_STATLINES="one"    && shift          ;;
      -n | --n*    ) PGF_NOCOMMENT="true"   && shift          ;;
      -C | --comm* ) PGF_COMMENT="$2"       && shift          ;;
      -D | --d*    ) PGF_FIFO="$2"          && shift          ;;
      -R | --r*    ) PGF_RESULTFILE="$2"    && shift          ;;

             --    ) shift                  && break          ;;
             -*    ) short_usage STDERR "Unknown option: $1"  ;;

   esac
   shift
done

# -- called compatibility names override any command line switches
#    (no reason for a user to call) "pfg-verify --decrypt"

case ${0##*/} in
     
  pgf-sign-encrypt ) PGF_MODE="S E"                           ;;
  pgf-sign         ) PGF_MODE="S"                             ;;
  pgf-clearsign    ) PGF_MODE="C"                             ;;
  pgf-encrypt      ) PGF_MODE="E"                             ;;
  pgf-decrypt      ) PGF_MODE="D"                             ;;
  pgf-verify       ) PGF_MODE="V"                             ;;
  *                ) :                                        ;;

esac

# -- all remaining arguments are taken to be recipient addresses
#
PGF_RECIPIENTS="$@"

verify_support $REQUIRED_UTILITIES \
  || abort "${0##*/}: Missing utilities, cannot continue, quitting."

test -n "$PGF_MODE" \
  || short_usage STDERR "One of encrypt, sign, decrypt or verify must be specified."

# -- actions taken after sanity checking

# -- iterate over the list of recipients and add them to the list
#    of addresses to which to encrypt the message
#
for RECIP in $PGF_RECIPIENTS ; do

  GPG_OPT_RECIPS="$GPG_OPT_RECIPS --recipient $RECIP"

done

# -- find out if the user specified an alternate email address and
#    specify that user ID for the following commands.  This is not necessary
#    for the --decrypt and --verify options and GnuPG-1.4.x simply ignores
#    the extraneous option.
#
if test -n "$PGF_LOCAL_USER" ; then

  GPG_OPT_LU="--local-user $PGF_LOCAL_USER"

  test -n "$PGF_TO_SELF" \
    && GPG_OPT_LU="$GPG_OPT_LU --no-encrypt-to --encrypt-to $PGF_LOCAL_USER"

fi

# -- Let's see about suppressing that pretty little "comment" string,
#    shall we?  The --comment (which is completely sugar) is included
#    in the header of the PGP encrypted (or signed) data.
#   
test -n "$PGF_NOCOMMENT"  && PGF_COMMENT=""

# -- send all STDERR nonsense from the gpg binary to pine's result
#    if the argument has been specified (otherwise, we'll just leave
#    STDERR alone)
#
test      -n "$PGF_RESULTFILE" \
  && exec 2> "$PGF_RESULTFILE"

# -- build up the primary action string for passing to GnuPG
#
GPG_OPT_ACTION="$PGF_QUIET --armor"

for ACTION in $PGF_MODE ; do

  GPG_OPT_ACTION="$GPG_OPT_ACTION ${!ACTION}"

done

GPG_OPT_ACTION="$GPG_OPT_ACTION --comment"

# -- And now, we are simply going to call GPG and pass this off.
#    We rely on GnuPG's error code to propagate back to pine, since
#    we are not handling any temporary files (if we can avoid it).
#
case "$PGF_MODE" in

  *E) pgf_encrypt      ;;
   V) pgf_verify       ;;
   *) pgf_catchall     ;;

esac

# -- end of file
