#!/usr/bin/env bash

# pki-authority: CA-side PKI management
# Copyright (C) 2016 Maciej Delmanowski <drybjed@gmail.com>
# Homepage: http://debops.org/

set -eu -o pipefail

umask 022

script="$(basename ${0})"

# OpenSSL writes to stderr/stdout even when there are no errors. So just
# display the output if the exit code was != 0 to simplify debugging.
_openssl () {
    set +e
    _openssl_out="$(openssl ${@} 2>&1)"
    local rc=$?
    set -e
    if [ ${rc} -ne 0 ]; then
        echo "${script}: Error: failed to run $* (Exitcode: ${rc})" >&2
        echo >&2
        echo "Details:" >&2
        echo "${_openssl_out}" >&2
        exit ${rc}
  fi
  unset _openssl_out
  return ${rc}
}

# Check if an associative array has a specified key present
key_exists () {
    eval '[ ${'$1'[$2]+test_of_existence} ]'
}

# Check if an element is in an array
array_exists () {
    local array="$1[@]"
    local seeking=${2}
    local in=1
    for element in "${!array}"; do
        if [[ $element == $seeking ]]; then
            in=0
            break
        fi
    done
    return $in
}

# Get an absolute path to a file
get_absolute_path () {
    python -c 'import sys, os.path; print os.path.abspath(sys.argv[1])' "${1}"
}

# Get a relative path to a file
get_relative_path () {
    python -c 'import sys, os.path; print os.path.relpath(sys.argv[1], sys.argv[2])' "${1}" "${2:-$PWD}"
}

# Wrap dnsdomainname if it's not present (on MacOS X)
get_dnsdomainname () {
    if type dnsdomainname > /dev/null 2>&1 ; then
        dnsdomainname
    else

        local fqdn="$(hostname -f)"
        if [[ ${fqdn} == *.* ]] ; then
            echo "${fqdn#*.}"
        else
            echo ""
        fi
    fi
}

initialize_environment () {

    declare -gA config

    config["pki_root"]="${PKI_ROOT:-$(pwd)}"
    config["pki_library"]="${PKI_LIBRARY:-openssl}"
    config["pki_ca_certificates"]="${PKI_CA_CERTIFICATES:-${config['pki_root']}/ca-certificates/by-group/all}"
    config["system_ca"]="true"

    config["pki_authorities"]="${config['pki_root']}/authorities"
    config["pki_requests"]="${config['pki_root']}/requests"

    config["pki_default_fqdn"]="$(hostname -f)"
    config["pki_default_domain"]="$(get_dnsdomainname)"
    config["pki_default_domain_dn"]=$(echo "${config['pki_default_domain']}" | cut -d. -f1 | sed 's/.*/\u&/')

    config["domain"]=""
    config["ca_type"]=""
    config["issuer_name"]=""
    config["alt_authority"]=""

    config["pki_default_sign_base"]="365"
    config["pki_default_root_sign_multiplier"]="12"
    config["pki_default_ca_sign_multiplier"]="10"
    config["pki_default_cert_sign_multiplier"]="3"

    config["root_sign_days"]=""
    config["ca_sign_days"]=""
    config["cert_sign_days"]=""

    config["public_dir_group"]="$(id -gn)"
    config["public_file_group"]="$(id -gn)"

    config["private_dir_group"]="$(id -gn)"
    config["private_file_group"]="$(id -gn)"

    config["public_dir_mode"]="755"
    config["private_dir_mode"]="700"

    config["public_file_mode"]="644"
    config["private_file_mode"]="600"

    test -d ${config['pki_root']} || mkdir -p -m ${config['public_dir_mode']} ${config['pki_root']}
}

print_usage () {
    cat << EOF
Usage: ${script} <command> [parameters]
EOF

}

enter_authority () {

    local authority="${1}"
    local config_file="${2:-config/authority.conf}"

    test -d ${config['pki_authorities']}/${authority} || mkdir -p -m ${config['public_dir_mode']} ${config['pki_authorities']}/${authority}

    cd ${config['pki_authorities']}/${authority}
    local rc=$?

    if [ -r ${config_file} ] ; then

        # FIXME: Add a code that checks if the config file has no dangerous code inside
        . ${config_file}
    fi

    return ${rc}
}

update_symlink () {

    local symlink_target="${1}"

    if [ -n "${symlink_target}" ] ; then

        shift 1

        for target in ${@} ; do
            if  [ -r ${target} ] ; then
                if [ -L ${symlink_target} -a "$(readlink ${symlink_target})" == "${target}" ] ; then
                    break
                else
                    ln -sf ${target} ${symlink_target}
                fi
                break
            fi
        done

    fi
}

create_openssl_request_config () {

    local config_file="${1:-config/openssl-request.conf}"
    local config_dn="${2}"

    if [ -n "${config_file}" -a -n "${config_dn}" -a ! -r ${config_file} ] ; then

        cat << EOF > ${config_file}
# Configuration file generated by pki-authority

[ req ]
default_md             = sha256
default_bits           = 4096
default_keyfile        = private/key.pem
prompt                 = no
encrypt_key            = no
distinguished_name     = ca_dn
utf8                   = yes
string_mask            = utf8only

[ ca_dn ]
EOF
        echo "${config_dn}" | tr "/" "\n" | sed \
            -e 's/^[Cc]=/countryName=/' \
            -e 's/^[Ss][Tt]=/stateOrProvinceName=/' \
            -e 's/^[Ll]=/localityName=/' \
            -e 's/^[Oo]=/organizationName=/' \
            -e 's/^[Oo][Uu]=/organizationalUnitName=/' \
            -e 's/^[Cc][Nn]=/commonName=/' >> ${config_file}

    fi
}

create_gnutls_request_config () {

    local config_file="${1:-config/gnutls-request.conf}"
    local config_dn="${2}"

    if [ -n "${config_file}" -a -n "${config_dn}" -a ! -r ${config_file} ] ; then

        cat << EOF > ${config_file}
# Configuration file generated by pki-authority

EOF
        echo "${config_dn}" | tr "/" "\n" | sed \
            -e 's/^[Cc]=/country = "/' \
            -e 's/^[Ss][Tt]=/state = "/' \
            -e 's/^[Ll]=/locality = "/' \
            -e 's/^[Oo]=/organization = "/' \
            -e 's/^[Oo][Uu]=/unit = "/' \
            -e 's/^[Cc][Nn]=/cn = "/' \
            -e 's/$/"/' >> ${config_file}

    fi
}

create_openssl_selfsign_config () {

    local config_file="${1:-config/openssl-selfsign.conf}"
    local config_name="${2}"
    local config_domain="${3}"
    local config_ca_type="${4}"

    if [ -n "${config_file}" -a ! -r ${config_file} ] ; then

        cat << EOF > ${config_file}
# Configuration file generated by pki-authority

[ default ]
name                    = ${config_name}
domain_suffix           = ${config_domain}
aia_url                 = http://\$name.\$domain_suffix/crt/
crl_url                 = http://\$name.\$domain_suffix/crl/
ocsp_url                = http://\$name.\$domain_suffix/ocsp/
default_ca              = ca_default
name_opt                = utf8,esc_ctrl,multiline,lname,align

[ ca_default ]
home                    = .
database                = \$home/database/index
serial                  = \$home/database/serial
crlnumber               = \$home/database/crlnumber
certificate             = \$home/subject/cert.pem
private_key             = \$home/private/key.pem
RANDFILE                = \$home/private/random
new_certs_dir           = \$home/certs
unique_subject          = no
copy_extensions         = none
default_days            = ${config['root_sign_days']:-$(( ${config['pki_default_sign_base']} * ${config['pki_default_root_sign_multiplier']} ))}
default_crl_days        = 365
default_md              = sha256
policy                  = policy_default
x509_extensions         = extension_default

[ crl_info ]
URI.0                   = \$crl_url

[ issuer_info ]
caIssuers;URI.0         = \$aia_url
OCSP;URI.0              = \$ocsp_url

[ extension_ocsp ]
authorityKeyIdentifier  = keyid:always
basicConstraints        = critical, CA:false
extendedKeyUsage        = OCSPSigning
keyUsage                = critical, digitalSignature
subjectKeyIdentifier    = hash

[ policy_default ]
countryName             = optional
stateOrProvinceName     = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = optional
emailAddress            = optional

[ extension_default ]
EOF

        if [ -z "${config_ca_type}" -o "${config_ca_type}" = "root" ] ; then
            cat << EOF >> ${config_file}
basicConstraints       = critical, CA:TRUE
keyUsage               = critical, keyCertSign, cRLSign
subjectKeyIdentifier   = hash

EOF
        elif [ "${config_ca_type}" = "service" ] ; then
            cat << EOF >> ${config_file}
authorityInfoAccess     = @issuer_info
basicConstraints        = critical, CA:TRUE, pathlen:0
crlDistributionPoints   = @crl_info
keyUsage                = critical, keyCertSign, cRLSign
subjectKeyIdentifier    = hash

EOF
        fi

    fi
}

create_gnutls_selfsign_config () {

    local config_file="${1:-config/gnutls-selfsign.conf}"
    local config_name="${2}"
    local config_domain="${3}"
    local config_ca_type="${4}"

    if [ -n "${config_file}" -a ! -r ${config_file} ] ; then

        cat << EOF > ${config_file}
# Configuration file generated by pki-authority

expiration_days = ${config['root_sign_days']:-$(( ${config['pki_default_sign_base']} * ${config['pki_default_root_sign_multiplier']} ))}

EOF

        if [ -z "${config_ca_type}" -o "${config_ca_type}" = "root" ] ; then
            cat << EOF >> ${config_file}
ca
cert_signing_key
crl_signing_key

EOF
        elif [ "${config_ca_type}" = "service" ] ; then
            cat << EOF >> ${config_file}
ca
cert_signing_key
crl_signing_key
path_len = 0

#ca_issuers_uri  = "http://${config_name}.${config_domain}/crt/"
crl_dist_points = "http://${config_name}.${config_domain}/crl/"
#ocsp_uri        = "http://${config_name}.${config_domain}/ocsp/"

EOF
        fi

    fi
}

create_openssl_sign_config () {

    local config_file="${1:-config/openssl-sign.conf}"
    local config_name="${2}"
    local config_domain="${3}"
    local config_ca_type="${4}"
    local config_issuer="${5}"

    if [ -n "${config_file}" -a ! -r ${config_file} ] ; then

        cat << EOF > ${config_file}
# Configuration file generated by pki-authority

[ default ]
name                    = ${config_name}
domain_suffix           = ${config_domain}
aia_url                 = http://\$name.\$domain_suffix/crt/
crl_url                 = http://\$name.\$domain_suffix/crl/
ocsp_url                = http://\$name.\$domain_suffix/ocsp/
default_ca              = ca_default
name_opt                = utf8,esc_ctrl,multiline,lname,align

[ ca_default ]
home                    = .
database                = \$home/database/index
serial                  = \$home/database/serial
crlnumber               = \$home/database/crlnumber
certificate             = \$home/subject/cert.pem
private_key             = \$home/private/key.pem
RANDFILE                = \$home/private/random
new_certs_dir           = \$home/certs
unique_subject          = no
policy                  = policy_default
x509_extensions         = extension_default
EOF

        if [ -z "${config_issuer}" -a -z "${config_ca_type}" -o "${config_ca_type}" = "root" ] ; then
            cat << EOF >> ${config_file}
copy_extensions         = none
default_days            = ${config['ca_sign_days']:-$(( ${config['pki_default_sign_base']} * ${config['pki_default_ca_sign_multiplier']} ))}
default_crl_days        = 365
default_md              = sha256

EOF
        elif [ -z "${config_issuer}" -a "${config_ca_type}" = "service" ] ; then
            cat << EOF >> ${config_file}
copy_extensions         = copy
default_days            = ${config['cert_sign_days']:-$(( ${config['pki_default_sign_base']} * ${config['pki_default_cert_sign_multiplier']} ))}
default_crl_days        = 365
default_md              = sha256

EOF
        elif [ -n "${config_issuer}" -a -z "${config_ca_type}" -o "${config_ca_type}" = "server" ] ; then
            cat << EOF >> ${config_file}
copy_extensions         = copy
default_days            = ${config['cert_sign_days']:-$(( ${config['pki_default_sign_base']} * ${config['pki_default_cert_sign_multiplier']} ))}
default_crl_days        = 30
default_md              = sha256

EOF
        fi

        cat << EOF >> ${config_file}
[ crl_info ]
URI.0                   = \$crl_url

[ issuer_info ]
caIssuers;URI.0         = \$aia_url
OCSP;URI.0              = \$ocsp_url

[ extension_ocsp ]
authorityKeyIdentifier  = keyid:always
basicConstraints        = critical, CA:false
extendedKeyUsage        = OCSPSigning
keyUsage                = critical, digitalSignature
subjectKeyIdentifier    = hash

EOF

        if [ -z "${config_issuer}" -a -z "${config_ca_type}" -o "${config_ca_type}" = "root" ] ; then
            cat << EOF >> ${config_file}
[ policy_default ]
countryName             = optional
stateOrProvinceName     = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = optional
emailAddress            = optional

EOF
        elif [ -z "${config_issuer}" -a "${config_ca_type}" = "service" ] ; then
            cat << EOF >> ${config_file}
[ policy_default ]
countryName             = optional
stateOrProvinceName     = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = optional
emailAddress            = optional

EOF
        elif [ -n "${config_issuer}" -a -z "${config_ca_type}" -o "${config_ca_type}" = "server" ] ; then
            cat << EOF >> ${config_file}
[ policy_default ]
countryName             = optional
stateOrProvinceName     = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = optional
emailAddress            = optional

EOF
        fi

        if [ -z "${config_issuer}" -a -z "${config_ca_type}" -o "${config_ca_type}" = "root" ] ; then
            cat << EOF >> ${config_file}
[ extension_default ]
authorityInfoAccess     = @issuer_info
authorityKeyIdentifier  = keyid:always
basicConstraints        = critical, CA:TRUE, pathlen:0
crlDistributionPoints   = @crl_info
keyUsage                = critical, keyCertSign, cRLSign
subjectKeyIdentifier    = hash

EOF
        elif [ -z "${config_issuer}" -a "${config_ca_type}" = "service" ] ; then
            cat << EOF >> ${config_file}
[ extension_default ]
authorityInfoAccess     = @issuer_info
authorityKeyIdentifier  = keyid:always, issuer:always
basicConstraints        = critical, CA:FALSE
crlDistributionPoints   = @crl_info
extendedKeyUsage        = clientAuth, serverAuth
keyUsage                = critical, digitalSignature, keyEncipherment
subjectKeyIdentifier    = hash

EOF
        elif [ -n "${config_issuer}" -a -z "${config_ca_type}" -o "${config_ca_type}" = "server" ] ; then
            cat << EOF >> ${config_file}
[ extension_default ]
authorityInfoAccess     = @issuer_info
authorityKeyIdentifier  = keyid:always, issuer:always
basicConstraints        = critical, CA:FALSE
crlDistributionPoints   = @crl_info
extendedKeyUsage        = clientAuth, serverAuth
keyUsage                = critical, digitalSignature, keyEncipherment
subjectKeyIdentifier    = hash

EOF
        fi

    fi
}

create_gnutls_sign_config () {

    local config_file="${1:-config/gnutls-sign.conf}"
    local config_name="${2}"
    local config_domain="${3}"
    local config_ca_type="${4}"
    local config_issuer="${5}"

    if [ -n "${config_file}" -a ! -r ${config_file} ] ; then

        cat << EOF > ${config_file}
# Configuration file generated by pki-authority

EOF

        if [ -z "${config_issuer}" -a -z "${config_ca_type}" -o "${config_ca_type}" = "root" ] ; then
            cat << EOF >> ${config_file}
expiration_days = ${config['ca_sign_days']:-$(( ${config['pki_default_sign_base']} * ${config['pki_default_ca_sign_multiplier']} ))}

ca
cert_signing_key
crl_signing_key
path_len = 0

#ca_issuers_uri  = "http://${config_name}.${config_domain}/crt/"
crl_dist_points = "http://${config_name}.${config_domain}/crl/"
#ocsp_uri        = "http://${config_name}.${config_domain}/ocsp/"

EOF
        elif [ -z "${config_issuer}" -a "${config_ca_type}" = "service" ] ; then
            cat << EOF >> ${config_file}
expiration_days = ${config['cert_sign_days']:-$(( ${config['pki_default_sign_base']} * ${config['pki_default_cert_sign_multiplier']} ))}
#copy_extensions         = copy

signing_key
encryption_key
tls_www_client
tls_www_server

#ca_issuers_uri  = "http://${config_name}.${config_domain}/crt/"
crl_dist_points = "http://${config_name}.${config_domain}/crl/"
#ocsp_uri        = "http://${config_name}.${config_domain}/ocsp/"

EOF
        elif [ -n "${config_issuer}" -a -z "${config_ca_type}" -o "${config_ca_type}" = "server" ] ; then
            cat << EOF >> ${config_file}
expiration_days = ${config['cert_sign_days']:-$(( ${config['pki_default_sign_base']} * ${config['pki_default_cert_sign_multiplier']} ))}
#copy_extensions         = copy

signing_key
encryption_key
tls_www_client
tls_www_server

#ca_issuers_uri  = "http://${config_name}.${config_domain}/crt/"
crl_dist_points = "http://${config_name}.${config_domain}/crl/"
#ocsp_uri        = "http://${config_name}.${config_domain}/ocsp/"

EOF
        fi
    fi

}

save_authority_config () {

    local config_file="${1:-config/authority.conf}"

    local ignored_config_vars=( pki_root pki_requests pki_authorities pki_ca_certificates public_file_group public_dir_group private_file_group private_dir_group )

    if [ -r ${config_file} ] ; then
        if $(grep -q -E "^#\s+Configuration\s+file\s+generated\s+by\s+pki-authority$" ${config_file}) ; then
            rm -f ${config_file}
        fi
    fi

    if [ ! -r ${config_file} ] ; then
        cat << EOF > ${config_file}
# Configuration file generated by pki-authority

EOF
        for key in "${!config[@]}" ; do
            if ! array_exists ignored_config_vars "${key}" ; then
                echo "config['${key}']='${config[${key}]}'" >> ${config_file}
            fi
        done
    fi
}

check_openssl_req_session_match () {
    local check_req="${1}"
    local check_session="${PKI_SESSION_TOKEN:-}"

    if [ -n "${check_req}" -a -n "${check_session}" ] ; then
        if [ -r ${check_req} ] ; then
            openssl asn1parse -in ${check_req} | grep -q "${check_session}"
            rc=$?
            return ${rc}
        fi
    fi
    return 1
}

check_gnutls_req_session_match () {
    local rc=$(check_openssl_req_session_match "${@}")
    return ${rc}
}

check_openssl_crt_ca_match () {
    local check_crt="${1}"
    local check_ca="${2}"
    local check_ca_issuer="${3:-}"

    if [ -n "${check_crt}" -a -n "${check_ca}" ] ; then
        if [ -r ${check_crt} -a -r ${check_ca} ] ; then
            if [ -n "${check_ca_issuer}" -a -r ${check_ca_issuer} ] ; then
                _openssl verify -CAfile ${check_ca_issuer} -untrusted ${check_ca} ${check_crt}
                local rc=$?
            else
                _openssl verify -CAfile ${check_ca} ${check_crt}
                local rc=$?
            fi
            return ${rc}
        fi
    fi
    return 1
}

check_gnutls_crt_ca_match () {
    local rc=$(check_openssl_crt_ca_match "${@}")
    return ${rc}
}

check_openssl_req_crt_match () {
    local check_req="${1}"
    local check_crt="${2}"

    local md5_req="$(openssl req  -noout -modulus -in ${check_req} | md5sum | awk '{print $1}')"
    local md5_crt="$(openssl x509 -noout -modulus -in ${check_crt} | md5sum | awk '{print $1}')"

    if [ "${md5_req}" = "${md5_crt}" ] ; then
        return 0
    else
        return 1
    fi
}

check_gnutls_req_crt_match () {
    local rc=$(check_openssl_req_crt_match "${@}")
    return ${rc}
}

sign_openssl_certificate () {
    local sign_config="${1}"
    local sign_request="${2}"
    local sign_out="${3}"
    local sign_sect="${4}"
    local sign_ext="${5}"

    if [ -n "${sign_config}" -a -n "${sign_request}" -a -n "${sign_out}" ] ; then

        if [ -r ${sign_request} -a ! -r ${sign_out} ] ; then

            _openssl ca -batch -notext \
                -in ${sign_request} -out ${sign_out}.tmp \
                -name ${sign_sect} -extensions ${sign_ext} \
                -config ${sign_config}

            if [ -r ${sign_out}.tmp -a -s ${sign_out}.tmp ] ; then
                mv ${sign_out}.tmp ${sign_out}
            fi

            if [ -d issuer ] ; then
                if [ -r $(dirname ${sign_out})/intermediate.pem ] ; then
                    if ! diff -q -N $(get_relative_path "subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/intermediate.pem > /dev/null ; then
                        ln -sf $(get_relative_path "subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/intermediate.pem
                    fi
                else
                    ln -sf $(get_relative_path "subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/intermediate.pem
                fi
                if [ -r $(dirname ${sign_out})/root.pem ] ; then
                    if ! diff -q -N $(get_relative_path "issuer/subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/root.pem > /dev/null ; then
                        ln -sf $(get_relative_path "issuer/subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/root.pem
                    fi
                else
                    ln -sf $(get_relative_path "issuer/subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/root.pem
                fi
            else
                if [ -r $(dirname ${sign_out})/root.pem ] ; then
                    if ! diff -q -N $(get_relative_path "subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/root.pem > /dev/null ; then
                        ln -sf $(get_relative_path "subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/root.pem
                    fi
                else
                    ln -sf $(get_relative_path "subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/root.pem
                fi
            fi
        fi
    fi
}

sign_openssl_host_certificate () {
    local sign_config="${1}"
    local sign_request="${2}"
    local sign_out="${3}"
    local sign_alt="${4:-}"

    if [ -n "${sign_config}" -a -n "${sign_request}" -a -n "${sign_out}" ] ; then

        if [ -r ${sign_request} -a ! -r ${sign_out} ] ; then

            _openssl ca -batch -notext \
                -in ${sign_request} -out ${sign_out}.tmp \
                -config ${sign_config}

            if [ -r ${sign_out}.tmp -a -s ${sign_out}.tmp ] ; then
                mv ${sign_out}.tmp ${sign_out}
            fi

            if [ -d issuer ] ; then
                if [ -r $(dirname ${sign_out})/intermediate.pem ] ; then
                    if ! diff -q -N $(get_relative_path "subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/intermediate.pem > /dev/null ; then
                        ln -sf $(get_relative_path "subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/intermediate.pem
                    fi
                else
                    ln -sf $(get_relative_path "subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/intermediate.pem
                fi
                if [ -r $(dirname ${sign_out})/root.pem ] ; then
                    if ! diff -q -N $(get_relative_path "issuer/subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/root.pem > /dev/null ; then
                        ln -sf $(get_relative_path "issuer/subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/root.pem
                    fi
                else
                    ln -sf $(get_relative_path "issuer/subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/root.pem
                fi
            else
                if [ -r $(dirname ${sign_out})/root.pem ] ; then
                    if ! diff -q -N $(get_relative_path "subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/root.pem > /dev/null ; then
                        ln -sf $(get_relative_path "subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/root.pem
                    fi
                else
                    ln -sf $(get_relative_path "subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/root.pem
                fi
            fi

            if [ -n "${sign_alt}" ] ; then

                if [ -d ${sign_alt}/issuer ] ; then
                    if [ -r $(dirname ${sign_out})/alt_intermediate.pem ] ; then
                        if ! diff -q -N $(get_relative_path "${sign_alt}/subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/alt_intermediate.pem > /dev/null ; then
                            ln -sf $(get_relative_path "${sign_alt}/subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/alt_intermediate.pem
                        fi
                    else
                        ln -sf $(get_relative_path "${sign_alt}/subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/alt_intermediate.pem
                    fi
                    if [ -r $(dirname ${sign_out})/root.pem ] ; then
                        if ! diff -q -N $(get_relative_path "${sign_alt}/issuer/subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/alt_root.pem > /dev/null ; then
                            ln -sf $(get_relative_path "${sign_alt}/issuer/subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/alt_root.pem
                        fi
                    else
                        ln -sf $(get_relative_path "${sign_alt}/issuer/subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/alt_root.pem
                    fi
                else
                    if [ -r $(dirname ${sign_out})/root.pem ] ; then
                        if ! diff -q -N $(get_relative_path "${sign_alt}/subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/alt_root.pem > /dev/null ; then
                            ln -sf $(get_relative_path "${sign_alt}/subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/alt_root.pem
                        fi
                    else
                        ln -sf $(get_relative_path "${sign_alt}/subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/alt_root.pem
                    fi
                fi

            fi
        fi
    fi
}

sign_gnutls_host_certificate () {
    local sign_config="${1}"
    local sign_request="${2}"
    local sign_out="${3}"
    local sign_alt="${4:-}"

    if [ -n "${sign_config}" -a -n "${sign_request}" -a -n "${sign_out}" ] ; then

        if [ -r ${sign_request} -a ! -r ${sign_out} ] ; then

            certtool --generate-certificate --template ${sign_config} \
                --load-request ${sign_request} --load-ca-privkey private/key.pem \
                --load-ca-certificate subject/cert.pem --outfile ${sign_out}.tmp

            if [ -r ${sign_out}.tmp -a -s ${sign_out}.tmp ] ; then
                mv ${sign_out}.tmp ${sign_out}
            fi

            if [ -d issuer ] ; then
                if [ -r $(dirname ${sign_out})/intermediate.pem ] ; then
                    if ! diff -q -N $(get_relative_path "subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/intermediate.pem > /dev/null ; then
                        ln -sf $(get_relative_path "subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/intermediate.pem
                    fi
                else
                    ln -sf $(get_relative_path "subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/intermediate.pem
                fi
                if [ -r $(dirname ${sign_out})/root.pem ] ; then
                    if ! diff -q -N $(get_relative_path "issuer/subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/root.pem > /dev/null ; then
                        ln -sf $(get_relative_path "issuer/subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/root.pem
                    fi
                else
                    ln -sf $(get_relative_path "issuer/subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/root.pem
                fi
            else
                if [ -r $(dirname ${sign_out})/root.pem ] ; then
                    if ! diff -q -N $(get_relative_path "subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/root.pem > /dev/null ; then
                        ln -sf $(get_relative_path "subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/root.pem
                    fi
                else
                    ln -sf $(get_relative_path "subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/root.pem
                fi
            fi

            if [ -n "${sign_alt}" ] ; then

                if [ -d ${sign_alt}/issuer ] ; then
                    if [ -r $(dirname ${sign_out})/alt_intermediate.pem ] ; then
                        if ! diff -q -N $(get_relative_path "${sign_alt}/subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/alt_intermediate.pem > /dev/null ; then
                            ln -sf $(get_relative_path "${sign_alt}/subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/alt_intermediate.pem
                        fi
                    else
                        ln -sf $(get_relative_path "${sign_alt}/subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/alt_intermediate.pem
                    fi
                    if [ -r $(dirname ${sign_out})/root.pem ] ; then
                        if ! diff -q -N $(get_relative_path "${sign_alt}/issuer/subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/alt_root.pem > /dev/null ; then
                            ln -sf $(get_relative_path "${sign_alt}/issuer/subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/alt_root.pem
                        fi
                    else
                        ln -sf $(get_relative_path "${sign_alt}/issuer/subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/alt_root.pem
                    fi
                else
                    if [ -r $(dirname ${sign_out})/root.pem ] ; then
                        if ! diff -q -N $(get_relative_path "${sign_alt}/subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/alt_root.pem > /dev/null ; then
                            ln -sf $(get_relative_path "${sign_alt}/subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/alt_root.pem
                        fi
                    else
                        ln -sf $(get_relative_path "${sign_alt}/subject/cert.pem" "$(dirname ${sign_out})") $(dirname ${sign_out})/alt_root.pem
                    fi
                fi

            fi
        fi
    fi
}

sign_openssl_ca_certificate () {
    local sign_config="${1}"
    local sign_request="${2}"
    local sign_out="${3}"

    if [ -n "${sign_config}" -a -n "${sign_request}" -a -n "${sign_out}" ] ; then
        if [ -r ${sign_request} -a ! -r ${sign_out} ] ; then

            _openssl ca -batch -notext \
                -in ${sign_request} -out ${sign_out}.tmp \
                -config ${sign_config}

            if [ -r ${sign_out}.tmp -a -s ${sign_out}.tmp ] ; then
                mv ${sign_out}.tmp ${sign_out}
            fi

        fi
    fi

}

sign_gnutls_ca_certificate () {
    local sign_config="${1}"
    local sign_request="${2}"
    local sign_out="${3}"

    if [ -n "${sign_config}" -a -n "${sign_request}" -a -n "${sign_out}" ] ; then
        if [ -r ${sign_request} -a ! -r ${sign_out} ] ; then

            certtool --generate-certificate --template ${sign_config} \
                --load-request ${sign_request} --load-ca-privkey private/key.pem \
                --load-ca-certificate subject/cert.pem --outfile ${sign_out}.tmp

            if [ -r ${sign_out}.tmp -a -s ${sign_out}.tmp ] ; then
                mv ${sign_out}.tmp ${sign_out}
            fi

        fi
    fi

}

sign_openssl_root_certificate () {
    local sign_config="${1}"
    local sign_request="${2}"
    local sign_out="${3}"

    if [ -n "${sign_config}" -a -n "${sign_request}" -a -n "${sign_out}" ] ; then
        if [ -r ${sign_request} -a ! -r ${sign_out} ] ; then

            _openssl ca -selfsign -batch -notext \
                -in ${sign_request} -out ${sign_out}.tmp \
                -config ${sign_config}

            if [ -r ${sign_out}.tmp -a -s ${sign_out}.tmp ] ; then
                mv ${sign_out}.tmp ${sign_out}
            fi

        fi
    fi

}

sign_gnutls_root_certificate () {
    local sign_config="${1}"
    local sign_request="${2}"
    local sign_out="${3}"

    if [ -n "${sign_config}" -a -n "${sign_request}" -a -n "${sign_out}" ] ; then
        if [ -r ${sign_request} -a ! -r ${sign_out} ] ; then

            certtool --generate-self-signed --template ${sign_config} \
                --load-request ${sign_request} --load-privkey private/key.pem \
                --outfile ${sign_out}.tmp

            if [ -r ${sign_out}.tmp -a -s ${sign_out}.tmp ] ; then
                mv ${sign_out}.tmp ${sign_out}
            fi

        fi
    fi

}

generate_openssl_request () {

    local req_config="${1}"
    local req_out="${2}"

    if [ -n "${req_config}" -a -n "${req_out}" ] ; then
        if [ -r ${req_config} -a ! -r ${req_out} ] ; then

            local req_keyfile=$(grep -E '^default_keyfile\s+=\s+' ${req_config} | awk '{print $3}')
            if [ -n "${req_keyfile}" ] ; then
                openssl req -new -key ${req_keyfile} -config ${req_config} -out ${req_out}.tmp
            else
                openssl req -new -config ${req_config} -out ${req_out}.tmp
            fi
            test -r ${req_out}.tmp && mv ${req_out}.tmp ${req_out}

        fi
    fi

}

generate_gnutls_request () {

    local req_config="${1}"
    local req_out="${2}"

    if [ -n "${req_config}" -a -n "${req_out}" ] ; then
        if [ -r ${req_config} -a ! -r ${req_out} ] ; then

            certtool --generate-request --template ${req_config} \
                     --load-privkey private/key.pem --outfile ${req_out}.tmp
            # Remove text output from the request
            test -r ${req_out}.tmp && sed -n -i '/-----BEGIN NEW CERTIFICATE REQUEST-----/,$ p' ${req_out}.tmp
            test -r ${req_out}.tmp && mv ${req_out}.tmp ${req_out}

        fi
    fi

}

generate_openssl_serial () {
    local serial_file="${1}"
    local serial_length="${2:-16}"

    if [ -n "${serial_file}" -a ! -r "${serial_file}" ] ; then
        openssl rand -hex ${serial_length} > ${serial_file}
    fi
}

# GnuTLS doesn't use this, so do nothing
generate_gnutls_serial () {
    return 0
}

generate_openssl_rsa_private_key () {

    local key_file="${1:-private/key.pem}"
    local key_size="${2:-4096}"
    local key_group="${3:-${config['private_file_group']}}"

    test -r ${key_file} || openssl genrsa -out ${key_file}.tmp ${key_size}

    if [ -r ${key_file}.tmp ] ; then
        chmod ${config['private_file_mode']} ${key_file}.tmp
        chgrp ${key_group} ${key_file}.tmp
        mv ${key_file}.tmp ${key_file}
    fi

    if [ "$(stat -c %a ${key_file})" != "${config['private_file_mode']}" ] ; then
        chmod ${config['private_file_mode']} ${key_file}
    fi
    if [ "$(stat -c %G ${key_file})" != "${key_group}" ] ; then
        chgrp ${key_group} ${key_file}
    fi

}

generate_gnutls_rsa_private_key () {

    local key_file="${1:-private/key.pem}"
    local key_size="${2:-4096}"
    local key_group="${3:-${config['private_file_group']}}"

    if ! [ -r ${key_file} ] ; then
        if ! awk -v ver="$(certtool --version | head -n 1 | awk '{print $NF}')" 'BEGIN { if (ver < 3.3.0) exit 1; }'; then
            certtool --generate-privkey --outfile ${key_file}.tmp --bits ${key_size}
        else
            certtool --generate-privkey --rsa --outfile ${key_file}.tmp --bits ${key_size}
        fi
    fi

    if [ -r ${key_file}.tmp ] ; then
        sed -n -i '/-----BEGIN RSA PRIVATE KEY-----/,$ p' ${key_file}.tmp
        chmod ${config['private_file_mode']} ${key_file}.tmp
        chgrp ${key_group} ${key_file}.tmp
        mv ${key_file}.tmp ${key_file}
    fi

    if [ "$(stat -c %a ${key_file})" != "${config['private_file_mode']}" ] ; then
        chmod ${config['private_file_mode']} ${key_file}
    fi
    if [ "$(stat -c %G ${key_file})" != "${key_group}" ] ; then
        chgrp ${key_group} ${key_file}
    fi

}

create_public_directories () {

    local dir_group="${1}"
    if [ -n "${dir_group}" ] ; then
        shift
    else
        return 1
    fi

    local directories=( ${@} )

    for directory in ${directories[@]} ; do
        test -d ${directory} || mkdir -p -m ${config['public_dir_mode']} ${directory}
        if [ "$(stat -c %a ${directory})" != "${config['public_dir_mode']}" ] ; then
            chmod ${config['public_dir_mode']} ${directory}
        fi
        if [ "$(stat -c %G ${directory})" != "${dir_group}" ] ; then
            chgrp ${dir_group} ${directory}
        fi
    done

}

create_private_directories () {

    local dir_group="${1}"
    if [ -n "${dir_group}" ] ; then
        shift
    else
        return 1
    fi

    local directories=( ${@} )

    for directory in ${directories[@]} ; do
        test -d ${directory} || mkdir -p -m ${config['private_dir_mode']} ${directory}
        if [ "$(stat -c %a ${directory})" != "${config['private_dir_mode']}" ] ; then
            chmod ${config['private_dir_mode']} ${directory}
        fi
        if [ "$(stat -c %G ${directory})" != "${dir_group}" ] ; then
            chgrp ${dir_group} ${directory}
        fi
    done

}

sub_sign () {

    local -A args

    local optspec=":hn-:"
    while getopts "${optspec}" optchar; do
        case "${optchar}" in
            -)
                case "${OPTARG}" in
                    name)
                        args["name"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    name=*)
                        args["name"]=${OPTARG#*=}
                        ;;
                    req)
                        args["req"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    req=*)
                        args["req"]=${OPTARG#*=}
                        ;;
                    out)
                        args["out"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    out=*)
                        args["out"]=${OPTARG#*=}
                        ;;
                    *)
                        if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then
                            echo "Unknown option --${OPTARG}" >&2
                        fi
                        ;;
                esac
                ;;
            h)
                echo "usage: ${script} init <-n|--name[=]authority>" >&2
                exit 2
                ;;
            n)
                args["name"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                ;;
            *)
                if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then
                    echo "Non-option argument: '-${OPTARG}'" >&2
                fi
                ;;
        esac
    done

    local name=""
    local req=""
    local out=""

    if key_exists args "name" ; then

        enter_authority ${args['name']}

        local library="${config['pki_library']}"
        local input="$(get_absolute_path ${args['req']})"
        local output="$(get_absolute_path ${args['out']})"

        sign_${library}_certificate "config/${library}-sign.conf" "${input}" "${output}"

    fi
}

sub_sign-by-host () {

    if [ $# -gt 0 ] ; then
        if [ -d requests -a -d realms ] ; then
            for authority in requests/* ; do
                if [ -r authorities/$(basename ${authority})/subject/cert.pem ] ; then
                    for host in ${authority}/* ; do
                        for play_host in ${@} ; do
                            if [ "$(basename ${host})" = "${play_host}" ] ; then
                                for realm in ${host}/* ; do
                                    if [ -r ${realm}/request.pem ] ; then

                                        local input="$(get_absolute_path ${realm}/request.pem)"
                                        local output="$(get_absolute_path realms/by-host/$(basename ${play_host})/$(basename ${realm})/internal/cert.pem)"

                                        enter_authority $(basename ${authority})

                                        local alt_authority=""

                                        if [ -n "${config['alt_authority']}" -a -d ../${config['alt_authority']} ] ; then
                                            alt_authority="$(get_absolute_path ../${config['alt_authority']})"
                                        fi

                                        if [ -r ${output} ] && check_${config['pki_library']}_req_session_match "${input}" ; then
                                            rm -f ${output}
                                        fi

                                        if [ -r ${output} ] && ! check_${config['pki_library']}_req_crt_match "${input}" "${output}" ; then
                                            rm -f ${output}
                                            if ! check_${config['pki_library']}_req_session_match "${input}" ; then
                                                rm -f ${input}
                                            fi
                                        fi

                                        if [ -r ${output} ] && ! check_${config['pki_library']}_crt_ca_match "${output}" "subject/cert.pem" "issuer/subject/cert.pem" ; then
                                            rm -f ${output}
                                            if ! check_${config['pki_library']}_req_session_match "${input}" ; then
                                                rm -f ${input}
                                            fi
                                        fi

                                        if [ ! -r ${output} ] && ! check_${config['pki_library']}_req_session_match "${input}" ; then
                                            rm -f ${input}
                                        fi

                                        if [ -r ${input} ] ; then
                                            sign_${config['pki_library']}_host_certificate "config/${config['pki_library']}-sign.conf" "${input}" "${output}" "${alt_authority}"
                                        fi

                                        cd - > /dev/null

                                    fi
                                done
                            fi
                        done
                    done
                fi
            done
        fi
    fi

}

sub_new-ca () {

    local -A args

    local optspec=":hn-:"
    while getopts "${optspec}" optchar; do
        case "${optchar}" in
            -)
                case "${OPTARG}" in
                    name)
                        args["name"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    name=*)
                        args["name"]=${OPTARG#*=}
                        ;;
                    system-ca)
                        args["system_ca"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    system-ca=*)
                        args["system_ca"]=${OPTARG#*=}
                        ;;
                    alt-authority)
                        args["alt_authority"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    alt-authority=*)
                        args["alt_authority"]=${OPTARG#*=}
                        ;;
                    library)
                        args["pki_library"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    library=*)
                        args["pki_library"]=${OPTARG#*=}
                        ;;
                    subdomain)
                        args["subdomain"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    subdomain=*)
                        args["subdomain"]=${OPTARG#*=}
                        ;;
                    subject)
                        args["subject"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    subject=*)
                        args["subject"]=${OPTARG#*=}
                        ;;
                    type)
                        args["ca_type"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    type=*)
                        args["ca_type"]=${OPTARG#*=}
                        ;;
                    issuer-name)
                        args["issuer_name"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    issuer-name=*)
                        args["issuer_name"]=${OPTARG#*=}
                        ;;
                    domain)
                        args["domain"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    domain=*)
                        args["domain"]=${OPTARG#*=}
                        ;;
                    root-sign-days)
                        args["root_sign_days"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    root-sign-days=*)
                        args["root_sign_days"]=${OPTARG#*=}
                        ;;
                    ca-sign-days)
                        args["ca_sign_days"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    ca-sign-days=*)
                        args["ca_sign_days"]=${OPTARG#*=}
                        ;;
                    cert-sign-days)
                        args["cert_sign_days"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    cert-sign-days=*)
                        args["cert_sign_days"]=${OPTARG#*=}
                        ;;
                    *)
                        if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then
                            echo "Unknown option --${OPTARG}" >&2
                        fi
                        ;;
                esac
                ;;
            h)
                echo "usage: ${script} new-ca <-n|--name[=]authority>" >&2
                exit 2
                ;;
            n)
                args["name"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                ;;
            *)
                if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then
                    echo "Non-option argument: '-${OPTARG}'" >&2
                fi
                ;;
        esac
    done

    if key_exists args "name" ; then

        enter_authority ${args['name']}

        local config_changed="false"

        for key in "${!args[@]}" ; do
            if [ ! -z "${args[${key}]+x}" ] ; then
                if key_exists config "${key}" ; then
                    if [ "${config[${key}]}" != "${args[${key}]}" ] ; then
                        local config_changed="true"
                        config["${key}"]="${args[${key}]}"
                    fi
                else
                    local config_changed="true"
                    config["${key}"]="${args[${key}]}"
                fi
            fi
        done

        create_public_directories ${config['public_dir_group']} config database subject certs requests signed
        create_private_directories ${config['private_dir_group']} private

        if [ "${config_changed}" = "true" ] ; then
            save_authority_config config/authority.conf
        fi

        local name="${config['name']}"
        local library="${config['pki_library']}"

        generate_${library}_rsa_private_key private/key.pem

        create_${library}_request_config config/${library}-request.conf "${config['subject']}"
        if [ -z "${config['issuer_name']}" ] ; then
            create_${library}_selfsign_config config/${library}-selfsign.conf "${config['subdomain']}" "${config['domain']}" "${config['ca_type']}"
        fi
        create_${library}_sign_config config/${library}-sign.conf "${config['subdomain']}" "${config['domain']}" "${config['ca_type']}" "${config['issuer_name']}"
        if [ -n "${config['issuer_name']}" ] ; then
            update_symlink issuer ../${config['issuer_name']}
        fi

        if [ "${library}" = "openssl" ] ; then
            generate_${library}_serial database/serial
            test -r database/index || touch database/index
        fi

        generate_${library}_request config/${library}-request.conf subject/request.pem

        if [ -z "${config['issuer_name']}" -a ! -L issuer ] ; then

            sign_${library}_root_certificate config/${library}-selfsign.conf \
                subject/request.pem subject/cert.pem

        elif [ -n "${config['issuer_name']}" -a -L issuer ] ; then
            local subject_dir="$(pwd)"
            cd issuer
            sign_${library}_ca_certificate config/${library}-sign.conf \
                ${subject_dir}/subject/request.pem ${subject_dir}/subject/cert.pem
            cd - > /dev/null
        fi

        if [ "${config['system_ca']}" = "true" -a -n "${config['pki_ca_certificates']}" -a -n "${config['subdomain']}" -a -z "${config['issuer_name']}" -a ! -L issuer ] ; then
            if [ -r $(dirname ${config['pki_ca_certificates']})/${config['subdomain']}.${config['domain']}.crt ] ; then
                if ! diff -q -N $(get_relative_path "subject/cert.pem" "${config['pki_ca_certificates']}") ${config['pki_ca_certificates']}/${config['subdomain']}.${config['domain']}.crt > /dev/null ; then
                    ln -sf $(get_relative_path "subject/cert.pem" "${config['pki_ca_certificates']}") ${config['pki_ca_certificates']}/${config['subdomain']}.${config['domain']}.crt
                fi
            else
                ln -sf $(get_relative_path "subject/cert.pem" "${config['pki_ca_certificates']}") ${config['pki_ca_certificates']}/${config['subdomain']}.${config['domain']}.crt
            fi
        fi

    fi
}

sub_init () {

    local -A args

    local optspec=":hn-:"
    while getopts "${optspec}" optchar; do
        case "${optchar}" in
            -)
                case "${OPTARG}" in
                    name)
                        args["name"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    name=*)
                        args["name"]=${OPTARG#*=}
                        ;;
                    library)
                        args["pki_library"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    library=*)
                        args["pki_library"]=${OPTARG#*=}
                        ;;
                    default-sign-base)
                        args["pki_default_sign_base"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    default-sign-base=*)
                        args["pki_default_sign_base"]=${OPTARG#*=}
                        ;;
                    root-sign-multiplier)
                        args["pki_default_root_sign_multiplier"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    root-sign-multiplier=*)
                        args["pki_default_root_sign_multiplier"]=${OPTARG#*=}
                        ;;
                    ca-sign-multiplier)
                        args["pki_default_ca_sign_multiplier"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    ca-sign-multiplier=*)
                        args["pki_default_ca_sign_multiplier"]=${OPTARG#*=}
                        ;;
                    cert-sign-multiplier)
                        args["pki_default_cert_sign_multiplier"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    cert-sign-multiplier=*)
                        args["pki_default_cert_sign_multiplier"]=${OPTARG#*=}
                        ;;
                    *)
                        if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then
                            echo "Unknown option --${OPTARG}" >&2
                        fi
                        ;;
                esac
                ;;
            h)
                echo "usage: ${script} init <-n|--name[=]authority>" >&2
                exit 2
                ;;
            n)
                args["name"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                ;;
            *)
                if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then
                    echo "Non-option argument: '-${OPTARG}'" >&2
                fi
                ;;
        esac
    done

    if key_exists args "name" ; then

        enter_authority ${args['name']}

        for key in "${!args[@]}" ; do
            if [ ! -z "${args[${key}]+x}" ] ; then
                if key_exists config "${key}" ; then
                    if [ "${config[${key}]}" != "${args[${key}]}" ] ; then
                        local config_changed="true"
                        config["${key}"]="${args[${key}]}"
                    fi
                else
                    local config_changed="true"
                    config["${key}"]="${args[${key}]}"
                fi
            fi
        done

        create_public_directories ${config['public_dir_group']} config database subject certs requests signed
        create_private_directories ${config['private_dir_group']} private

        if [ "${config_changed}" = "true" ] ; then
            save_authority_config config/authority.conf
        fi

    fi
}

subcommand=""

initialize_environment

if [ $# -gt 0 ] ; then

    subcommand="${1}"

    if [ -n "${subcommand}" ] ; then
        case "${subcommand}" in
            init|new-ca|sign|sign-by-host)
                shift
                sub_${subcommand} "${@}"
                ;;

            *)
                echo "${script}: Error: unknown subcommand '${subcommand}'" >&2
                print_usage
                exit 1
                ;;
        esac
    fi

else
    print_usage
    exit 1
fi

