#! /bin/sh 

# This script creates a new CA certificate and the configuration files so that
# the grid-cert-request and grid-ca-sign tools can be used with the CA.
# Unlike GT 2-5, this version of the simple CA tool does not create a setup package

set -e

# catch the kill signal (ctrl-c) and do cleanup
trap do_trap 1 2 3 6 9 13 15

##
# create_ca_directory:
# Creates the directory tree needed for a grid CA. The tree contains:
# $GRID_CA_DIR
# +- serial
# +- index.txt
# +- certs/
# +- crl/
# +- newcerts/
# +- private/
#
# On error, this function exits the shell.
#
# @param DIRECTORY
#     Path to the new CA directory
#
create_ca_directory()
{
    _ca_directory="$1"
    _ca_dir_perms="0700"

    if [ -d "${_ca_directory}/." ]; then
        if test -z "${force}"; then
            echo ""
            echo "It looks like a CA has already been setup at this location."
            printf "Do you want to overwrite this CA? (y/n) [n]: "
            read tmp_answer
            if ! expr "${tmp_answer:-n}" : '[Yy]' > /dev/null; then
                exit 1
            fi
        fi
        rm -rf "${_ca_directory}"
    fi

    mkdir -m ${_ca_dir_perms} -p "${_ca_directory}"

    if [ $? -ne 0 -o ! -d "${_ca_directory}/." ]; then
        echo "ERROR: Couldn't create directory: ${_ca_directory}"
        echo "       make sure you have valid permissions set."
        exit 1;
    fi

    # from the CA.sh script - setup the CA directory 
    for directory in certs crl newcerts private ; do
        mkdir -m ${_ca_dir_perms} "${_ca_directory}/${directory}"
        if test $? -ne 0; then
            exec 1>&2
            echo ""
            echo "ERROR: Failed to make directory: ${1}"
            echo "Check permissions of base dir"
            echo ""
            exit 1
        fi
    done

    echo "01" > ${_ca_directory}/serial
    if test $? -ne 0; then
        exec 1>&2
        echo ""
        echo "ERROR: Could not write to ${_ca_directory}/serial"
        echo "Check permissions on the dir"
        echo ""
        exit 1
    fi

    touch ${_ca_directory}/index.txt
    if test $? -ne 0; then
        exec 1>&2
        echo ""
        echo "ERROR: Could not write to ${_ca_directory}/index.txt"
        echo "Check the permissions on the dir"
        echo ""
        exit 1
    fi
}

##
# generate_unique_name: finds a unique name for the CA
#                       based on the hostname
generate_unique_name()
{
    tmp_hostname="$globus_hostname"

    if test -z "${tmp_hostname}"; then
        if [ -n "${noint}" ]; then
            exec 1>&2
        fi
        echo "" 
        echo "Cannot determine this machine's hostname for the CA name."
        echo ""



        if [ -n "${noint}" ]; then
            exit 1
        fi
    fi

    echo "simpleCA-${tmp_hostname}"
    return 0
}

#
# get_ca_subject: gets the CA subject name from the user if not in command-line options
#
get_ca_subject()
{
    varname="${1}"
    _casubject="${request_subject:-cn=Globus Simple CA, ou=$(generate_unique_name), ou=GlobusTest, o=Grid}"

    _got_subject="no"

    while [ "${_got_subject}" = "no" ] ; do
        echo ""
        echo "The unique subject name for this CA is:"
        echo ""
        echo "${_casubject}"
        echo ""

        if [ -n "${noint}" -o -n "${request_subject}" ]; then
            _got_subject="yes"
        else
            printf "Do you want to keep this as the CA subject (y/n) [y]: "
            
            while [ "${_got_subject}" = "no" ]; do
                read _answer

                case "${_answer:-y}" in
                [Nn]*)
                    echo ""

                    while [ "$_got_subject" = "no" ]; do
                        printf "Enter a unique subject name for this CA: "
                        read _casubject
                        echo ""
                        if expr "${_casubject}" : "[Cc][Nn]=.*,.*=.*"  > /dev/null; then
                            _got_subject="yes"
                        else
                            echo "Invalid CA subject name. Please include a common name and at least one"
                            echo "other name component (e.g. CN=Globus, O=Test)"
                        fi
                    done
                    ;;
                [Yy]*)
                    _got_subject="yes"
                    ;;
                *)
                    echo ""
                    echo "Please answer 'y' or 'n'"
                    echo ""
                    ;;
                esac
            done
        fi
    done
    eval "$varname=\"${_casubject}\""
}

get_ca_email()
{
    _varname="${1}"
    _caemail=""
    _defaultemail="${request_email:-${globus_username}@$globus_hostname}"

    if [ -n "${request_email}" -o -n "${noint}" ]; then
        _caemail="${_defaultemail}"
    fi

    while [ -z "${_caemail}" ]; do
        echo ""
        echo "Enter the email of the CA (this is the email where certificate"
        printf "requests will be sent to be signed by the CA) [${_defaultemail}]: "
        read _caemail
        _caemail="${_caemail:-${_defaultemail}}"
    done

    eval "${_varname}=\"${_caemail}\""
}

get_ca_lifetime()
{
    _varname="${1}"

    if [ -z "${noint}" -a -z "${request_days}" ]; then
        cat <<-EOF
	    The CA certificate has an expiration date. Keep in mind that 
       	    once the CA certificate has expired, all the certificates 
       	    signed by that CA become invalid.  A CA should regenerate 
       	    the CA certificate and start re-issuing ca-setup packages 
       	    before the actual CA certificate expires.  This can be done 
       	    by re-running this setup script.  Enter the number of DAYS 
       	    the CA certificate should last before it expires.
	EOF

        printf "[default: 5 years $((365 * 5)) days]: "
        read _ca_cert_days
        _ca_cert_days="${_ca_cert_days:-$((365 * 5))}"
        echo
    else
        _ca_cert_days="${request_days:-$((365 * 5))}"
    fi

    eval "${_varname}=\"${_ca_cert_days}\""
}

##
# save_ca_settings: save the settings determined from this
#                   script to grid-security.conf for this CA
#
save_ca_settings()
{
    _dest="$1"
    _subj="$2"
    _addr="$3"

    # Save stdin and stdout
    exec 3<&0
    exec 4>&1

    # Translate template to configuration file
    exec 0< "${datadir}/globus/simple-ca/grid-security.conf.tmpl" 
    exec 1> "${_dest}/grid-security.conf" 

    # Template variables
    domain="$(globus-domainname)"
    GSI_CA_BASE_DN="$(expr "${_subj}" : "[Cc][Nn]=[^,]*, *\(.*\)")"
    GSI_CA_NAME="$(expr "${_subj}" : "[Cc][Nn]= *\([^,]*\)")"
    GSI_CA_EMAIL="${_addr}"

    # Replace template variables in the configuration file
    while read line; do
        case "$line" in
            "#"*)
                echo "$line"
                ;;
            *)
                eval echo "$line"
                ;;
        esac
    done

    # Restore stdin and stdout
    exec 0<&3
    exec 1>&4

    exec 3<&-
    exec 4>&-
}

############################################################
# create_input_file: generate the input file to be passed as
#                    stdin to the openssl req utility.
############################################################
create_input_file ()
{
    _common_name="$1"
    _config_file="$2"

    # Parse the ssleay configuration file, to determine the
    # correct default 
    exec 3<&0
    exec 0< "${_config_file}" || {
        rc=$?;
        echo 1>&2 "Error opening ${_config_file}";
        exit $rc
    }
    _skip=1

    while read _line; do
        if [ "$_line" = "# BEGIN CONFIG" ]; then
            _skip=0
            continue
        elif [ "$_line" = "# END CONFIG" ]; then
            break
        fi
        if [ "$_skip" -eq 0 ]; then
            _attr="$(expr "$_line" : "\(.*[^ ]\) *=")"
            _value="$(expr "$_line" : "[^=]*= *\(.*\)")"
            if expr "${_attr}" : ".*_default\$" > /dev/null; then
                echo "${_value}"
            fi
        fi
    done
    echo "$_common_name"

    exec 0<&3
    exec 3<&-
}

rfc2253_to_ssl_config()
{
    _type="${1}"
    _name="${2}"

    OLDIFS="$IFS"
    IFS=","
    oucount=0
    ocount=0

    _reversei=""
    for i in ${_name}; do
        _reversei="${i}${_reversei:+,${_reversei}}"
    done

    for i in ${_reversei}; do
        i="${i# }"
        _component="${i%%=*}"
        _value="${i#*=}"

        case "$_component" in
            [Cc])
                printf "%-40s= %s\n" countryName "Country Name (2 letter code)"
                printf "%-40s= %s\n" countryName_default "${_value}"
                printf "%-40s= %s\n" countryName_min "2"
                printf "%-40s= %s\n" countryName_max "2"
                ;;
            [Oo])
                printf "%-40s= %s\n" "$ocount.organizationName" "Level $ocount Organization"
                printf "%-40s= %s\n" "$ocount.organizationName_default" "${_value}"
                ocount=$(($ocount+1))
                ;;
            [Oo][Uu])
                printf "%-40s= %s\n" "$oucount.organizationalUnitName" "Level $oucount Organizational Unit"
                printf "%-40s= %s\n" "$oucount.organizationalUnitName_default" "${_value}"
                oucount=$(($oucount+1))
                ;;
            [Cc][Nn])
                if [ "$_type" = "-user" ]; then
                    printf "%-40s= %s\n" "$oucount.organizationalUnitName" "Level $oucount Organizational Unit"
                    printf "%-40s= %s\n" "$oucount.organizationalUnitName_default" "local"
                fi
                printf "%-40s= %s\n" "commonName" "Name (E.g., John M. Smith)"
                printf "%-40s= %s\n" "commonName_max" "64"
                ;;
            *)
                echo 1>&2 "Unknown subject name component"
                exit 1
                ;;
        esac
    done
    IFS="$OLDIFS"
}

rfc2253_to_oneline()
{
    _name="${1}"

    OLDIFS="$IFS"
    IFS=","
    _oneline=""

    for i in ${_name}; do
        i="${i# }"
        _component="${i%%=*}"
        _value="${i#*=}"

        case "$_component" in
            [Cc])
                _oneline="/C=${_value}${_oneline}"
                ;;
            [Oo])
                _oneline="/O=${_value}${_oneline}"
                ;;
            [Oo][Uu])
                _oneline="/OU=${_value}${_oneline}"
                ;;
            [Cc][Nn])
                _oneline="/CN=${_value}${_oneline}"
                ;;
            *)
                echo 1>&2 "Unknown subject name component"
                exit 1
                ;;
        esac
    done
    IFS="$OLDIFS"
    echo "$_oneline"
}

generate_signing_policy()
{
    _cadir="${1}"
    _caname="${2}"
    _template="${datadir}/globus/simple-ca/ca-signing-policy.tmpl"

    GRID_CA_SUBJECT="$(rfc2253_to_oneline "${_caname}")"
    rc=$?
    if [ $rc -ne 0 ]; then
        exit $rc
    fi

    GRID_CA_COND_SUBJECT="\"${GRID_CA_SUBJECT%%/CN=*}/*\""

    exec 3<&0
    exec 4>&1

    exec 0< "${_template}"
    exec 1> "${_cadir}/signing-policy"

    while read line; do
        if expr "${line}" : ".*#" > /dev/null; then
            comment="#${line#*#}"
        else
            comment=""
        fi
        precomment="${line%%${comment}}"

        eval lineval="\"${precomment}\""
        echo "${lineval}${comment}"
    done


    exec 0<&3
    exec 1>&4

    exec 3<&-
    exec 4>&-
}

setup_grid_security_dir()
{
    _cadir="${1}"
    _destdir="${2}"
    _cahash="$(openssl x509 -in "${_cadir}/cacert.pem" -noout -hash)"

    printf "Installing new CA files to ${_destdir}... "

    if [ ! -d "${_destdir}" ]; then
        mkdir -m 0755 -p "${_destdir}"
    fi

    cp "${_cadir}/cacert.pem" "${_destdir}/${_cahash}.0"
    cp "${_cadir}/signing-policy" "${_destdir}/${_cahash}.signing_policy"
    cp "${_cadir}/grid-security.conf" "${_destdir}/grid-security.conf.${_cahash}"
    cp "${_cadir}/globus-user-ssl.conf" "${_destdir}/globus-user-ssl.conf.${_cahash}"
    cp "${_cadir}/globus-host-ssl.conf" "${_destdir}/globus-host-ssl.conf.${_cahash}"

    echo "done"
}

create_ssl_config()
{
(
    _type="${1}"
    _template="${2}"
    _caname="${3}"
    _cadir="${4}"

    . "${_cadir}/grid-security.conf"

    _ssl_conf_template=${datadir}/globus/simple-ca/grid-ca-ssl.conf.tmpl

    exec 3<&0
    exec 0< "${_ssl_conf_template}"

    _skip=0

    GRID_CA_DIR="${_cadir}"
    while read line; do
        if expr "${line}" : ".*#" > /dev/null; then
            comment="#${line#*#}"
        else
            comment=""
        fi
        precomment="${line%%${comment}}"

        if [ "$comment" = "# BEGIN CONFIG" ]; then
            _skip=1
            echo "${comment}"
            rfc2253_to_ssl_config "${_type}" "${_caname}"
        elif [ "$comment" = "# END CONFIG" ]; then
            _skip=0
        fi
        if [ $_skip -eq 1 ]; then
            continue
        fi
        case "$precomment" in
            \[* )
                echo "${precomment}${comment}"
                ;;
            *)
                eval printf "%s" "\"${precomment}\""
                echo "${comment}"
                ;;
        esac
    done

    exec 0<&3
    exec 3<&-
)
}

############################################################
# generate_ca_certificate: the meat & potatoes - calls the 
#                          openssl req utility that creates
#                          the CA certificate
############################################################
generate_ca_certificate() 
{
    _cadir="${1}"
    _caname="${2}"
    _privatedir="${_cadir}/private"
    _ca_ssl_conf="${_cadir}/grid-ca-ssl.conf"
    _user_ssl_conf="${_cadir}/globus-user-ssl.conf"
    _host_ssl_conf="${_cadir}/globus-host-ssl.conf"

    _ssl_conf_template="${datadir}/globus/simple-ca/grid-ca-ssl.conf.tmpl"

    create_ssl_config -ca "${_ssl_conf_template}" "${_caname}" "${_cadir}" > "${_ca_ssl_conf}"
    create_ssl_config -user "${_ssl_conf_template}" "${_caname}" "${_cadir}" > "${_user_ssl_conf}"
    create_ssl_config -host "${_ssl_conf_template}" "${_caname}" "${_cadir}" > "${_host_ssl_conf}"

    CA_REQ_INPUT=${_privatedir}/tmp_openssl_input.conf

    create_input_file "${GSI_CA_NAME}" "${_ca_ssl_conf}" > ${CA_REQ_INPUT}

    if test -n "${request_password}"; then
        password_option="-passout pass:${request_password} "
    elif test -n "${noint}"; then
        password_option="-passout pass:globus "
    else
        password_option=""
    fi

    # create CA certificate
    if [ -n "${verbose}" ]; then
        openssl req ${openssl_options} ${password_option} -config "${_ca_ssl_conf}" \
            -x509 -days ${CA_CERT_DAYS} \
            -newkey rsa:1024 -keyout ${CA_KEY_FILE} \
            -out ${CA_CERT_FILE} < ${CA_REQ_INPUT}
        RET=$?
    else
        openssl req ${openssl_options} ${password_option} -config "${_ca_ssl_conf}" \
            -x509 -days ${CA_CERT_DAYS} \
            -newkey rsa:1024 -keyout ${CA_KEY_FILE} \
            -out ${CA_CERT_FILE} < ${CA_REQ_INPUT} > openssl_req.log 2>&1
        RET=$?
    fi


    if [ "${RET}" -eq 0 -a -n "${verbose}" ]; then
        tput clear
    elif [ "${RET}" -ne 0 ]; then
        echo "Error number ${RET} was returned by openssl" 1>&2
        exit ${RET}
    fi
}


############################################################
# do_trap:  catches any abortive signals and does cleanup
############################################################
do_trap() {

    echo ""
    echo ""
    echo "Normal program execution interrupted.  You will"
    echo "need to rerun the script:"
    echo ""
    echo "\${GLOBUS_LOCATION}/setup/globus/setup-simple-ca"
    echo ""
    echo "to setup the simple CA."
    echo ""

    exit 1
}


############################################################
# main code section
############################################################

if test -n "${GLOBUS_LOCATION}" ; then
    prefix="${GLOBUS_LOCATION}"
else
    prefix="/usr"
fi

exec_prefix="${prefix}"
sbindir="${exec_prefix}/sbin"
bindir="${exec_prefix}/bin"
libdir="${prefix}/lib"
includedir="${prefix}/include/globus"
datarootdir="${prefix}/share"
datadir="${datarootdir}"
libexecdir="${datadir}/globus"
sysconfdir="/etc"
sharedstatedir="/var/lib"
localstatedir="/var"
aclocaldir="${datadir}/globus/aclocal"
. "${libexecdir}/globus-script-initializer"
globus_source "${libexecdir}/globus-sh-tools-vars.sh"

PROGRAM_NAME="${0##*/}"
PROGRAM_VERSION="$(expr '$Revision: 1.4.10.2 $' : ".Revision: \([0-9\.]*\) .")"
PACKAGE="globus_simple_ca"
VERSION="3.5"
DIRT_TIMESTAMP="1381425335"
DIRT_BRANCH_ID="83"


short_usage="$PROGRAM_NAME [-help] [ options ...] [ openssl options ...]"

printhelp() {
    option="${1}"
    helpstr="${2}"
    optwidth="${optwidth:-$((${COLUMNS:-80} / 3))}"
    if [ "$optwidth" -gt 30 ]; then
        optwidth=30
    fi
    helpwidth="${helpwidth:-$((${COLUMNS:-80} - $optwidth - 6))}"
    helpstrformatted="$(echo "${helpstr}" | tr -sd '\n\t' ' ' | \
            fold -sw ${helpwidth})"

    OLDIFS="$IFS"
    IFS="
"
    first=1

    for x in $helpstrformatted; do
        printf "    %-${optwidth}s %-${helpwidth}s\n" "${first:+$option}" "$x"
        first=""
    done
    IFS="$OLDIFS"
}

globus_hostname="$(globus-hostname)"
globus_username="$(id -un)"

long_usage () {
    cat <<EOF

${short_usage}

  Note: Many of the following options can be used instead of allowing
        the script to interactively request configration info.  If
        its not clear what to do, let the interactive prompts guide you.

  Options:
EOF
    printhelp "-help, -?, -h, -usage" "shows this help message"
    printhelp "-verbose" "Show verbose output [unset]"
    printhelp "-force" "Overwite existing CA if one exists [unset]"
    printhelp "-noint" "Run in non-interactive mode. This will choose
            defaults for parameters or those specified on the command line
            without prompting. This option also implies -force. [unset]"
    printhelp "-dir DIRECTORY" "Create the SimpleCA in DIRECTORY.
            [$( ([ -w "${localstatedir}/lib/." ] &&  \
                    echo \${localstatedir}/lib/globus/simple_ca ) \
                    || echo \${HOME}/.globus/simpleCA)]"
    printhelp "-subject SUBJECT" "Create CA with the subject name SUBJECT 
           [cn=Globus Simple CA, ou=$(generate_unique_name),
           ou=GlobusTest, o=Grid]"
    printhelp "-email ADDRESS" "Include instructions to send certificate
            requests to ADDRESS [${globus_username}@${globus_hostname}]"
    printhelp "-days DAYS" "Create a CA certificate that lasts for DAYS days
            [$((365 * 5))]"
    printhelp "-pass PASSWORD" "Set the password for the CA's private key
            to PASSWORD.  Since the password is visible to utilities, this
            should only be used where security is not important. [globus]"
    printhelp "-nobuild" "Don't create a package for distributing
            configuration files for this CA. These can be created later by
            using the grid-ca-package utility [unset]"
    printhelp "-g" "Create a GPT binary package containing the CA and
            configuration files [unset]"
    printhelp "-b" "Create a GPT binary package containing the CA and
            configuration files compatible with GPT 3.2 and GT 4 and 5 [unset]"
    printhelp "-openssl-help" "Show help text for openssl"
    printhelp "[OPENSSL-OPTIONS]" "Specify additional options to pass to the 
            openssl command.  Use with caution, some options will conflict 
            with this script."
}

. "${libexecdir}/globus-args-parser-header"

readCommandLine () {
  # Expects $@ from the shell invocation

  while test -n "$1"; do
    case $1 in
      -\?|-h|-help|-usage)
         long_usage
         exit 0
         ;;
     -g)
         gpt_package=1
         shift
         ;;
     -b)
         gpt_package=1
         backward_compatible=1
         shift
         ;;
     -dir)
         tmp_ca_dir="$2"
         if test -z "${tmp_ca_dir}"; then
            echo "ERROR: the -dir option expects a directory."
            exit 1;
         fi
         if test "${tmp_ca_dir##/*}" = ""; then
             GRID_CA_DIR="${tmp_ca_dir}"
         else
             GRID_CA_DIR="$PWD/${tmp_ca_dir}"
         fi
         
         shift ; shift
         ;;
     -force)
         force="yes"
         shift
         ;;
     -subject)
         shift
         request_subject="${1}"
         shift
         if ! expr "${request_subject}" : "[Cc][Nn]=.*,.*=.*"  > /dev/null; then
             exec 1>&2
             echo "Invalid CA subject name. Please include a common name and at least one"
             echo "other name component (e.g. CN=Globus, O=Test)"
             exit 1
         fi
         ;;
     -email)
         request_email="${2}"
         shift ; shift
         ;;
     -days)
         request_days="${2}"
         shift ; shift
         ;;
     -pass)
         request_password="${2}"
         shift ; shift
         ;;
     -nobuild)
         nobuild="yes"
         shift
         ;;
     -noint)
         noint="yes"
         force="yes"
         shift
         ;;
     -verbose)
         verbose="yes"
         shift
         ;;
     -openssl-help)
         shift;
         openssl req -help;
         exit;
         ;;

     *)
         openssl_options="$openssl_options $1"
         shift;
         ;;
    esac
  done
}

# MAIN
readCommandLine "$@"

# setup variables used by the script
if test -z "${GRID_CA_DIR}"; then
    if [ -d "${localstatedir}/lib/." -a -w "${localstatedir}/lib/." ]; then
        GRID_CA_DIR="$localstatedir/lib/globus/simple_ca"
        default_loc="yes"
    else
        GRID_CA_DIR="${HOME}/.globus/simpleCA"
        default_loc="yes"
    fi
fi


CA_KEY_FILE="${GRID_CA_DIR}/private/cakey.pem"
CA_CERT_FILE="${GRID_CA_DIR}/cacert.pem"

${GLOBUS_SH_CAT-cat} <<EOF
 

    C e r t i f i c a t e    A u t h o r i t y    S e t u p

This script will setup a Certificate Authority for signing Globus
users certificates.  It will also generate a simple CA package
that can be distributed to the users of the CA.

The CA information about the certificates it distributes will
be kept in:

${GRID_CA_DIR}
EOF

create_ca_directory "${GRID_CA_DIR}"

# These functions assign a value to the variable named by their first parameter so that
# they can prompt for input
get_ca_subject grid_ca_subject
get_ca_email grid_ca_email
get_ca_lifetime CA_CERT_DAYS

save_ca_settings "${GRID_CA_DIR}" "$grid_ca_subject" "$grid_ca_email"
generate_ca_certificate "${GRID_CA_DIR}" "${grid_ca_subject}"
generate_signing_policy "${GRID_CA_DIR}" "${grid_ca_subject}"
if [ -w "${sysconfdir}/grid-security/certificates/." -o "$(id -u)" -eq 0 ]; then
    setup_grid_security_dir "${GRID_CA_DIR}" "${sysconfdir}/grid-security/certificates"
elif [ -w "${datadir}/certificates" ]; then
    setup_grid_security_dir "${GRID_CA_DIR}" "${datadir}/certificates"
else
    echo "Insufficient permissions to install CA into the trusted certifiicate"
    echo "directory (tried \${sysconfdir}/grid-security/certificates and"
    echo "\${datadir}/certificates)"
fi

cahash="$(openssl x509 -in "${_cadir}/cacert.pem" -noout -hash)"

if [ -z "$nobuild" ]; then
    grid-ca-package ${backward_compatible:+-b} ${gpt_package:+-g} -cadir "${GRID_CA_DIR}"
fi

exit 0
