#! /bin/sh -e

# set -e in case we are run using `sh axa_link_certs`
set -e

OPENSSL=openssl

DEBUG=
XARGS_DEBUG=
FORCE=
CERT_FILE=
SERVER=

ME=`basename $0`
USAGE="$ME: [-xf] [-S certsdir] [-o cert_file] -s server,port"

while getopts "xfS:o:s:" c; do
    case $c in
	x) set -x; DEBUG=-x; XARGS_DEBUG=-t;;
	f) FORCE=yes;;
	S) CERTS_DIR="$OPTARG";;
	o) CERT_FILE="$OPTARG";;
	s) SERVER="$OPTARG";;
	*) echo "$USAGE" 1>&2; exit 1;;
    esac
done
shift `expr $OPTIND - 1 || true`
if test "$#" -ne 0; then
    echo "$USAGE" 1>&2
    exit 1
fi

if expr "$CERT_FILE" : '.*\/' >/dev/null; then
    echo "\"-o cert_file\" must be a file instead of a path" 1>&2
    exit 1
fi

# require a server and port number
SERVER=`echo "$SERVER" | sed -n 's/,\([0-9][0-9]*\)$/:\1/p'`
if test -z "$SERVER"; then
    echo "\"-server,port\" is required" 1>&2
    echo "$USAGE" 1>&2
    exit 1
fi


# Function to get permission
yesno () {
    read -p "$1" YES
    case "$YES" in
	[yY]|[yY][eE][sS])
	    return 0
	    ;;
	*)
	    if test -n "$2"; then
		echo "?" 2>&1
		exit 1
	    fi
	    return 1
	    ;;
    esac
}

# Function to get the CN of a certificate
cn () {
    $OPENSSL x509 -noout -subject | sed -e 's/.*CN=\([^=\/]*\).*/\1/'
}

# Get the target directory
#   The user's expressed preference overrides.
#   Otherwise, take the first existing directory among $AXACONF/certs,
#	~/.axa/certs, and /usr/local/etc/axa/certs even if it is not writable,
#	because it will be used by AXA clients
#   Otherwise, offer to create a certs directory
#	in the first existing and writable among  $AXACONF, ~/.axa,
#	and /usr/local/etc/axa
#   Otherwise, give up
if test -n "$CERTS_DIR"; then
    if test ! -e "$CERTS_DIR"; then
	yesno "Create $CERTS_DIR? " X
	mkdir -p "$CERTS_DIR"
    fi
else
    NEW1=
    NEW2=
    NEW3=
    if test -n "$AXACONF" -a -d "$AXACONF"; then
	if test -e "$AXACONF/certs"; then
	    if test -d "$AXACONF/certs" -a -x "$AXACONF/certs"; then
		$CERTS_DIR="$AXACONF/certs"
	    fi
	elif test -w "$AXACONF"; then
	    NEW1="$AXACONF/certs"
	fi
    fi
    if test -z "$CERTS_DIR" -a -d ~/.axa -a -x ~/.axa; then
	if test -e ~/.axa/certs; then
	    if test -d ~/.axa/certs; then
		CERTS_DIR=~/.axa/certs
	    fi
	elif test -w ~/.axa; then
	    NEW2=~/.axa/certs
	fi
    fi
    if test -z "$CERTS_DIR"; then
	if test -e /usr/local/etc/axa/certs; then
	    if test -d /usr/local/etc/axa/certs -a -x /usr/local/etc/axa/certs; then
		CERTS_DIR=/usr/local/etc/axa/certs
	    fi
	elif test -w /usr/local/etc/axa; then
	    NEW3=/usr/local/etc/axa/certs
	fi
    fi
    if test -z "$CERTS_DIR" -a -n "$NEW1" && yesno "Create $NEW1? "; then
	mkdir "$NEW1" && CERTS_DIR="$NEW1"
    fi
    if test -z "$CERTS_DIR" -a -n "$NEW2" && yesno "Create $NEW2? "; then
	mkdir "$NEW2" && CERTS_DIR="$NEW2"
    fi
    if test -z "$CERTS_DIR" -a -n "$NEW3" && yesno "Create $NEW3? "; then
	mkdir "$NEW3" && CERTS_DIR="$NEW3"
    fi
fi
cd ${CERTS_DIR:=/usr/local/etc/axa/certs}
if test ! -w .; then
    echo "$CERTS_DIR is not writable" 1>&2
    exit 1
fi


CERT=`$OPENSSL s_client -connect "$SERVER" </dev/null 2>&1		\
    | sed -n '/--BEGIN CERTIFICATE--/,/--END CERTIFICATE---/p'`

if test -z "$CERT"; then
    echo "failed to get certificate from $SERVER" 1>&2
    exit 1
fi

SUBJECT=`echo "$CERT" | cn`
FPRINT=`echo "$CERT" | $OPENSSL x509 -noout -fingerprint`
HASH=`echo "$CERT" | $OPENSSL x509 -noout -subject_hash`

echo "Obtained certificate for \"$SUBJECT\" with"
echo "$FPRINT"

test -z "$CERT_FILE" && CERT_FILE="$SUBJECT.pem"
if ! expr "$CERT_FILE" : '[-._a-zA-Z0-9][-._a-zA-Z0-9]*$' >/dev/null; then
    echo "\"$CERT_FILE\" is not a suitable certificate file name" 1>&2
    exit 1
fi

DELETE=
if test -e "$CERT_FILE"; then
    if test -z "$FORCE"; then
	echo "$CERT_FILE already exists" 1>&2
	exit 1
    else
	DELETE=yes
    fi
fi

yesno "Install it in $CERTS_DIR/$CERT_FILE? " X

test -n "$DELETE" && rm "$CERT_FILE"

# Look for an existing certificate and a free link name.
for N in 0 1 2 3 4 5 6 7 8 9 ""; do
    LINK_CERT_FILE=$HASH.$N
    test ! -e "$LINK_CERT_FILE" && break

    SUBJECT2=`cn <"$LINK_CERT_FILE"`
    test "$SUBJECT" != "$SUBJECT2" && continue

    # Delete the old certificate unless it is a link from /usr/local/etc/axa
    CERT_FILE2=`ls -l $LINK_CERT_FILE | sed -e 's/.*-> //'`
    if ! expr "$CERT_FILE2" : '.*\/' >/dev/null; then
	if test -z "$FORCE"; then
	    echo "$CERTS_DIR/$CERT_FILE2 is also for \"$SUBJECT\""
	    test -z "$FORCE" && yesno "Delete $CERTS_DIR/$CERT_FILE2? " X
	fi
	rm "$CERT_FILE2"
    fi

    # Delete the old link
    rm "$LINK_CERT_FILE"
    break
done

echo "$CERT" >$CERT_FILE
ln -f -s $CERT_FILE $LINK_CERT_FILE
