#! /bin/bash
# This file contains a number of functions that simplify key
# generation and certification.  See simple.sh for an example of its
# usage.

set -e

# Some files that must exist in the current directory.
assert_files="gen-helper.sh cliques cycle local-optima roundabout simple my-own-grandpa"

skip_keygen=0

function init {
    function usage {
        echo "Usage: $0 [--skip-keygen] [--directory=DIR]" 1>&2
        exit 1
    }

    options=$(getopt -o "" -l skip-keygen,directory: -- "$@")
    [ $? -eq 0 ] || {
        echo "Incorrect options provided"
        exit 1
    }
    eval set -- "$options"

    while true; do
        case "$1" in
            --skip-keygen)
                skip_keygen=1
                ;;
            --directory)
                shift
                dir=$1
                ;;
            --)
                shift
                break
                ;;
        esac
        shift
    done

    if test $# -gt 0
    then
        echo "Too many arguments"
        usage
    fi

    echo $0
    if test x"$dir.sh" != x"${0#*/}"
    then
        echo "Value of --directory ($dir) does not match script ($0)."
        exit 1
    fi


    # Make sure we are at the right spot.
    for f in $assert_files $dir
    do
        if ! test -e $f
        then
            echo "Invalid current working directory; '%f' not found."
            exit 1
        fi
    done
}

function check_init {
    if test -z "$dir"
    then
        echo "output directory not set, did you call init?"
        exit 1
    fi
}

key_files=""

# Generate a key.
#
#   key [-t CREATION_TIME] PRIMARY_USERID_EMAIL [EMAIL...]
#
#   $1 is primary user id's email address.  If it is not an email
#   address, then it is expanded to "$1@example.org".  The basename is
#   derived from the local part.
#
#   Email addresses are unconditionally wrapped in angle brackets ("<"
#   and ">").
#
# The created key is saved in "$dir/$basename-priv.pgp".
#
# Example:
#
#     key alice
#
#   Generates a key in $dir/alice-priv.pgp with the User ID:
#   "<alice@example.org>".
#
#     key alice@some.org alice@other.org
#
#   Generates a key in $dir/alice-priv.pgp with the primary User ID
#   "<alice@some.org>" and the secondary User ID "<alice@other.org>".
function key {
    check_init

    time=
    expires_in=never

    options=$(getopt -o t:e: -l domain: -- "$@")
    [ $? -eq 0 ] || {
        echo "Incorrect options provided"
        exit 1
    }
    eval set -- "$options"

    while true; do
        case "$1" in
            -t)
                shift
                time=$1
                ;;
            -e)
                shift
                expires_in=$1
                ;;
            --)
                shift
                break
                ;;
        esac
        shift
    done

    if test "${1%@*}" != "$1"
    then
        # It's an email address.  Use the local part for the
        # filename.
        userid="<$1>"
        basename="${1%@*}"
    else
        userid="<$1@example.org>"
        basename=$1
    fi
    shift

    declare -a userids
    userids+=(--userid "$userid")

    for userid in ${@:+"$@"}
    do
        userids+=(--userid "<$userid>")
    done

    keyfile="$dir"/"$basename"-priv.pgp

    if test $skip_keygen -eq 1
    then
        if ! test -e "$keyfile"
        then
            echo --skip-keygen provided, but "$keyfile" does not exist.
            exit 1
        fi
    else
        sq --overwrite key generate --without-password \
           --cannot-sign --cannot-encrypt --cannot-authenticate \
           ${expires_in:+--expiration "$expires_in"} \
           ${time:+--time "$time"} \
           ${userids[@]} \
           --output "$keyfile" \
           --rev-cert - \
           --own-key >/dev/null
    fi

    keyfiles="$keyfiles $keyfile"

    sq --overwrite key delete --cert-file "$keyfile" --output "$dir"/"$basename".pgp
}

# Certifies a key.
#
#   certify SIGNER TARGET [USERID] [-a AMOUNT] [-d DEPTH] [-r REGEX]
#
# SIGNER and TARGET are basenames.  The output is written to
# DIR/TARGET.pgp.
#
# If USERID is not provided, it defaults to "<$TARGET@example.org>".
#
# If AMOUNT is not provided, it defaults to 120.
#
# If DEPTH is not provided, it defaults to 0.
#
# If REGEX is not provided, no regex is set.
#
# If --domain is provided, a regex based on the domain is set.
function certify {
    check_init

    function usage {
        echo "Usage: $0 [-a AMOUNT] [-d DEPTH] [-r REGEX] [--domain=DOMAIN] [-t TIME] [-e EXPIRES_IN] SIGNER TARGET [USERID]" 1>&2
        exit 1
    }


    amount=120
    depth=0
    declare -a regex
    time=
    expires_in=never

    options=$(getopt -o a:d:r:t:e: -l domain: -- "$@")
    [ $? -eq 0 ] || {
        echo "Incorrect options provided"
        exit 1
    }
    eval set -- "$options"

    unconstrained=--unconstrained
    while true; do
        case "$1" in
            -a)
                shift
                amount=$1
                ;;
            -d)
                shift
                depth=$1
                ;;
            -r)
                shift
                regex+=(--regex "$1")
                unconstrained=
                ;;
            --domain)
                shift
                regex+=(--regex '<[^>]+[@.]'"$(echo -n "$1" | sed 's/[.]/\\./')"'>$')
                unconstrained=
                ;;
            -t)
                shift
                time=$1
                ;;
            -e)
                shift
                expires_in=$1
                ;;
            --)
                shift
                break
                ;;
        esac
        shift
    done

    if test $# -lt 2
    then
        echo "Too few arguments"
        usage
    fi

    if test $# -gt 3
    then
        echo "Too many arguments"
        usage
    fi

    signer=$1
    target=$2

    if test $# -eq 3
    then
        userid=$3
    elif test x"$target" != x"${target%@*}"
    then
        # Email address.
        userid="<$target>"
        # Compute the base name.
        target=${target%@*}
    else
        # Base name.
        userid="<$target@example.org>"
    fi

    sq pki vouch authorize --amount $amount --depth $depth ${regex[@]} \
       $unconstrained \
       ${time:+--time "$time"} \
       ${expires_in:+--expiration "$expires_in"} \
       --certifier-file "$dir"/"$signer"-priv.pgp \
       --cert-file "$dir"/"$target".pgp \
       --userid "$userid" > "$dir"/"$target".pgp~

    mv "$dir"/"$target".pgp~ "$dir"/"$target".pgp
}

# Revokes a certificate or user id.
#
#   revoke [-r REVOKER] [-u USERID] [-t TIME] CERT REASON
#
# CERT is a basename.  The output is written to DIR/CERT.pgp.
function revoke {
    check_init

    function usage {
        echo "Usage: $0 [-r REVOKER] [-u USERID] [-t TIME] CERT REASON" 1>&2
        exit 1
    }

    subcommand=
    userid=
    time=
    revoker=

    options=$(getopt -o u:t:r: -l domain: -- "$@")
    [ $? -eq 0 ] || {
        echo "Incorrect options provided"
        exit 1
    }
    eval set -- "$options"

    while true; do
        case "$1" in
            -u)
                shift
                subcommand=userid
                userid=$1
                ;;
            -t)
                shift
                time=$1
                ;;
            -r)
                shift
                revoker=$1
                ;;
            --)
                shift
                break
                ;;
        esac
        shift
    done

    if test $# -lt 2
    then
        echo "Too few arguments"
        usage
    fi

    if test $# -gt 3
    then
        echo "Too many arguments"
        usage
    fi

    cert=$1
    reason=$2
    if test "x$revoker" = x
    then
        revoker=$cert
    fi

    set -x

    set -o pipefail
    {
        sq packet dearmor "$dir"/"$cert".pgp
        sq key ${subcommand} revoke ${userid:+--userid "$userid"} \
           ${time:+--time "$time"} \
           --revoker-file "$dir"/"$revoker"-priv.pgp \
           --cert-file "$dir"/"$cert".pgp \
           --reason "$reason" --message "some message" \
           --output - | sq packet dearmor
    } | sq keyring merge > "$dir"/"$cert".pgp~
    set +o pipefail

    mv "$dir"/"$cert".pgp~ "$dir"/"$cert".pgp
}

# Expires a certificate.
#
#   expire CERT TIME
#
# CERT is a basename.  The output is written to DIR/CERT.pgp.
#
# Time is the expiration time and may be relative.
function expire {
    check_init

    function usage {
        echo "Usage: $0 CERT TIME" 1>&2
        exit 1
    }

    time=

    if test $# -lt 2
    then
        echo "Too few arguments"
        usage
    fi

    if test $# -gt 2
    then
        echo "Too many arguments"
        usage
    fi

    cert=$1
    time=$2

    set -x

    set -o pipefail
    {
        sq packet dearmor "$dir"/"$cert".pgp
        sq key expire \
           --expiration "$time" \
           --cert-file "$dir"/"$cert"-priv.pgp \
           --output - | sq packet dearmor
    } | sq keyring merge > "$dir"/"$cert".pgp~
    set +o pipefail

    mv "$dir"/"$cert".pgp~ "$dir"/"$cert".pgp
}

function finish {
    find "$dir" -type f -name '*-priv.pgp' | while read f
    do
        cat "${f%-priv.pgp}.pgp"
    done | sq keyring merge > "$dir.pgp"

    ./torust.sh "$dir"

    find "$dir" -type f -name '*-priv.pgp' \
         -a -\( $(for f in $keyfiles; do echo -wholename $f -o; done) -print -\) \
        | while read f
    do
        echo "WARNING: $f unused."
    done
}

init ${@:+"$@"}
