#!/bin/bash
# -*- bash -*-

# Copyright (C) 2021 Jacob Bachmeyer
#
# This file is part of a testsuite for the GNU FTP upload system.
#
# This file is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

: ${GNUPGHOME:=~/.gnupg}

# parse command line
while :; do
    case "$1" in
	--version)
	    echo 'gpgv (Testing mock)'
	    exit 0
	    ;;
	--keyring)
	    case "$2" in
		*/*)
		    Keyrings="$Keyrings \"$2\""
		    ;;
		*)
		    Keyrings="$Keyrings \"${GNUPGHOME}/$2\""
		    ;;
	    esac
	    shift 2
	    ;;
	--homedir)
	    GNUPGHOME=$2
	    shift 2
	    ;;
	--status-fd)
	    StatusFd=$2
	    shift 2
	    ;;
	--logger-fd)
	    LogFd=$2
	    shift 2
	    ;;
	--*)
	    echo bogus option $1
	    shift
	    ;;
	*)
	    break
	    ;;
    esac
done

### Output helpers
function status {
    if [ x$StatusFd != x ]; then
	echo '[GNUPG:]' "$@" >&${StatusFd}
    fi
}
function log {
    echo gpgv: "$@" >&${LogFd-2}
}

### Keyring handling

# Mock keyrings consist of line-oriented records, in the format:
#  <long key ID>:<status>:<primary long key ID>:<user ID>:<expiration timestamp>
#   The <long key ID> field is 16 hex digits.
#   The <status> is 'V' for a valid key or 'R' for a revoked key.
#   The <primary long key ID> field may be omitted if same as key ID.
#   The <user ID> field is free-form text.
#   The <expiration timestamp> is empty for no expiration.

# search keyrings for the given long key ID
# return success and load global KeyRec if found
function search_keyrings {
    KeyRec=$(eval grep -he \"^$1:\" $Keyrings | head -1; \
	return ${PIPESTATUS[0]})
    return $?
}
# access fields from global KeyRec
function keyrec_id()	(IFS=: ; set -- $KeyRec ; echo $1)
function keyrec_status()(IFS=: ; set -- $KeyRec ; echo $2)
function keyrec_pri()	(IFS=: ; set -- $KeyRec ; echo ${3:-$1})
function keyrec_user()	(IFS=: ; set -- $KeyRec ; echo $4)
function keyrec_exp()	(IFS=: ; set -- $KeyRec ; echo $5)

function keyrec_expired() {
    [ -n "$(keyrec_exp)" \
	-a $(date -d "$(keyrec_exp)" +%s) -lt $(date -d now +%s) ]
}
function keyrec_revoked() { [ "$(keyrec_status)" = R ]; }

### Signature handling

ExitCode=0

# handle SIGNED record in mock data
function handle_SIGNED {
    # SIGNED <status> <sigID> <keyID> "<timestamp>" \
    #	"<expiration timestamp>"
    local status="$1" sigID="$2" keyID="$3" tstamp="$4"
    if [ "x$5" = x ]; then local exptstamp=0
    else local exptstamp=$(date -d "$5" +%s) exptstamp_raw="$5"
    fi

    log Signature made $(date -d "$tstamp" +'%a %b %e %T %Y %Z') \
	using RSA key ID ${keyID:8}
    if search_keyrings $keyID; then
	# key was found in the keyrings

	# handle caveats (expired signature, expired key, revoked key)
	local chktype=GOOD desctype=Good
	keyrec_revoked && chktype=REVKEY
	keyrec_expired && chktype=EXPKEY
	# Note that signatures from expired and revoked keys are still
	# considered "Good" apparently ...
	[ $exptstamp -gt 0 \
	    -a $exptstamp -lt $(date -d now +%s) ] \
	    && chktype=EXP desctype=Expired
	# ... but a signature that has *itself* expired is reported.

	case "$status" in
	    V)	# valid signature
		if keyrec_expired; then
		    status KEYEXPIRED $(date -d "$(keyrec_exp)" +%s)
		fi
		status SIG_ID testmock/sig/"$sigID"/id \
		    $(date -d "$tstamp" +'%Y-%m-%d %s')
		status ${chktype}SIG $keyID $(keyrec_user)
		log ${desctype} signature from \"$(keyrec_user)\"
		status VALIDSIG 000000000000000000000000$keyID \
		    $(date -d "$tstamp" +'%Y-%m-%d %s') \
		    $exptstamp 3 0 1 2 01 \
		    000000000000000000000000$(keyrec_pri)
		if [ $chktype = EXP ]; then
		    log Signature expired \
			$(date -d "$exptstamp_raw" +'%a %b %e %T %Y %Z')
		elif [ $exptstamp -gt 0 ]; then
		    log Signature expires \
			$(date -d "$exptstamp_raw" +'%a %b %e %T %Y %Z')
		fi
		;;
	    B)	# bogus signature
		status BADSIG $keyID $(keyrec_user)
		log BAD signature from \"$(keyrec_user)\"
		ExitCode=1
		;;
	esac

    else
	# key not found
	status ERRSIG $keyID 1 2 01 $(date -d "$tstamp" +%s) 9
	status NO_PUBKEY $keyID
	log "Can't check signature:" public key not found
	ExitCode=2
    fi
}

# process a "signature" file for a testing run
function process_sig_file {
    while read line; do
	case "$line" in
	    -----BEGIN?PGP?SIGNATURE-----)
		break
		;;
	    DETACHED?SIGNATURE)
		DataFile="$1"
		shift
		break
	esac
    done
    while read token rest; do
	case "$token" in
	    -----END)
		break
		;;
	    Version:)
		# this looks like a GPG signature instead of mock data...
		# complain... loudly
		echo mockgpgv: looks like someone fed a real signature \
		    to the mock tool
		log mock: real GPG signature detected instead of mock test data
		status NODATA 3
		exit 120
		;;
	    SIGNED_FILE)
		if [ x"$DataFile" = x ]; then
		    log no signed data
		    log "can't hash datafile:" file open error
		    ExitCode=2
		    break
		elif [ ! -f "$DataFile" ]; then
		    log "can't open signed data" $DataFile
		    log "can't hash datafile:" file open error
		    ExitCode=2
		    break
		fi
		;;
	    SIGNED)
		eval handle_SIGNED $rest
		;;
	    *)
		echo $token $rest
		;;
	esac
    done
} <"$SigFile"

# check that we were actually given a file to process
if [ x"$*" = x ]; then
    status NODATA 2
    log verify signatures failed: eof
    exit 2
fi

# check that the file is readable and process it
SigFile="$1"
shift
if [ -r "$SigFile" ] ; then
    process_sig_file "$@"
else
    log "can't" open '`'"$SigFile'"
    log verify signatures failed: file open error
    exit 2
fi

exit $ExitCode

#EOF
