#!/bin/bash

# Copyright (c) 2012 Jonathan McDowell <noodles@earth.li>,
#              2019 Daniel Kahn Gillmor <dkg@fifthhorseman.net>
# GNU GPL; v2 or later
# Given a key directory, prune, clean, or minimize the keys

# "prune" just does basic cleanup on the file, without getting rid of
# any third-party signatures.

set -e

if [ -z "$1" ] || [ -z "$2" ]; then
	cat >&2 <<EOF
Usage: $0 [prune|launder|clean|minimal] dir
   prune: remove invalid parts
   launder: invoke GnuPG's merge logic to trim the key
   clean: prune and drop non-debian third-party certifications
   minimal: prune and remove *all* third-party certifications
EOF
	exit 1
fi

declare -a GPGOPTIONS=(--batch
                       --no-tty
                       --quiet
                       --no-options
                       --homedir=/dev/null
                       --trust-model=always
                       --fixed-list-mode
                       --with-colons
                       --export-options=no-export-attributes
                      )


if [ "$1" == prune ]; then
    GPGOPTIONS+=(--no-keyring
                 --import-options=import-export
                )
elif [ "$1" == launder ]; then
    # we are going to do something very ugly...
    # see https://dev.gnupg.org/T4421
    : pass
elif [ "$1" == clean ]; then
    # we need to include all the known keys so that we keep the
    # interlocking signatures
    make
    GPGOPTIONS+=(--no-default-keyring
                 --import-options=import-export,import-clean
                 --export-options=export-clean
                 --keyring "$(readlink -f output/keyrings/debian-keyring.gpg)"
                 --keyring "$(readlink -f output/keyrings/debian-nonupload.gpg)"
                 --keyring "$(readlink -f output/keyrings/debian-maintainers.gpg)"
                 --keyring "$(readlink -f output/keyrings/debian-role-keys.gpg)"
                 --keyring "$(readlink -f output/keyrings/emeritus-keyring.gpg)"
                )
elif [ "$1" == minimal ]; then
    GPGOPTIONS+=(--no-keyring
                 --import-options=import-export,import-minimal
                 --export-options=export-minimal
                )
else
    echo "Must specify prune, launder, clean or minimal; not $1" >&2
    exit 1
fi

if [ ! -d "$2" ]; then
	printf '%s is not a directory' "$2" >&2
	exit 1
fi

# takes name of transferable public key file as $1, emits the laundered key to file named $2
launder_tpk() {
    local interim="$(mktemp -d interim.XXXXXXX)"
    local success=false
    local key="$1"
    local output="$2"
    mkdir -p -m 0700 "$interim/gpg" "$interim/split"
    cat > "$interim/gpg/gpg.conf" <<EOF
batch
no-tty
quiet
no-options
trust-model always
fixed-list-mode
with-colons
export-options no-export-attributes
EOF
    if gpg --homedir "$interim/gpg" --import-options=import-minimal --status-file "$interim/status" --import < "$key" &&
            fpr="$(awk '{ if ($1 == "[GNUPG:]" && $2 == "IMPORT_OK" && $3 == "1") { print $4 } }' < "$interim/status")" &&
            [ -n "$fpr" ] &&
            gpg --homedir "$interim/gpg" --export | (cd "$interim/split" && gpgsplit) &&
            gpg --homedir "$interim/gpg" --delete-key "$fpr"; then
        local pub="$interim/split/000001-006.public_key"
        local uid=$(ls "$interim/split/"*.user_id | head -n1)
        local sig=$(printf '%s/split/%06d-002.sig' "$interim" $(( "$(echo "${uid##$interim/split/}" | sed -e 's_^0*__' -e 's_-.*$__')" + 1 )) )
        if [ -r "$pub" ] && [ -r "$uid" ] && [ -r "$sig" ]; then
            if cat "$pub" "$uid" "$sig" | gpg --homedir "$interim/gpg" --import &&
                    gpg --homedir "$interim/gpg" --import < "$key" &&
                    gpg --homedir "$interim/gpg" --output "$output" --export "$fpr"; then
                success=true
            else
                printf 'Merging failed for %s (fpr: %s)\n' "$key" "$fpr" >&2
            fi
        else
            printf 'Could not find minimal TPK for %s (fpr: %s)\n' "$key" "$fpr" >&2
        fi
    else
        printf 'failed to do initial import of %s\n' "$key" >&2
    fi
    rm -rf "$interim"
    [ $success = true ]
}

cd "$2"
for key in 0x*; do
    success=false
    if [ "$1" == launder ]; then
        if launder_tpk "$key" "$key.new"; then
            success=true
        fi
    else
        if gpg "${GPGOPTIONS[@]}" --output "$key.new" --import "$key"; then
            success=true
        fi
    fi
	if [ $success = true ] &&  [ -s $key.new ]; then
		OLDSIZE=$(stat -c "%s" "$key")
		NEWSIZE=$(stat -c "%s" "$key.new")
		if [ $OLDSIZE -gt $NEWSIZE ]; then
			echo "Cleaning $key [$OLDSIZE] -> [$NEWSIZE]"
			mv "$key.new" "$key"
                elif [ $OLDSIZE -eq $NEWSIZE ] && ! cmp --quiet "$key" "$key.new" ; then
                    printf "Packets were reordered in $key"
                    if [ "$1" == launder ]; then
                        echo " (but ignoring while doing launder: https://dev.gnupg.org/T4422)"
                    else
		        mv "$key.new" "$key"
                        echo
                    fi
		fi
	fi
	[ -e "$key.new" ] && rm "$key.new"
done

exit 0
