#!/bin/bash

source $(dirname $(dirname $0))/../lib/ca-functions

ALT_NAMES=()
QUALIFY=1
CNF_ONLY=0
CSR_ONLY=0
CRT_ONLY=0
MAKE_P12=0

# XXX: in the ca_extension_policy section of ca-config.tpl it states that the
#      C= and O= DN values in a CSR have to match those of the CA
#      should we have options here to change them when it will cause breakage?
usage() {
    cat <<__EOT__
Usage:
  $PROGNAME [options] <common name>

Options:
  -h, --help            Print this helpful message!
  -c, --encrypt         Encrypt certificate private key with Triple-DES
  -f, --config FILE     Use config file instead of $CONFFILE
  -t, --type TYPE       Certificate type: "server" (default), "client" or "user"
  -d, --days DAYS       Certificate valid for DAYS days instead of CA_CRT_DAYS
  -b, --bits BITS       Generate a BITS bit certificate instead of CA_CRT_BITS
  -n, --alt-name NAME   Alternative host name (can be provided multiple times)
  -p, --pkcs12          Create PKCS#12 certificate archive from generated cert
  -q, --no-qualify      Don't qualify short (dotless) names with CA_DOMAIN
  -r, --req-only        Only generate CSR, don't sign it
  -s, --sign-only       Only sign certificate, requires CSR in place
  -x, --cnf-only        Only generate templates, do not create CSR or sign CRT
  --country             Certificate DN -- C
  --state               Certificate DN -- ST
  --loc                 Certificate DN -- L
  --org                 Certificate DN -- O
  --ounit               Certificate DN -- OU
  --email               Certificate DN -- E
  --comment             Certificate nsComment field

__EOT__
}

short="hcf:t:d:b:n:pqrsx"
long="help,encrypt,config:,type:,days:,bits:,alt-name:"
long="$long,pkcs12,no-qualify,req-only,sign-only,cnf-only"
long="$long,country:,state:,loc:,org:,ounit:,email:,comment:"
opts=$( getopt -o "$short" -l "$long" -n "$PROGNAME" -- "$@" )
if [ 0 -ne $? ]; then echo; usage; exit 1; fi
eval set -- "$opts";

while :; do
    case "$1" in
        -h|--help) usage; exit 0;;
        -c|--encrypt) CRYPTKEY=""; shift;;
        -f|--config) shift; CONFFILE="$1"; CONFFILECLI=1; shift;;
        -t|--type) shift; USER_CA_CRT_TYPE="$1"; shift;;
        -d|--days) shift; USER_CA_CRT_DAYS="$1"; shift;;
        -b|--bits) shift; USER_CA_CRT_BITS="$1"; shift;;
        -n|--alt-name) shift; ALT_NAMES+=("$1"); shift;;
        -p|--pkcs12) MAKE_P12=1; shift;;
        -q|--no-qualify) QUALIFY=0; shift;;
        -r|--req-only) CSR_ONLY=1; shift;;
        -s|--sign-only) CRT_ONLY=1; shift;;
        -x|--cnf-only) CNF_ONLY=1; shift;;
        --country) shift; USER_CA_CRT_C="$1"; shift;;
        --state) shift; USER_CA_CRT_ST="$1"; shift;;
        --location) shift; USER_CA_CRT_L="$1"; shift;;
        --org) shift; USER_CA_CRT_O="$1"; shift;;
        --ounit) shift; USER_CA_CRT_OU="$1"; shift;;
        --email) shift; USER_CA_CRT_E="$1"; shift;;
        --comment) shift; USER_CA_CRT_COMMENT="$1"; shift;;
        --) shift; break;;
        *) echo "Unknown value '$1'"; exit 1;;
    esac
done

# load up the configuration file
ca_load_conf

# This must be provided on the command line. There's no point setting a
# "default" certificate CN in the config, it should be different every time.
CA_CRT_CN="$1"

# parameter checking fun -- we need a type and a cn (either user or host name)
if [ -z "$CA_CRT_CN" ]; then
    error "The host or username parameter is mandatory!"
fi

if [ 1 -eq "$CSR_ONLY" -a 1 -eq "$CRT_ONLY" ]; then
    error "Options --csr-only and --crt-only are mutually exclusive."
fi

if [ "$CA_CRT_TYPE" = "user" ]; then
    # append @$CA_DOMAIN to user CN if it's not already there and -q not set
    if [ 1 -eq "$QUALIFY" -a "${CA_CRT_CN%%@*}" = "$CA_CRT_CN" ]; then
        CA_CRT_CN="$CA_CRT_CN@$CA_DOMAIN";
    fi
else
    # fully qualify server or client CN with $CA_DOMAIN if it's not already
    if [ 1 -eq "$QUALIFY" -a "${CA_CRT_CN%%.*}" = "$CA_CRT_CN" ]; then
        # however we may also want the unqualified one as an alt-name
        ALT_NAMES+=("$CA_CRT_CN")
        CA_CRT_CN="$CA_CRT_CN.$CA_DOMAIN"
    fi
fi
CNF_NAME=$( echo -n "$CA_CRT_CN" | tr -c '[:alnum:]@-' _ )".$CA_CRT_TYPE";

# if they've provided a comment, reformat it correctly
if [ -n "$CA_CRT_COMMENT" ]; then
    CA_CRT_COMMENT="$( tr -d\" <<< $CA_CRT_COMMENT )"
    CA_CRT_COMMENT="nsComment               = \"$CA_CRT_COMMENT\"\n"
else
    CA_CRT_COMMENT=""
fi

CA_CRT_ALT_NAMES=""
# generate a list of alternative DNS names for server or client certificates
if [ "$CA_CRT_TYPE" != "user" ]; then
    i=1
    for ALT_NAME in "$CA_CRT_CN" "${ALT_NAMES[@]}"; do
        # also fully-qualify unqualified alt-names too (see below)
        # NOTE: except when it's the previously-fully-qualified CN...
        # XXX: maybe we should uniq the alt-names? hmm.
        if [ 1 -eq "$QUALIFY" -a "${ALT_NAME%%.*}" = "$ALT_NAME" \
          -a "${CA_CRT_CN%%.*}" != "$ALT_NAME" ]; then
            CA_CRT_ALT_NAMES="${CA_CRT_ALT_NAMES}DNS.$i=$ALT_NAME.$CA_DOMAIN\n"
            i=$(( $i+1 ))
        fi
        CA_CRT_ALT_NAMES="${CA_CRT_ALT_NAMES}DNS.$i=$ALT_NAME\n"
        i=$(( $i+1 ))
    done
fi

if [ 1 -ne "$CRT_ONLY" ]; then
    if [ 1 -eq "$CNF_ONLY" -o "$CSR_ONLY" -eq "$CNF_ONLY" ]; then
        # dirty logic here that probably needs commenting!
        # generate a *new* certificate request configuration if...
        #   a) --cnf-only is set, i.e. we only want to generate a config
        #   b) both --cnf-only and --csr-only are unset, i.e.
        #      we're just generating a csr/crt as per usual
        #   c) both --cnf-only and --csr-only are set, i.e.
        #      we're just generating the config and not the csr itself
        ca_template "req-config" "$CA_HOME/cnf/$CNF_NAME.req.cnf"
    fi
    if [ 1 -ne "$CNF_ONLY" ]; then
        if [ ! -f "$CA_HOME/cnf/$CNF_NAME.req.cnf" ]; then
            error "Couldn't find CSR config $CA_HOME/cnf/$CNF_NAME.req.cnf!"
        fi
        # the above logic means that if you pass --csr-only but not
        # --cnf-only, you can re-use a pre-existing config to generate
        # a new csr, should you wish to do so...
        openssl req -new $CRYPTKEY -config "$CA_HOME/cnf/$CNF_NAME.req.cnf" \
          -keyout "$CA_HOME/key/$CNF_NAME.key" \
          -out    "$CA_HOME/csr/$CNF_NAME.csr"
    fi
fi
if [ 1 -ne "$CSR_ONLY" ]; then
    if [ 1 -eq "$CNF_ONLY" -o "$CRT_ONLY" -eq "$CNF_ONLY" ]; then
        # same logic above applies here, but for generating the extensions
        # configuration file and signed certificate instead
        ca_template "$CA_CRT_TYPE-ext" "$CA_HOME/cnf/$CNF_NAME.ext.cnf"
    fi
    if [ 1 -ne "$CNF_ONLY" ]; then
        # ensure relevant files are in place before continuing...
        if [ ! -f "$CA_HOME/csr/$CNF_NAME.csr" ]; then
            error "CSR not present in $CA_HOME/csr/$CNF_NAME.csr"
        fi
        if [ ! -f "$CA_HOME/cnf/$CNF_NAME.ext.cnf" ]; then
            error "Couldn't find extensions in $CA_HOME/cnf/$CNF_NAME.ext.cnf"
        fi
        openssl ca -config "$CA_HOME/cnf/$CA_NAME.ca.cnf" \
          -days    "$CA_CRT_DAYS" \
          -extfile "$CA_HOME/cnf/$CNF_NAME.ext.cnf" -batch \
          -out     "$CA_HOME/crt/$CNF_NAME.crt" \
          -in      "$CA_HOME/csr/$CNF_NAME.csr"
    fi
fi

if [ 1 -eq "$MAKE_P12" ]; then
    ca_gen_p12 "$CNF_NAME"
fi