#!/usr/bin/env bash

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

set -eu -o pipefail

umask 022

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

state=()

# Display a message if script is used interactively, otherwise send it to syslog
log_message () {
    local msg="${1:-}"

    if [ -n "${msg}" ] ; then
        if tty -s > /dev/null 2>&1 ; then
            echo "${script_name}: ${msg}" 1>&2
        elif type logger > /dev/null 2>&1 ; then
            logger -t "${script_name}[${pid}]" "${msg}"
        fi
    fi
}

# Clean up pidfile
clean_up () {
    local pidfile="${1}"

    if [ -n "${pidfile}" -a -r ${pidfile} ] ; then
        rm -f ${pidfile}
    fi
    exit 0
}

# Wait for the specified process to exit
wait_for_pid () {
    local pidfile="${1}"

    if [ -n "${pidfile}" -a -r ${pidfile} ] ; then
        local wait_pid="$(<${pidfile})"
        while kill -0 ${wait_pid} > /dev/null 2>&1 ; do
            log_message "Waiting for PID ${wait_pid} to finish"
            sleep $(( ( $RANDOM % 30 ) + 5 ))
        done
        test -f ${pidfile} && rm -f ${pidfile} || true
    fi
}

create_lock () {
    local pidfile="${1}"

    # Try and lock the script operation
    echo ${pid} > ${pidfile}
    sleep 0.5

    # Exclusive lock failed
    if [ "x$(cat ${pidfile})" != "x"${pid} ] ; then
        log_message "PKI management started by PID $(<${pidfile})"
        exit 0
    fi

    # Exclusive lock succeded
    trap "clean_up ${pidfile}" EXIT
}

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

# 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_authority_preference"]="external/acme/internal"

    config["acme_user"]="${PKI_ACME_USER:-pki-acme}"
    config["acme_group"]="${PKI_ACME_GROUP:-pki-acme}"

    config["private_group"]="${PKI_PRIVATE_GROUP:-ssl-cert}"

    config["acme_challenge_dir"]="/srv/www/sites/acme/public/.well-known/acme-challenge"

    config["acme_client"]="acme_tiny"

    local -A acme_client_script_map
    acme_client_script_map=(
        ["acme_tiny"]="/usr/local/lib/pki/acme-tiny"
    )

    config["acme_client_script"]="${acme_client_script_map[${config['acme_client']}]}"

    config["acme_ca"]="le-staging"

    local -A acme_ca_api_map
    acme_ca_api_map=(
        ["le-live"]="https://acme-v01.api.letsencrypt.org"
        ["le-staging"]="https://acme-staging.api.letsencrypt.org"
    )

    config["acme_ca_api"]="${acme_ca_api_map[${config['acme_ca']}]}"

    config["acme_expiration_days"]="30"

    config["acme_expiration_seconds"]="$(( 60 * 60 * 24 * ${config['acme_expiration_days']} ))"

    config["acme_root_ca_path"]="/usr/share/ca-certificates"
    config["acme_root_ca_file"]="mozilla/DST_Root_CA_X3.crt"

    config["pki_default_ca_bundle"]="/etc/ssl/certs/ca-certificates.crt"
    config["pki_default_subject"]="CN=$(hostname -f)"
    config["pki_default_fqdn"]="$(hostname -f)"
    config["pki_default_domain"]="$(get_dnsdomainname)"
    config["pki_default_subdomains"]="_wildcard_"

    config["pki_dhparam"]="false"
    config["dhparam_file"]=""

    config["internal_expiration_days"]="14"
    config["internal_expiration_seconds"]="$(( 60 * 60 * 24 * ${config['internal_expiration_days']} ))"

    config["subject"]=""
    config["domains"]=""
    config["subdomains"]=""
    config["subject_alt_names"]=""

    config["acme_default_subject"]="CN=$(get_dnsdomainname)"
    config["acme_default_domain"]="$(get_dnsdomainname)"
    config["acme_default_subdomains"]="www/ftp/mail/smtp/imap"

    config["acme_subject"]=""
    config["acme_domains"]=""
    config["acme_subdomains"]=""
    config["acme_alt_names"]=""

    if [ "$EUID" -eq 0 ] ; then

        config["lock_dir"]="/run"

        config["pki_root"]="${PKI_ROOT:-/etc/pki}"
        config["pki_library"]="${PKI_LIBRARY:-gnutls}"
        config["pki_acme"]="${PKI_ACME:-true}"
        config["pki_acme_library"]="${PKI_ACME_LIBRARY:-openssl}"
        config["pki_internal"]="${PKI_INTERNAL:-true}"

        config["pki_realms"]="${config['pki_root']}/realms"
        config["pki_hooks"]="${config['pki_root']}/hooks"

        config["public_dir_group"]="root"
        config["public_file_group"]="root"

        config["realm_dir_group"]="root"
        config["realm_file_group"]="root"

        if $(getent group ${config['private_group']} > /dev/null) ; then
            config["private_dir_group"]="${config['private_group']}"
            config["private_file_group"]="${config['private_group']}"
        else
            config["private_dir_group"]="root"
            config["private_file_group"]="root"
        fi

        if $(getent group ${config['acme_group']} > /dev/null) ; then
            config["acme_dir_group"]="${config['acme_group']}"
            config["acme_file_group"]="${config['acme_group']}"
        else
            config["acme_dir_group"]="root"
            config["acme_file_group"]="root"
        fi

        config["public_dir_mode"]="755"
        config["acme_dir_mode"]="775"
        config["private_dir_mode"]="750"
        config["realm_dir_mode"]="700"

        config["public_file_mode"]="644"
        config["acme_file_mode"]="640"
        config["private_file_mode"]="640"
        config["realm_file_mode"]="400"

    else

        config["lock_dir"]="${XDG_RUNTIME_DIR:-/tmp}"

        config["pki_root"]="${PKI_ROOT:-${XDG_CONFIG_HOME:-${HOME}/.config}/pki}"
        config["pki_library"]="${PKI_LIBRARY:-gnutls}"
        config["pki_acme"]="${PKI_ACME:-true}"
        config["pki_acme_library"]="${PKI_ACME_LIBRARY:-openssl}"
        config["pki_internal"]="${PKI_INTERNAL:-true}"

        config["pki_realms"]="${config['pki_root']}/realms"
        config["pki_hooks"]="${config['pki_root']}/hooks"

        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["realm_dir_group"]="$(id -gn)"
        config["realm_file_group"]="$(id -gn)"

        config["acme_dir_group"]="$(id -gn)"
        config["acme_file_group"]="$(id -gn)"

        config["public_dir_mode"]="755"
        config["acme_dir_mode"]="775"
        config["private_dir_mode"]="700"
        config["realm_dir_mode"]="700"

        config["public_file_mode"]="644"
        config["acme_file_mode"]="640"
        config["private_file_mode"]="600"
        config["realm_file_mode"]="400"

    fi

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

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

}

check_openssl_key_req_match () {
    local check_key="${1}"
    local check_req="${2}"

    local md5_key="$(openssl rsa -noout -modulus -in ${check_key} | md5sum | awk '{print $1}')"
    local md5_req="$(openssl req -noout -modulus -in ${check_req} | md5sum | awk '{print $1}')"

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

check_openssl_key_crt_match () {
    local check_key="${1}"
    local check_crt="${2}"

    if [ -r ${check_key} -a -r ${check_crt} ] ; then
        local md5_key="$(openssl rsa  -noout -modulus -in ${check_key} | md5sum | awk '{print $1}')"
        local md5_crt="$(openssl x509 -noout -modulus -in ${check_crt} | md5sum | awk '{print $1}')"

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

update_file_signature () {
    local check_file="${1}"

    if [ -n "${check_file}" -a -r ${check_file} ] ; then
        if [ -r ${check_file}.sig ] ; then
            set +e
            md5sum --quiet --status -c ${check_file}.sig
            local rc=$?
            set -e
            if [ ${rc} -ne 0 ] ; then
                rm -rf ${check_file}.sig
                md5sum ${check_file} > ${check_file}.sig
            fi
        else
            md5sum ${check_file} > ${check_file}.sig
        fi
    fi
}

check_file_signature () {
    local check_file="${1}"

    if [ -n "${check_file}" -a -r ${check_file} ] ; then
        if [ -r ${check_file}.sig ] ; then
            set +e
            md5sum --quiet --status -c ${check_file}.sig
            local rc=$?
            set -e
            return ${rc}
        fi
    fi
    return 1
}

enter_realm () {

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

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

    if [ ! -d ${config['pki_realms']}/${realm} ] ; then
        mkdir -p -m ${config['public_dir_mode']} ${config['pki_realms']}/${realm}
        state+=('new-realm')
    fi

    cd ${config['pki_realms']}/${realm}
    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}
}

run_pki_hooks () {

    if [ -n "${state:-}" -a -d ${config['pki_hooks']} ] ; then

        export PKI_SCRIPT_REALM="${config['name']}"
        export PKI_SCRIPT_FQDN="${config['pki_default_fqdn']}"
        export PKI_SCRIPT_SUBJECT="${config['subject']:-${config['pki_default_subject']}}"
        export PKI_SCRIPT_DOMAINS="${config['domains']:-${config['pki_default_domain']}}"
        export PKI_SCRIPT_SUBDOMAINS="${config['subdomains']:-${config['pki_default_subdomains']}}"
        export PKI_SCRIPT_PRIVATE_KEY="$(get_absolute_path private/key.pem)"

        export PKI_SCRIPT_DEFAULT_CRT="$(get_absolute_path default.crt)"
        export PKI_SCRIPT_DEFAULT_KEY="$(get_absolute_path default.key)"
        export PKI_SCRIPT_DEFAULT_PEM="$(get_absolute_path default.pem)"

        local current_state=$(printf ",%s" "${state[@]:-}")
        local current_state="${current_state:1}"
        export PKI_SCRIPT_STATE="${current_state}"

        cd ${config['pki_hooks']}
        ( run-parts . )
        cd - > /dev/null 2>&1

    fi
}

run_external_script () {

    if [ -r external/script ] ; then

        if [ ! -r private/realm_key.pem ] ; then
            generate_${config['pki_library']}_rsa_realm_key private/realm_key.pem
            if [ ! -r private/key.pem ] ; then
                install -g ${config['private_file_group']} -m ${config['private_file_mode']} /dev/null private/key.pem.tmp
                cat private/realm_key.pem >> private/key.pem.tmp
                mv private/key.pem.tmp private/key.pem
            fi
        fi

        export PKI_SCRIPT_REALM="${config['name']}"
        export PKI_SCRIPT_FQDN="${config['pki_default_fqdn']}"
        export PKI_SCRIPT_SUBJECT="${config['subject']:-${config['pki_default_subject']}}"
        export PKI_SCRIPT_DOMAINS="${config['domains']:-${config['pki_default_domain']}}"
        export PKI_SCRIPT_SUBDOMAINS="${config['subdomains']:-${config['pki_default_subdomains']}}"
        export PKI_SCRIPT_PRIVATE_KEY="$(get_absolute_path private/key.pem)"

        export PKI_SCRIPT_DEFAULT_CRT="$(get_absolute_path default.crt)"
        export PKI_SCRIPT_DEFAULT_KEY="$(get_absolute_path default.key)"
        export PKI_SCRIPT_DEFAULT_PEM="$(get_absolute_path default.pem)"

        local current_state=$(printf ",%s" "${state[@]:-}")
        local current_state="${current_state:1}"
        export PKI_SCRIPT_STATE="${current_state}"

        test -x external/script || chmod ug+x external/script

        cd external
        ( ./script )
        cd - > /dev/null 2>&1
    fi
}

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

        if [ ! -e ${symlink_target} ] ; then
            rm -f ${symlink_target}
        fi

    fi
}

create_openssl_config () {

    local config_file="${1:-config/openssl.conf}"
    local config_type="${2:-internal}"
    local req_dn="${3}"
    local req_domains="${4}"
    local req_subdomains="${5:-}"
    local req_san="${6:-}"

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

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

[ req ]
default_md         = sha256
default_bits       = 2048
default_keyfile    = private/key.pem
prompt             = no
encrypt_key        = no
distinguished_name = req_dn
req_extensions     = ext_req
utf8               = yes
string_mask        = utf8only
EOF

        if [ -n "${PKI_SESSION_TOKEN:-}" -a "${config_type}" = "internal" ] ; then
            cat << EOF >> ${config_file}
attributes         = req_attributes

[ req_attributes ]
challengePassword  = \${ENV::PKI_SESSION_TOKEN}
EOF
        fi

        cat << EOF >> ${config_file}

[ req_dn ]
EOF

        echo "${req_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}

        cat << EOF >> ${config_file}

[ ext_req ]
basicConstraints   = CA:FALSE
keyUsage           = digitalSignature, keyEncipherment
extendedKeyUsage   = serverAuth, clientAuth
EOF
        if [ -n "${req_domains}" -o -n "${req_subdomains}" -o -n "${req_san}" ] ; then
            cat << EOF >> ${config_file}
subjectAltName     = @ext_req_san

[ ext_req_san ]
EOF

            local domains=( $(echo "${req_domains}" | tr "/" " ") )
            local subdomains=( $(echo "${req_subdomains}" | tr "/" " ") )

            local san_ip=(    $( echo "${req_san}" | tr "|" "\n" | awk '/^[Ii][Pp]:/ {print;}' | sed -e 's/^[Ii][Pp]://' | xargs ) )
            local san_dns=(   $( echo "${req_san}" | tr "|" "\n" | awk '/^[Dd][Nn][Ss]:/ {print;}' | sed -e 's/^[Dd][Nn][Ss]://' | xargs ) )
            local san_uri=(   $( echo "${req_san}" | tr "|" "\n" | awk '/^[Uu][Rr][Ii]:/ {print;}' | sed -e 's/^[Uu][Rr][Ii]://' | xargs ) )
            local san_email=( $( echo "${req_san}" | tr "|" "\n" | awk '/^[Ee][Mm][Aa][Ii][Ll]:/ {print;}' | sed -e 's/^[Ee][Mm][Aa][Ii][Ll]://' | xargs ) )

            local all_dns=( ${domains[@]:-} )

            if [ ${#subdomains[@]} -gt 0 ] ; then
                for i in "${!domains[@]}" ; do
                    for j in "${!subdomains[@]}" ; do
                        if [ "${subdomains[${j}]}" == "_wildcard_" ] ; then
                            all_dns+=( "*.${domains[${i}]}" )
                        else
                            all_dns+=( "${subdomains[${j}]}.${domains[${i}]}" )
                        fi
                    done
                done
            fi

            all_dns+=( ${san_dns[@]:-} )

            for i in "${!all_dns[@]}" ; do
                printf "%-18s = %s\n" "DNS.${i}" "${all_dns[${i}]}" >> ${config_file}
            done

            for i in "${!san_ip[@]}" ; do
                printf "%-18s = %s\n" "IP.${i}" "${san_ip[${i}]}" >> ${config_file}
            done

            for i in "${!san_uri[@]}" ; do
                printf "%-18s = %s\n" "URI.${i}" "${san_uri[${i}]}" >> ${config_file}
            done

            for i in "${!san_email[@]}" ; do
                printf "%-18s = %s\n" "email.${i}" "${san_email[${i}]}" >> ${config_file}
            done

        fi

    fi
}

create_gnutls_config () {

    local config_file="${1:-config/gnutls.conf}"
    local config_type="${2:-internal}"
    local req_dn="${3}"
    local req_domains="${4}"
    local req_subdomains="${5:-}"
    local req_san="${6:-}"

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

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

signing_key
encryption_key
tls_www_client
tls_www_server

EOF

        if [ -n "${PKI_SESSION_TOKEN:-}" -a "${config_type}" = "internal" ] ; then
            cat << EOF >> ${config_file}
# This password is meaningless outside of current session
challenge_password  = ${PKI_SESSION_TOKEN:-}

EOF
        fi

        echo "${req_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}

        local domains=( $(echo "${req_domains}" | tr "/" " ") )
        local subdomains=( $(echo "${req_subdomains}" | tr "/" " ") )

        local san_ip=(    $( echo "${req_san}" | tr "|" "\n" | awk '/^[Ii][Pp]:/ {print;}' | sed -e 's/^[Ii][Pp]://' | xargs ) )
        local san_dns=(   $( echo "${req_san}" | tr "|" "\n" | awk '/^[Dd][Nn][Ss]:/ {print;}' | sed -e 's/^[Dd][Nn][Ss]://' | xargs ) )
        local san_uri=(   $( echo "${req_san}" | tr "|" "\n" | awk '/^[Uu][Rr][Ii]:/ {print;}' | sed -e 's/^[Uu][Rr][Ii]://' | xargs ) )
        local san_email=( $( echo "${req_san}" | tr "|" "\n" | awk '/^[Ee][Mm][Aa][Ii][Ll]:/ {print;}' | sed -e 's/^[Ee][Mm][Aa][Ii][Ll]://' | xargs ) )

        local all_dns=( ${domains[@]:-} )

        if [ ${#subdomains[@]} -gt 0 ] ; then
            for i in "${!domains[@]}" ; do
                for j in "${!subdomains[@]}" ; do
                    if [ "${subdomains[${j}]}" == "_wildcard_" ] ; then
                        all_dns+=( "*.${domains[${i}]}" )
                    else
                        all_dns+=( "${subdomains[${j}]}.${domains[${i}]}" )
                    fi
                done
            done
        fi

        all_dns+=( ${san_dns[@]:-} )

        for i in "${!all_dns[@]}" ; do
            printf 'dns_name = "%s"\n' "${all_dns[${i}]}" >> ${config_file}
        done

        for i in "${!san_ip[@]}" ; do
            printf 'ip_address = "%s"\n' "${san_ip[${i}]}" >> ${config_file}
        done

        for i in "${!san_uri[@]}" ; do
            printf 'uri = "%s"\n' "${san_uri[${i}]}" >> ${config_file}
        done

        for i in "${!san_email[@]}" ; do
            printf 'email = "%s"\n' "${san_email[${i}]}" >> ${config_file}
        done

    fi
}

save_realm_config () {

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

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

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

EOF
        for key in "${!config[@]}" ; do
            echo "config['${key}']='${config[${key}]}'" >> ${config_file}
        done
    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_rsa_realm_key () {

    local key_file="${1:-private/realm_key.pem}"
    local key_size="${2:-2048}"
    local key_group="${3:-${config['realm_file_group']}}"

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

    if [ -r ${key_file}.tmp ] ; then
        chmod ${config['realm_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['realm_file_mode']}" ] ; then
        chmod ${config['realm_file_mode']} ${key_file}
    fi
    if [ "$(stat -c %G ${key_file})" != "${key_group}" ] ; then
        chgrp ${key_group} ${key_file}
    fi

}

generate_gnutls_rsa_realm_key () {

    local key_file="${1:-private/realm_key.pem}"
    local key_size="${2:-2048}"
    local key_group="${3:-${config['realm_file_group']}}"

    if ! [ -r ${key_file} ] ; then
        if ! awk -v ver="$(certtool --version | head -n 1 | awk '{print $NF}')" 'BEGIN { if (ver < 3.1.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['realm_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['realm_file_mode']}" ] ; then
        chmod ${config['realm_file_mode']} ${key_file}
    fi
    if [ "$(stat -c %G ${key_file})" != "${key_group}" ] ; then
        chgrp ${key_group} ${key_file}
    fi

}

generate_openssl_rsa_private_key () {

    local key_file="${1:-private/key.pem}"
    local key_size="${2:-2048}"
    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:-2048}"
    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.1.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

}

check_expiration_time () {

    local cert_file="${1}"
    local seconds="${2}"

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

        current_time="$(date +%s)"
        cert_enddate="$(openssl x509 -in ${cert_file} -noout -enddate | cut -d= -f2)"
        cert_expires=$(date --date="${cert_enddate}" +%s)

        if [ -n "${seconds}" ] ; then
            cert_expires="$(( ${cert_expires} - ${seconds} ))"
        fi

        if (( (${cert_expires} - ${current_time}) >= 0 )) ; then
            return 0
        else
            return 1
        fi

    else
        return 1
    fi
}

request_acme_tiny_certificate () {

    if [ "${config['pki_acme']}" = "true" -a -x ${config['acme_client_script']} -a -d ${config['acme_challenge_dir']} -a ! -r acme/error.log -a -r acme/request.pem ] ; then

        if ! ( check_expiration_time acme/cert.pem ${config['acme_expiration_seconds']} && check_openssl_key_crt_match private/key.pem acme/cert.pem ) ; then

            if [ "$EUID" -eq 0 ] ; then

                set +e
                su --shell /bin/sh -c "${config['acme_client_script']} --account-key acme/account_key.pem \
                    --ca ${config['acme_ca_api']} --csr acme/request.pem \
                    --acme-dir ${config['acme_challenge_dir']} > acme/cert.pem.tmp 2>acme/error.log" "${config['acme_user']}"
                set -e

            else

                set +e
                ${config['acme_client_script']} --account-key acme/account_key.pem \
                    --ca ${config['acme_ca_api']} --csr acme/request.pem \
                    --acme-dir ${config['acme_challenge_dir']} > acme/cert.pem.tmp 2>acme/error.log
                set -e

            fi

            if [ -r acme/cert.pem.tmp -a -s acme/cert.pem.tmp ] ; then

                test -r acme/cert.pem && mv acme/cert.pem acme/cert.pem.old || true
                mv acme/cert.pem.tmp acme/cert.pem

                local acme_intermediate_uri="$(openssl x509 -in acme/cert.pem -text -noout -certopt ca_default,no_validity,no_serial | grep -E '^\s+CA\s+Issuers\s+-\s+URI:' | awk '{print $4}' | sed -e 's/^URI://')"

                if [ ! -r acme/intermediate.pem ] ; then

                    if [ -n "${acme_intermediate_uri}" ] ; then
                        curl -s -o acme/intermediate.pem.tmp ${acme_intermediate_uri}
                        mv acme/intermediate.pem.tmp acme/intermediate.pem
                        if [ ! -r acme/intermediate_uri.txt ] ; then
                            echo "${acme_intermediate_uri}" > acme/intermediate_uri.txt
                        fi
                    fi

                elif [ -r acme/intermediate.pem ] ; then

                    if [ -n "${acme_intermediate_uri}" -a -r acme/intermediate_uri.txt ] ; then
                        if [ "${acme_intermediate_uri}" != "$(<acme/intermediate_uri.txt)" ] ; then
                            curl -s -o acme/intermediate.pem.tmp ${acme_intermediate_uri}
                            if ! diff -q -N acme/intermediate.pem acme/intermediate.pem.tmp > /dev/null ; then
                                rm -f acme/intermediate.pem acme/intermediate_uri.txt
                                mv acme/intermediate.pem.tmp acme/intermediate.pem
                                if [ ! -r acme/intermediate_uri.txt ] ; then
                                    echo "${acme_intermediate_uri}" > acme/intermediate_uri.txt
                                fi
                            else
                                rm -f acme/intermediate.pem.tmp acme/intermediate_uri.txt
                                if [ ! -r acme/intermediate_uri.txt ] ; then
                                    echo "${acme_intermediate_uri}" > acme/intermediate_uri.txt
                                fi
                            fi
                        fi
                    fi

                fi

                if [ ! -r acme/root.pem ] ; then
                    if [ -n "${config['acme_root_ca_path']}" -a -n "${config['acme_root_ca_file']}" ] ; then
                        if [ -r ${config['acme_root_ca_path']}/${config['acme_root_ca_file']} ] && grep -q "${config['acme_root_ca_file']}" /etc/ca-certificates.conf ; then
                            ln -s ${config['acme_root_ca_path']}/${config['acme_root_ca_file']} acme/root.pem
                        fi
                    fi
                fi

                if [ -r acme/error.log ] ; then
                    rm -f acme/error.log
                fi

            elif [ -r acme/cert.pem.tmp -a ! -s acme/cert.pem.tmp ] ; then
                rm -f acme/cert.pem.tmp
            fi

        fi

    fi

}

check_public_certificate () {

    if [ -r public/cert.pem ] ; then
        if ! $(check_file_signature public/cert.pem) ; then
            state+=("changed-certificate")
            state+=("file-change")
        fi
    fi

}

check_files () {

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

    if [ "${config['pki_internal']}" == "true" ] ; then

        if [ ! -r private/realm_key.pem ] ; then
            generate_${library}_rsa_realm_key private/realm_key.pem
            if [ ! -r private/key.pem ] ; then
                install -g ${config['private_file_group']} -m ${config['private_file_mode']} /dev/null private/key.pem.tmp
                cat private/realm_key.pem >> private/key.pem.tmp
                mv private/key.pem.tmp private/key.pem
                state+=('new-private-key')
            fi
        fi

        if [ -n "${PKI_SESSION_TOKEN:-}" -a -r internal/cert.pem ] ; then
            if ! ( check_expiration_time internal/cert.pem ${config['internal_expiration_seconds']} && check_openssl_key_crt_match private/key.pem internal/cert.pem ) ; then
                rm -f internal/${library}.conf internal/request.pem
            fi
        fi

        if [ -n "${PKI_SESSION_TOKEN:-}" -a ! -r public/cert.pem ] ; then
            if ! ( check_expiration_time internal/cert.pem ${config['internal_expiration_seconds']} && check_openssl_key_crt_match private/key.pem internal/cert.pem ) ; then
                rm -f internal/${library}.conf internal/request.pem
            fi
        fi

        if [ ! -r internal/${library}.conf -a ! -r internal/request.pem ] ; then

            if [[ ${name} != *.* && ${name} != *@* ]] ; then

                create_${library}_config internal/${library}.conf internal "${config['subject']:-${config['pki_default_subject']}}" "${config['domains']:-${config['pki_default_domain']}}" "${config['subdomains']:-${config['pki_default_subdomains']}}" "${config['subject_alt_names']:-}"

            elif [[ ${name} == *.* && ${name} != *@* ]] ; then

                create_${library}_config internal/${library}.conf internal "${config['subject']:-cn=${name}}" "${config['domains']:-${name:-${config['pki_default_domain']}}}" "${config['subdomains']:-${config['pki_default_subdomains']}}" "${config['subject_alt_names']:-}"

            fi

            generate_${library}_request internal/${library}.conf internal/request.pem
            state+=('new-internal-request')
        fi

        if [ -r internal/${library}.conf -a -r internal/request.pem ] ; then
            if ! check_openssl_key_req_match private/key.pem internal/request.pem ; then

                rm -f internal/request.pem
                generate_${library}_request internal/${library}.conf internal/request.pem
                state+=('new-internal-request')

            fi
        fi

    fi

    if [ "${config['pki_acme']}" = "true" ] ; then

        if [ ! -r private/realm_key.pem ] ; then
            generate_${library}_rsa_realm_key private/realm_key.pem
            if [ ! -r private/key.pem ] ; then
                install -g ${config['private_file_group']} -m ${config['private_file_mode']} /dev/null private/key.pem.tmp
                cat private/realm_key.pem >> private/key.pem.tmp
                mv private/key.pem.tmp private/key.pem
                state+=('new-private-key')
            fi
        fi

        if [ -r acme/cert.pem -a -r acme/${acme_library}.conf -a -r acme/request.pem ] ; then
            if ! check_expiration_time acme/cert.pem ${config['acme_expiration_seconds']} ; then
                rm -f acme/${acme_library}.conf acme/request.pem
            fi
        fi

        if [ ! -r acme/${acme_library}.conf -a ! -r acme/request.pem ] ; then

            if [[ ${name} != *.* && ${name} != *@* ]] ; then

                create_${acme_library}_config acme/${acme_library}.conf acme "${config['acme_subject']:-${config['acme_default_subject']}}" "${config['acme_domains']:-${config['acme_default_domain']}}" "${config['acme_subdomains']:-${config['acme_default_subdomains']}}"

            elif [[ ${name} == *.* && ${name} != *@* ]] ; then

                create_${acme_library}_config acme/${acme_library}.conf acme "${config['acme_subject']:-cn=${name}}" "${config['acme_domains']:-${name:-${config['acme_default_domain']}}}" "${config['acme_subdomains']:-${config['acme_default_subdomains']}}"

            fi

            generate_${library}_rsa_private_key acme/account_key.pem 4096 "${config['acme_file_group']}"
            generate_${acme_library}_request acme/${acme_library}.conf acme/request.pem
            chgrp "${config['acme_file_group']}" acme/request.pem
            chmod "${config['acme_file_mode']}" acme/request.pem
            chmod "${config['acme_dir_mode']}" acme
            state+=('new-acme-request')
        fi

        if [ -r acme/${acme_library}.conf -a -r acme/request.pem ] ; then
            if ! check_openssl_key_req_match private/key.pem acme/request.pem ; then

                rm -f acme/request.pem
                generate_${acme_library}_request acme/${acme_library}.conf acme/request.pem
                chgrp "${config['acme_file_group']}" acme/request.pem
                chmod "${config['acme_file_mode']}" acme/request.pem
                state+=('new-acme-request')

            fi

        elif [ -r acme/${acme_library}.conf -a ! -r acme/request.pem ] ; then

            generate_${acme_library}_request acme/${acme_library}.conf acme/request.pem
            chgrp "${config['acme_file_group']}" acme/request.pem
            chmod "${config['acme_file_mode']}" acme/request.pem
            state+=('new-acme-request')

        fi

    fi

}

# Select the preferred authority in a given PKI realm (external, acme,
# internal) and check all of the files, make sure that everything is in order
process_public_files () {

    local authority_preference=( $(echo "${config['pki_authority_preference']}" | tr "/" " ") )

    for authority in ${authority_preference[@]} ; do

        if [ -r ${authority}/cert.pem ] && ( check_expiration_time ${authority}/cert.pem $(( 60 * 60 * 24 * 10 )) || [ "${authority}" = "${authority_preference[-1]}" ] ) ; then

            if ! ( diff -q -N ${authority}/cert.pem public/cert.pem > /dev/null && check_file_signature public/cert.pem ) ; then

                for source_file in ${authority}/cert.pem ${authority}/intermediate.pem ${authority}/root.pem ${authority}/alt_intermediate.pem ${authority}/alt_root.pem ; do

                    if [ -r ${source_file} ] ; then
                        if ! diff -q -N ${source_file} ${source_file/${authority}/public} > /dev/null ; then
                            ln -sf $(get_relative_path "${source_file}" "public") ${source_file/${authority}/public}.tmp
                        fi
                    elif [ ! -r ${source_file} -a -r ${source_file/${authority}/public} ] ; then
                        rm -f ${source_file/${authority}/public}
                    fi

                done

                if [ -r public/cert.pem ] ; then
                    if ! check_file_signature public/cert.pem ; then
                        if [ -r public/intermediate.pem ] ; then
                            test -r public/cert_intermediate.pem.tmp && rm -f public/cert_intermediate.pem.tmp || true
                            cat public/cert.pem public/intermediate.pem > public/cert_intermediate.pem.tmp
                        fi
                    fi
                fi

                if [ -r public/cert.pem.tmp -a -r public/intermediate.pem.tmp ] ; then
                        cat public/cert.pem.tmp public/intermediate.pem.tmp > public/cert_intermediate.pem.tmp
                elif [ -r public/cert.pem.tmp -a -r public/intermediate.pem ] ; then
                        cat public/cert.pem.tmp public/intermediate.pem > public/cert_intermediate.pem.tmp
                fi

                if [ -r public/intermediate.pem.tmp -a -r public/root.pem.tmp ] ; then
                    cat public/intermediate.pem.tmp public/root.pem.tmp > public/intermediate_root.pem.tmp
                elif [ -r public/intermediate.pem -a -r public/root.pem.tmp ] ; then
                    cat public/intermediate.pem public/root.pem.tmp > public/intermediate_root.pem.tmp
                fi

                if [ -r public/alt_intermediate.pem.tmp -a -r public/alt_root.pem.tmp ] ; then
                    cat public/alt_intermediate.pem.tmp public/alt_root.pem.tmp > public/alt_intermediate_root.pem.tmp
                elif [ -r public/alt_intermediate.pem -a -r public/alt_root.pem.tmp ] ; then
                    cat public/alt_intermediate.pem public/alt_root.pem.tmp > public/alt_intermediate_root.pem.tmp
                fi

                for public_file in public/*.pem ; do
                    if [ ! -r ${public_file/public/${authority}} -a ! -r ${public_file}.tmp ] ; then
                        touch ${public_file}.rm
                    fi
                done

            fi

            if [ -r private/key.pem -a -r public/cert.pem -a -r public/intermediate.pem -a -r public/cert_intermediate.pem ] ; then

                if ! check_openssl_key_crt_match private/key.pem public/cert_intermediate.pem ; then
                    test -r public/cert_intermediate.pem.tmp && rm -f public/cert_intermediate.pem.tmp || true
                    cat public/cert.pem public/intermediate.pem > public/cert_intermediate.pem.tmp
                fi

            fi

            if [ "${config['pki_dhparam']}" = "true" -a -n "${config['dhparam_file']}" -a -r ${config['dhparam_file']} ] ; then

                if [ -r public/cert_intermediate.pem.tmp ] ; then
                    cat public/cert_intermediate.pem.tmp ${config['dhparam_file']} > public/cert_intermediate_dhparam.pem.tmp
                    state+=("changed-dhparam")
                elif [ -r public/cert.pem.tmp ] ; then
                    cat public/cert.pem.tmp ${config['dhparam_file']} > public/cert_dhparam.pem.tmp
                    state+=("changed-dhparam")
                elif [ ! -r public/cert_intermediate_dhparam.pem -a -r public/cert_intermediate.pem ] ; then
                    cat public/cert_intermediate.pem ${config['dhparam_file']} > public/cert_intermediate_dhparam.pem.tmp
                    state+=("changed-dhparam")
                elif [ ! -r public/cert_dhparam.pem -a ! -r public/cert_intermediate.pem -a -r public/cert.pem ] ; then
                    cat public/cert.pem ${config['dhparam_file']} > public/cert_dhparam.pem.tmp
                    state+=("changed-dhparam")
                elif [ -r public/cert_intermediate_dhparam.pem ] ; then
                    if ! diff -q -N ${config['dhparam_file']} <( sed -n '/-----BEGIN DH PARAMETERS-----/,/-----END DH PARAMETERS-----/p' public/cert_intermediate_dhparam.pem ) > /dev/null ; then
                        cat public/cert_intermediate.pem ${config['dhparam_file']} > public/cert_intermediate_dhparam.pem.tmp
                        state+=("changed-dhparam")
                    fi
                elif [ -r public/cert_dhparam.pem ] ; then
                    if ! diff -q -N ${config['dhparam_file']} <( sed -n '/-----BEGIN DH PARAMETERS-----/,/-----END DH PARAMETERS-----/p' public/cert_dhparam.pem ) > /dev/null ; then
                        cat public/cert.pem ${config['dhparam_file']} > public/cert_dhparam.pem.tmp
                        state+=("changed-dhparam")
                    elif ! check_file_signature public/cert.pem ; then
                        cat public/cert.pem ${config['dhparam_file']} > public/cert_dhparam.pem.tmp
                        state+=("changed-dhparam")
                    fi
                fi

            fi

            break

        elif [ ! -r ${authority}/cert.pem -a -r ${authority}/root.pem ] ; then

            if ! diff -q -N ${authority}/root.pem public/root.pem > /dev/null ; then

                for source_file in ${authority}/intermediate.pem ${authority}/root.pem ${authority}/alt_intermediate.pem ${authority}/alt_root.pem ; do

                    if [ -r ${source_file} ] ; then
                        if ! diff -q -N ${source_file} ${source_file/${authority}/public} > /dev/null ; then
                            ln -sf $(get_relative_path "${source_file}" "public") ${source_file/${authority}/public}.tmp
                        fi
                    fi

                done

                if [ -r public/intermediate.pem.tmp -a -r public/root.pem.tmp ] ; then
                    cat public/intermediate.pem.tmp public/root.pem.tmp > public/intermediate_root.pem.tmp
                elif [ -r ${authority}/intermediate.pem -a -r public/intermediate.pem -a -r public/root.pem.tmp ] ; then
                    cat public/intermediate.pem public/root.pem.tmp > public/intermediate_root.pem.tmp
                fi

                if [ -r public/alt_intermediate.pem.tmp -a -r public/alt_root.pem.tmp ] ; then
                    cat public/alt_intermediate.pem.tmp public/alt_root.pem.tmp > public/alt_intermediate_root.pem.tmp
                elif [ -r ${authority}/alt_intermediate.pem -a -r public/alt_intermediate.pem -a -r public/alt_root.pem.tmp ] ; then
                    cat public/alt_intermediate.pem public/alt_root.pem.tmp > public/alt_intermediate_root.pem.tmp
                fi

                for public_file in public/*.pem ; do
                    if [ ! -r ${public_file/public/${authority}} -a ! -r ${public_file}.tmp ] ; then
                        touch ${public_file}.rm
                    fi
                done

            fi

            break

        fi

    done

}

process_private_files () {

    local current_umask="$(umask)"
    umask 027

    local authority_preference=( $(echo "${config['pki_authority_preference']}" | tr "/" " ") )

    for authority in ${authority_preference[@]} ; do

        if [ -r ${authority}/cert.pem ] && ( check_expiration_time ${authority}/cert.pem $(( 60 * 60 * 24 * 10 )) || [ "${authority}" = "${authority_preference[-1]}" ] ) ; then

            if [ ! -r private/key.pem ] ; then

                if check_openssl_key_crt_match private/realm_key.pem ${authority}/cert.pem ; then

                    test -r private/key.pem.tmp && rm -f private/key.pem.tmp || true
                    install -g ${config['private_file_group']} -m ${config['private_file_mode']} /dev/null private/key.pem.tmp
                    cat private/realm_key.pem >> private/key.pem.tmp

                fi
            fi

            if [ -r public/cert.pem.tmp -o -r public/cert_intermediate.pem.tmp ] ; then

                if [ -r public/cert.pem.tmp -a -r private/realm_key.pem ] ; then

                    if ! check_openssl_key_crt_match private/key.pem public/cert.pem.tmp ; then

                        test -r private/key.pem.tmp && rm -f private/key.pem.tmp || true
                        install -g ${config['private_file_group']} -m ${config['private_file_mode']} /dev/null private/key.pem.tmp
                        cat private/realm_key.pem >> private/key.pem.tmp

                    fi

                fi

                if [ -r public/cert_intermediate.pem.tmp ] ; then

                    test -r private/key_chain.pem.tmp && rm -f private/key_chain.pem.tmp || true
                    install -g ${config['private_file_group']} -m ${config['private_file_mode']} /dev/null private/key_chain.pem.tmp
                    if [ -r private/key.pem ] && check_openssl_key_crt_match private/key.pem public/cert_intermediate.pem.tmp ; then
                        cat private/key.pem public/cert_intermediate.pem.tmp >> private/key_chain.pem.tmp
                    elif [ -r private/realm_key.pem ] && check_openssl_key_crt_match private/realm_key.pem public/cert_intermediate.pem.tmp ; then
                        cat private/realm_key.pem public/cert_intermediate.pem.tmp >> private/key_chain.pem.tmp
                    fi

                elif [ -r public/cert.pem.tmp ] ; then

                    test -r private/key_chain.pem.tmp && rm -f private/key_chain.pem.tmp || true
                    install -g ${config['private_file_group']} -m ${config['private_file_mode']} /dev/null private/key_chain.pem.tmp
                    if [ -r private/key.pem ] ; then
                        cat private/key.pem public/cert.pem.tmp >> private/key_chain.pem.tmp
                    elif [ -r private/realm_key.pem ] ; then
                        cat private/realm_key.pem public/cert.pem.tmp >> private/key_chain.pem.tmp
                    fi

                fi
            fi

            if [ -r private/key.pem -a -r public/cert_intermediate.pem ] ; then

                if ! check_openssl_key_crt_match private/key.pem public/cert_intermediate.pem ; then
                    test -r private/key_chain.pem.tmp && rm -f private/key_chain.pem.tmp || true
                    install -g ${config['private_file_group']} -m ${config['private_file_mode']} /dev/null private/key_chain.pem.tmp
                    cat private/key.pem public/cert_intermediate.pem >> private/key_chain.pem.tmp
                fi

            elif [ -r private/key.pem -a -r public/cert.pem ] ; then

                if ! check_openssl_key_crt_match private/key.pem public/cert.pem ; then
                    test -r private/key_chain.pem.tmp && rm -f private/key_chain.pem.tmp || true
                    install -g ${config['private_file_group']} -m ${config['private_file_mode']} /dev/null private/key_chain.pem.tmp
                    cat private/key.pem public/cert.pem >> private/key_chain.pem.tmp
                elif ! check_file_signature public/cert.pem ; then
                    test -r private/key_chain.pem.tmp && rm -f private/key_chain.pem.tmp || true
                    install -g ${config['private_file_group']} -m ${config['private_file_mode']} /dev/null private/key_chain.pem.tmp
                    cat private/key.pem public/cert.pem >> private/key_chain.pem.tmp
                fi

            fi

            if [ "${config['pki_dhparam']}" = "true" -a -n "${config['dhparam_file']}" -a -r ${config['dhparam_file']} ] ; then
                if [ -r private/key_chain.pem.tmp ] ; then
                    install -g ${config['private_file_group']} -m ${config['private_file_mode']} /dev/null private/key_chain_dhparam.pem.tmp
                    cat private/key_chain.pem.tmp ${config['dhparam_file']} >> private/key_chain_dhparam.pem.tmp
                    state+=("changed-dhparam")
                elif [ ! -r private/key_chain_dhparam.pem -a -r private/key_chain.pem ] ; then
                    install -g ${config['private_file_group']} -m ${config['private_file_mode']} /dev/null private/key_chain_dhparam.pem.tmp
                    cat private/key_chain.pem ${config['dhparam_file']} >> private/key_chain_dhparam.pem.tmp
                    state+=("changed-dhparam")
                elif [ -r private/key_chain_dhparam.pem ] ; then
                    if ! diff -q -N ${config['dhparam_file']} <( sed -n '/-----BEGIN DH PARAMETERS-----/,/-----END DH PARAMETERS-----/p' private/key_chain_dhparam.pem ) > /dev/null ; then
                        install -g ${config['private_file_group']} -m ${config['private_file_mode']} /dev/null private/key_chain_dhparam.pem.tmp
                        cat private/key_chain.pem ${config['dhparam_file']} >> private/key_chain_dhparam.pem.tmp
                        state+=("changed-dhparam")
                    fi
                fi
            fi

            break

        elif [ ! -r ${authority}/cert.pem -a -r ${authority}/root.pem ] ; then

            if ! diff -q -N ${authority}/root.pem public/root.pem > /dev/null ; then

                if [ -r private/key_chain.pem ] ; then
                    touch private/key_chain.pem.rm
                fi
                if [ -r private/key_chain_dhparam.pem ] ; then
                    touch private/key_chain_dhparam.pem.rm
                fi

            fi

            break
        fi

    done

    umask ${current_umask}
}

activate_new_files () {

    if [ -r public/cert.pem.tmp -o -r public/root.pem.tmp ] ; then

        if [ ! -r public/intermediate.pem.tmp -a -r public/intermediate.pem ] ; then
            rm -f public/intermediate.pem public/cert_intermediate.pem public/cert_intermediate_dhparam.pem public/chain.pem public/intermediate_root.pem
            state+=("file-change")
        fi

        if [ ! -r public/alt_intermediate.pem.tmp -a -r public/alt_intermediate.pem ] ; then
            rm -f public/alt_intermediate.pem public/alt_intermediate_root.pem public/alt_trusted.pem
            state+=("file-change")
        fi

        state+=("changed-certificate")
    fi

    for rm_file in $(find public/ -maxdepth 1 -name '*.rm') ; do
        rm -f ${rm_file/.rm/} ${rm_file}
        state+=("file-deletion")
    done

    if [ ! -r public/cert.pem -a public/cert.pem.sig ] ; then
        rm -f public/cert.pem.sig
    fi

    for new_file in $(find public/ -maxdepth 1 -name '*.tmp') ; do
        mv --force ${new_file} ${new_file/.tmp/}
        state+=("changed-public-file")
        state+=("file-change")
    done

    for rm_file in $(find private/ -maxdepth 1 -name '*.rm') ; do
        rm -f ${rm_file/.rm/} ${rm_file}
        state+=("file-deletion")
    done

    for new_file in $(find private/ -maxdepth 1 -name '*.tmp') ; do
        mv --force ${new_file} ${new_file/.tmp/}
        state+=("changed-private-file")
        state+=("file-change")
    done

    update_file_signature public/cert.pem
    update_realm_symlinks

}

update_realm_symlinks () {

    if [ -r public/intermediate.pem ] ; then
        cd public
        update_symlink chain.pem cert_intermediate_dhparam.pem cert_intermediate.pem cert_dhparam.pem cert.pem
        update_symlink trusted.pem intermediate_root.pem root.pem
        cd - > /dev/null
    fi

    if [ -r public/alt_intermediate_root.pem ] ; then
        cd public
        update_symlink alt_trusted.pem alt_intermediate_root.pem alt_root.pem
        cd - > /dev/null
    fi

    update_symlink default.crt public/chain.pem public/cert_dhparam.pem public/cert.pem
    update_symlink default.key private/key.pem
    update_symlink default.pem private/key_chain_dhparam.pem private/key_chain.pem
    update_symlink CA.crt public/alt_trusted.pem public/alt_root.pem public/trusted.pem public/root.pem ${config['pki_default_ca_bundle']}
    update_symlink trusted.crt public/trusted.pem public/root.pem

}

sub_new-realm () {

    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#*=}
                        ;;
                    acme-library)
                        args["pki_acme_library"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    acme-library=*)
                        args["pki_acme_library"]=${OPTARG#*=}
                        ;;
                    internal)
                        args["pki_internal"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    internal=*)
                        args["pki_internal"]=${OPTARG#*=}
                        ;;
                    subject)
                        args["subject"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    subject=*)
                        args["subject"]=${OPTARG#*=}
                        ;;
                    domains)
                        args["domains"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    domains=*)
                        args["domains"]=${OPTARG#*=}
                        ;;
                    subdomains)
                        args["subdomains"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    subdomains=*)
                        args["subdomains"]=${OPTARG#*=}
                        ;;
                    subject-alt-names)
                        args["subject_alt_names"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    subjest-alt-names=*)
                        args["subject_alt_names"]=${OPTARG#*=}
                        ;;
                    acme)
                        args["pki_acme"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    acme=*)
                        args["pki_acme"]=${OPTARG#*=}
                        ;;
                    acme-subject)
                        args["acme_subject"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    acme-subject=*)
                        args["acme_subject"]=${OPTARG#*=}
                        ;;
                    acme-domains)
                        args["acme_domains"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    acme-domains=*)
                        args["acme_domains"]=${OPTARG#*=}
                        ;;
                    acme-subdomains)
                        args["acme_subdomains"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    acme-subdomains=*)
                        args["acme_subdomains"]=${OPTARG#*=}
                        ;;
                    acme-alt-names)
                        args["acme_alt_names"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    acme-alt-names=*)
                        args["acme_alt_names"]=${OPTARG#*=}
                        ;;
                    *)
                        if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then
                            echo "Unknown option --${OPTARG}" >&2
                        fi
                        ;;
                esac
                ;;
            h)
                echo "usage: ${script_name} new-realm <-n|--name[=]realm>" >&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

        local pidfile="${config['lock_dir']}/${script_name}-${args['name']}.pid"

        wait_for_pid "${pidfile}"
        create_lock "${pidfile}"

        enter_realm ${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 external internal public
        create_private_directories ${config['private_dir_group']} private
        create_private_directories ${config['acme_dir_group']} acme

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

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

        if [ "${config['pki_internal']}" = "true" ] ; then
            generate_${library}_rsa_realm_key private/realm_key.pem
            if [ ! -r private/key.pem ] ; then
                install -g ${config['private_file_group']} -m ${config['private_file_mode']} /dev/null private/key.pem.tmp
                cat private/realm_key.pem >> private/key.pem.tmp
                mv private/key.pem.tmp private/key.pem
                state+=("new-private-key")
            fi
        fi

        if [[ ${name} != *.* && ${name} != *@* ]] ; then

            create_${library}_config internal/${library}.conf internal "${config['subject']:-${config['pki_default_subject']}}" "${config['domains']:-${config['pki_default_domain']}}" "${config['subdomains']:-${config['pki_default_subdomains']}}" "${config['subject_alt_names']:-}"

            if [ "${config['pki_acme']}" = "true" ] ; then
                create_${acme_library}_config acme/${acme_library}.conf acme "${config['acme_subject']:-${config['acme_default_subject']}}" "${config['acme_domains']:-${config['acme_default_domain']}}" "${config['acme_subdomains']:-${config['acme_default_subdomains']}}" "${config['acme_alt_names']:-}"
            fi

        elif [[ ${name} == *.* && ${name} != *@* ]] ; then

            create_${library}_config internal/${library}.conf internal "${config['subject']:-cn=${name}}" "${config['domains']:-${name:-${config['pki_default_domain']}}}" "${config['subdomains']:-${config['pki_default_subdomains']}}" "${config['subject_alt_names']:-}"

            if [ "${config['pki_acme']}" = "true" ] ; then
                create_${acme_library}_config acme/${acme_library}.conf acme "${config['acme_subject']:-cn=${name}}" "${config['acme_domains']:-${name:-${config['acme_default_domain']}}}" "${config['acme_subdomains']:-${config['acme_default_subdomains']}}" "${config['acme_alt_names']:-}"
            fi

        fi

        generate_${library}_request internal/${library}.conf internal/request.pem
        state+=("new-internal-request")
        if [ "${config['pki_acme']}" = "true" ] ; then
            generate_${library}_rsa_private_key acme/account_key.pem 4096 "${config['acme_file_group']}"
            generate_${acme_library}_request acme/${acme_library}.conf acme/request.pem
            chgrp "${config['acme_file_group']}" acme/request.pem
            chmod "${config['acme_file_mode']}" acme/request.pem
            chmod "${config['acme_dir_mode']}" acme
            state+=("new-acme-request")
        fi

        update_realm_symlinks

        run_pki_hooks

    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#*=}
                        ;;
                    public-dir-group)
                        args["public_dir_group"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    public-dir-group=*)
                        args["public_dir_group"]=${OPTARG#*=}
                        ;;
                    private-dir-group)
                        args["private_dir_group"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    private-dir-group=*)
                        args["private_dir_group"]=${OPTARG#*=}
                        ;;
                    private-file-group)
                        args["private_file_group"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    private-file-group=*)
                        args["private_file_group"]=${OPTARG#*=}
                        ;;
                    library)
                        args["pki_library"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    library=*)
                        args["pki_library"]=${OPTARG#*=}
                        ;;
                    internal)
                        args["pki_internal"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    internal=*)
                        args["pki_internal"]=${OPTARG#*=}
                        ;;
                    subject)
                        args["subject"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    subject=*)
                        args["subject"]=${OPTARG#*=}
                        ;;
                    domains)
                        args["domains"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    domains=*)
                        args["domains"]=${OPTARG#*=}
                        ;;
                    subdomains)
                        args["subdomains"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    subdomains=*)
                        args["subdomains"]=${OPTARG#*=}
                        ;;
                    subject-alt-names)
                        args["subject_alt_names"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    subjest-alt-names=*)
                        args["subject_alt_names"]=${OPTARG#*=}
                        ;;
                    default-domain)
                        args["pki_default_domain"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    default-domain=*)
                        args["pki_default_domain"]=${OPTARG#*=}
                        ;;
                    default-subdomains)
                        args["pki_default_subdomains"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    default-subdomains=*)
                        args["pki_default_subdomains"]=${OPTARG#*=}
                        ;;
                    dhparam)
                        args["pki_dhparam"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    dhparam=*)
                        args["pki_dhparam"]=${OPTARG#*=}
                        ;;
                    dhparam-file)
                        args["dhparam_file"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    dhparam-file=*)
                        args["dhparam_file"]=${OPTARG#*=}
                        ;;
                    acme)
                        args["pki_acme"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    acme=*)
                        args["pki_acme"]=${OPTARG#*=}
                        ;;
                    acme-subject)
                        args["acme_subject"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    acme-subject=*)
                        args["acme_subject"]=${OPTARG#*=}
                        ;;
                    acme-domains)
                        args["acme_domains"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    acme-domains=*)
                        args["acme_domains"]=${OPTARG#*=}
                        ;;
                    acme-subdomains)
                        args["acme_subdomains"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    acme-subdomains=*)
                        args["acme_subdomains"]=${OPTARG#*=}
                        ;;
                    acme-alt-names)
                        args["acme_alt_names"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    acme-alt-names=*)
                        args["acme_alt_names"]=${OPTARG#*=}
                        ;;
                    acme-ca)
                        args["acme_ca"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    acme-ca=*)
                        args["acme_ca"]=${OPTARG#*=}
                        ;;
                    acme-ca-api)
                        args["acme_ca_api"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    acme-ca-api=*)
                        args["acme_ca_api"]=${OPTARG#*=}
                        ;;
                    acme-default-subdomains)
                        args["acme_default_subdomains"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    acme-default-subdomains=*)
                        args["acme_default_subdomains"]=${OPTARG#*=}
                        ;;
                    acme-challenge-dir)
                        args["acme_challenge_dir"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    acme-challenge-dir=*)
                        args["acme_challenge_dir"]=${OPTARG#*=}
                        ;;
                    *)
                        if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then
                            echo "Unknown option --${OPTARG}" >&2
                        fi
                        ;;
                esac
                ;;
            h)
                echo "usage: ${script_name} init <-n|--name[=]realm>" >&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

        local pidfile="${config['lock_dir']}/${script_name}-${args['name']}.pid"

        wait_for_pid "${pidfile}"
        create_lock "${pidfile}"

        enter_realm ${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 external internal public
        create_private_directories ${config['private_dir_group']} private
        create_private_directories ${config['acme_dir_group']} acme

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

        run_pki_hooks

    fi
}

sub_run () {

    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#*=}
                        ;;
                    *)
                        if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then
                            echo "Unknown option --${OPTARG}" >&2
                        fi
                        ;;
                esac
                ;;
            h)
                echo "usage: ${script_name} run <-n|--name[=]realm>" >&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

        local pidfile="${config['lock_dir']}/${script_name}-${args['name']}.pid"

        wait_for_pid "${pidfile}"
        create_lock "${pidfile}"

        enter_realm ${args['name']}

        check_files
        request_acme_tiny_certificate
        run_external_script
        process_public_files
        process_private_files
        activate_new_files
        update_realm_symlinks
        check_public_certificate
        run_pki_hooks

    fi
}

schedule_job () {
    local name="${1}"
    local scheduler_type="${2}"
    local min_delay="${3:-30}"
    local max_delay="${4:-300}"

    if [ -n "${scheduler_type}" -a "${scheduler_type}" = "batch" ] ; then
        if type batch > /dev/null 2>&1 ; then
            echo "${script} run -n '${name}'" | batch > /dev/null 2>&1
        fi
    elif [ -n "${scheduler_type}" -a "${scheduler_type}" = "sleep" ] ; then
        ( sleep $(( ( $RANDOM % ${max_delay} ) + ${min_delay} )) ; ${script} run -n "${name}" ) &
    else
        if type batch > /dev/null 2>&1 ; then
            echo "${script} run -n '${name}'" | batch > /dev/null 2>&1
        else
            ( sleep $(( ( $RANDOM % ${max_delay} ) + ${min_delay} )) ; ${script} run -n "${name}" ) &
        fi
    fi
}

sub_schedule () {

    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#*=}
                        ;;
                    realm-dir)
                        args["realm_dir"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    realm-dir=*)
                        args["realm_dir"]=${OPTARG#*=}
                        ;;
                    type)
                        args["type"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    type=*)
                        args["type"]=${OPTARG#*=}
                        ;;
                    min-delay)
                        args["min_delay"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    min-delay=*)
                        args["min_delay"]=${OPTARG#*=}
                        ;;
                    max-delay)
                        args["max_delay"]="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                        ;;
                    max-delay=*)
                        args["max_delay"]=${OPTARG#*=}
                        ;;
                    *)
                        if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then
                            echo "Unknown option --${OPTARG}" >&2
                        fi
                        ;;
                esac
                ;;
            h)
                echo "usage: ${script_name} schedule <-n|--name[=]realm>" >&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
        schedule_job "${args['name']}" "${args['type']:-}" "${args['min_delay']:-}" "${args['max_delay']:-}"
    elif key_exists args "realm_dir" ; then
        for realm in ${args['realm_dir']}/* ; do
            schedule_job "$(basename ${realm})" "${args['type']:-}" "${args['min_delay']:-}" "${args['max_delay']:-}"
        done
    else
        if [ -d ${config['pki_realms']} ] ; then
            for realm in ${config['pki_realms']}/* ; do
                schedule_job "$(basename ${realm})" "${args['type']:-}" "${args['min_delay']:-}" "${args['max_delay']:-}"
            done
        fi
    fi
}

subcommand=""

initialize_environment

if [ $# -gt 0 ] ; then

    subcommand="${1}"

    if [ -n "${subcommand}" ] ; then
        case "${subcommand}" in
            init|new-realm|run|schedule)
                shift
                sub_${subcommand} "${@}"
                ;;

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

else
    print_usage
    exit 1
fi

