#!/usr/bin/env bash
#
# Copyright: Patrick Koppen
# License:   GPLv3

set -eu -o pipefail

# Locking code to try to make sure that two instances do not run together and
# corrupt the CA files.
LOCKDIR="/tmp/burp_ca.lockdir"
PIDFILE="$LOCKDIR/pid"

cleanup()
{
	# Perform program exit housekeeping.
	# Optionally accepts an exit status.
	rm -rf "$LOCKDIR"
	trap - INT TERM EXIT
}

lock()
{
	# Originally from http://wiki.bash-hackers.org/howto/mutex, and
	# adjusted somewhat.
	if mkdir "$LOCKDIR" 2>/dev/null ; then
		trap cleanup INT TERM EXIT
		echo $$ > "$PIDFILE" || return 1
		return 0
	else
		oldpid="$(cat "$PIDFILE")" || return 1

		if ! ps -p "$oldpid" &>/dev/null; then
			echo "Removing stale lock with pid $oldpid" 1>&2
			rm -rf "$LOCKDIR" || return 1
			echo "Restarting myself" 1>&2
			exec "$0" "$@"
		fi
		return 1
	fi
}

maxwait=60
sleeptime=1
slept=0

while ! lock; do
	sleep "$sleeptime"
	let slept+="$sleeptime"
	let sleeptime*=2
	if [ "$slept" -ge "$maxwait" ]; then
		echo "Failed to acquire lock $LOCKDIR in $slept seconds" 1>&2
		exit 1
	fi
done

# Solaris and AIX don't support 'hostname -f' and, in fact,
# set the hostname to '-f' with that call.
name=
case "$(uname -s)" in
	SunOS) name="$(check-hostname | cut -d' ' -f7)" ;;
	AIX) name="$(host "$(hostname)" | awk '{ print $1}')" ;;
	*) name="$(hostname -f)" ;;
esac
# Try NetBSD style.
[ -n "$name" ] || name="$(hostname)"

etc="/etc/burp"
dir="$etc"/CA
conf="$etc"/CA.cnf
days=
ca_days=7300
size=2048
init=
key=
keypath=
request=
requestpath=
sign=
batch=
revoke=
crl=
ca=
dhfile=
altname=
def_umask=022
sec_umask=077

function help() {
  cat <<EOF
$0: Help:
    -h|--help               show help
    -i|--init               inititalize CA
    -k|--key                generate new key
    -K|--keypath <path>     path to new key
    -r|--request            generate certificate sign request
    -R|--requestpath <path> path to certificate sign request
    -s|--sign               sign csr (use --ca <ca> and --name <name>)
       --batch              do not prompt for anything
       --revoke <number>    revoke certificate with serial number
       --crl                generate certificate revoke list
    -d|--dir <dir>          ca output dir (default: $dir)
    -c|--config             config file (default: $conf)
    -n|--name               name (default: $name)
    -D|--days               valid days for certificate (default in config file)
       --ca_days            valid days for CA certificate (default: $ca_days)
    -S|--size               key size (default: $size)
    -a|--ca                 ca name if different from name
    -f|--dhfile <path>      generate Diffie-Hellman file
    -A|--altname            subjectAltName
EOF
}

check_second_arg()
{
	if [ "$1" -eq 0 ] ; then
		help
		exit 1
	fi
}

while [ $# -gt 0 ]
do
	case "$1" in
		-h|--help) help; exit 0 ;;
		-i|--init) init=yes ;;
		-k|--key) key=yes ;;
		-K|--keypath) check_second_arg "$#"; keypath="$2"; shift ;;
		-r|--request) request=yes ;;
		-R|--requestpath) check_second_arg "$#"; requestpath="$2"; shift ;;
		-s|--sign) sign=yes ;;
		   --batch) batch="-batch" ;;
		   --revoke) check_second_arg "$#"; revoke="$2"; shift ;;
		   --crl) crl=yes ;;
		-d|--dir) check_second_arg "$#"; dir="$2"; shift ;;
		-c|--config) check_second_arg "$#"; conf="$2"; shift ;;
		-n|--name) check_second_arg "$#"; name="$2"; shift ;;
		-D|--days) check_second_arg "$#"; days="-days $2"; shift ;;
		   --ca_days) check_second_arg "$#"; ca_days="$2"; shift ;;
		-S|--size) check_second_arg "$#"; size="$2"; shift ;;
		-a|--ca) check_second_arg "$#"; ca="$2"; shift ;;
		-f|--dhfile) check_second_arg "$#"; dhfile="$2"; shift ;;
		-A|--altname) check_second_arg "$#"; altname="$2"; shift ;;
		--) shift; break ;;
		-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1 ;;
		*) break ;;
	esac
	shift
done

if [ -n "$dhfile" ] ; then
	openssl dhparam \
		-dsaparam \
		-out "$dhfile" \
		2048
	chmod 600 "$dhfile"
fi

if [ -z "$ca" ]; then
	ca="$name"
fi

if [ -n "$altname" ]; then
	altname="subjectAltName=$altname"
fi

export CA_DIR="$dir"

# init CA
if [ "$init" = "yes" ]; then
	echo "Init... $ca"
	if [ ! -f "$conf" ]; then
		echo "$0: error - config $conf missing" 1>&2
		exit 1
	fi
	if [ -d "$dir" ]; then
		echo "$0: error - $dir exists, ca initialized" 1>&2
		exit 1
	fi
  
	mkdir "$dir"
	mkdir "$dir"/certs
	mkdir "$dir"/newcerts

	umask "$sec_umask"
	openssl genrsa \
		-out "$dir/CA_$ca.key" \
		"$size"
	umask "$def_umask"
	TEMP="$(mktemp "/tmp/burp_ca.tmp.XXXXXXXX" || echo "/tmp/burp_ca.tmp.$$")"
	cat <<-EOF > "$TEMP"
	RANDFILE                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  