#!/bin/sh

if test -z "${GLOBUS_LOCATION}"; then
    echo ""
    echo "ERROR: Please set GLOBUS_LOCATION to the Globus installation directory before"
    echo "running this script"
    echo ""
    exit 1
fi

. ${GLOBUS_LOCATION}/libexec/globus-script-initializer
globus_source ${GLOBUS_LOCATION}/libexec/globus-sh-tools.sh

if [ ! -z "${GRID_SECURITY_DIR}" ] ; then
    secconfdir="${GRID_SECURITY_DIR}"
    trusted_certs_dir="${secconfdir}/certificates"
elif [ -r "/etc/grid-security/globus-user-ssl.conf" ] && 
     [ -r "/etc/grid-security/globus-host-ssl.conf" ] ; then
    secconfdir="/etc/grid-security"
    trusted_certs_dir="${secconfdir}/certificates"
elif [ -r "${GLOBUS_LOCATION}/etc/globus-user-ssl.conf" ] &&
     [ -r "${GLOBUS_LOCATION}/etc/globus-host-ssl.conf" ] ; then
    secconfdir="${GLOBUS_LOCATION}/etc"
    trusted_certs_dir="${GLOBUS_LOCATION}/share/certificates"
else
    echo "Can't find valid gsi config files.  Run setup-gsi."
    echo ""
    exit 1
fi

#
# Sample script to generate a certificate request which can be sent 
# to the Globus CA, who will sign it. This script uses the 
# globus-ssl.conf file.
#
# When generating a certificate request and private key for a 
# globus gatekeeper daemon, use the -nopw option, so the
# key is not protected by pass phrase. 
#
PROGRAM_NAME=`echo $0 | ${GLOBUS_SH_SED-sed} 's|.*/||g'`

PROGRAM_VERSION=`echo '$Revision: 1.6 $'| ${GLOBUS_SH_SED-sed} -e 's|\\$||g' -e 's|Revision: \(.*\)|\1|'`

VERSION="0.12"

PACKAGE="globus_gsi_cert_utils"

DIRT_TIMESTAMP="1055342205"
DIRT_BRANCH_ID="42"

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


###########################################################
# long_usage
#   Provide usage instructions to the end user
###########################################################

long_usage ()
{
    ${GLOBUS_SH_CAT-cat} >&2 <<EOF

${short_usage}

  Example Usage:

    Creating a user certifcate:
      grid-cert-request

    Creating a host or gatekeeper certifcate:
      grid-cert-request -host [my.host.fqdn]

    Creating a LDAP server certificate:
      grid-cert-request -service ldap -host [my.host.fqdn]

  Options:
    
    -version           : Display version
    -?, -h, -help,     : Display usage
    -usage 
    -cn <name>,        : Common name of the user
    -commonname <name>
    -service <service> : Create certificate for a service. Requires
                         the -host option and implies that the generated 
                         key will not be password protected (ie implies -nopw).
    -host <FQDN>       : Create certificate for a host named <FQDN>
    -dir <dir_name>    : Changes the directory the private key and certificate 
                         request will be placed in. By default user 
                         certificates are placed in $HOME/.globus, host 
                         certificates are placed in /etc/grid-security and 
                         service certificates are place in 
                         /etc/grid-security/<service>.
    -prefix <prefix>   : Causes the generated files to be named 
                         <prefix>cert.pem, <prefix>key.pem and
                         <prefix>cert_request.pem
    -nopw,             : Create certificate without a passwd
    -nodes,
    -nopassphrase,
    -verbose           : Don't clear the screen
    -int[eractive]     : Prompt user for each component of the DN
    -force             : Overwrites preexisting certifictes
    -ca                : Will ask which CA is to be used (interactive)
    -ca <hash>         : Will use the CA with hash value <hash>
EOF
}

globus_source ${libexecdir}/globus-args-parser-header $@

###########################################################
# set_non_default_ca
#   Change the CA used for generating the certificate
###########################################################

set_non_default_ca() 
{
    if [ "${nondefaultca}" == "true" ]; then

        INSTALLED_CERTS="`echo ${trusted_certs_dir}/*.0`"

        if [ -z "${INSTALLED_CERTS}" ]; then
            echo "No CA's have been installed on this host!"
            echo "To run grid-cert-request, a CA must first"
            echo "be installed"
            exit 1;
        else
            echo
            echo "The available CA configurations installed on this host are:"
            echo
        fi
    
        index=1
        
        for cert in ${trusted_certs_dir}/*.0; do
            eval "CA${index}=${cert}"
            TEMP_SUBJECT=`openssl x509 -in ${cert} -noout -subject`
            eval "CA_SUBJECT${index}=\"`echo ${TEMP_SUBJECT} | ${GLOBUS_SH_SED-sed} -e \"s|subject= ||\"`\""
            eval "CA_HASH${index}=`openssl x509 -in ${cert} -noout -hash`"
            eval "echo \"$index)  \${CA_HASH${index}} - \${CA_SUBJECT${index}}\""
            index=`expr $index + 1`
        done
    
        echo
        echo -n "Enter the index number of the CA you want to sign your cert request: "
        read CA_CHOSEN_INDEX
    
        if [ 0 -ge $CA_CHOSEN_INDEX ] || [ $index -le $CA_CHOSEN_INDEX ] ; then
            echo "${CA_CHOSEN_INDEX} is not a valid index!"
            exit 1
        fi
    
        eval "CA_SUBJECT=\${CA_SUBJECT${CA_CHOSEN_INDEX}}"
        eval "CA_HASH=\${CA_HASH${CA_CHOSEN_INDEX}}"

    elif [ ! -z "${nondefaultca}" ] ; then
        CA_HASH=${nondefaultca}
        TEMP_SUBJECT=`openssl x509 -in ${trusted_certs_dir}/${CA_HASH}.0 -noout -subject`
        eval "CA_SUBJECT=\"`echo ${TEMP_SUBJECT} | ${GLOBUS_SH_SED-sed} -e \"s|subject= ||\"`\""
    fi

    echo
    echo "Using CA: ${CA_HASH} - ${CA_SUBJECT}"
    echo

    SSL_CONFIG=${trusted_certs_dir}/globus-user-ssl.conf.${CA_HASH}
    SSL_USER_CONFIG=${trusted_certs_dir}/globus-user-ssl.conf.${CA_HASH}
    SSL_HOST_CONFIG=${trusted_certs_dir}/globus-host-ssl.conf.${CA_HASH}
    security_conf=${trusted_certs_dir}/grid-security.conf.${CA_HASH}
}


###########################################################
# abort_cleanup
#   Remove temp files generated by this script
###########################################################

abort_cleanup () 
{
    ${GLOBUS_SH_RM-rm} -f ${CERT_FILE} ${KEY_FILE} \
                          ${REQUEST_FILE} ${RAND_TEMP} \
                          ${REQ_HEAD} ${REQ_INPUT} ${REQ_OUTPUT}
}


###########################################################
# absolute_path
#    Make these absolute file names if they are not
###########################################################

absolute_path () 
{
   _file_name="$1"

   case $_file_name in
      /*)
        echo ${_file_name}
        ;;
      *)
        echo ${PWD}/${_file_name}
    esac
}


###########################################################
# read_command_line
#   Command line parsing
###########################################################

read_command_line () 
{
    # Expects $* from the shell invocation

    while [ "X$1" !=  "X" ]
    do
        case $1 in
            -\?|-h|-help|-usage)
                long_usage
                exit 0
                ;;
            -cn | -commonname)
                COMMON_NAME="$2"
                shift ; shift
                ;;
            -host)
                SERVICE_HOST="$2"
                SERVICE_HOST=`echo ${SERVICE_HOST}|${GLOBUS_SH_TR-tr} 'A-Z' 'a-z'`
                if echo ${SERVICE_HOST}|grep "localhost" >/dev/null || \
                   echo ${SERVICE_HOST}|grep "localdomain" >/dev/null; then
                    globus_args_option_error "$1" "$2 may not contain localhost or localdomain"
                fi
                NO_DES="-nodes"
                shift ; shift
                ;;
            -service)
                SERVICE="$2"
                SERVICE=`echo ${SERVICE}|${GLOBUS_SH_TR-tr} 'A-Z' 'a-z'`
                shift ; shift
                ;;
            -dir)
                TARGET_DIR="$2"
                TARGET_DIR="`absolute_path ${TARGET_DIR}`"
                shift ; shift
                ;;
            -prefix)
                PREFIX="$2"
                shift ; shift
                ;;
            -nopw|-nodes|-nopassphrase)
                NO_DES="-nodes"
                shift
                ;;
            -verbose)
                VERBOSE="yes"
                shift
                ;;
            -int|-interactive)
                INTERACTIVE="TRUE"
                shift
                ;;
            -force)
                FORCE="TRUE"
                shift
                ;;
            -ca)
                if [ ! -z "$2" ] && \
                   [ -z "`echo $2 | ${GLOBUS_SH_SED-sed} -e "s|[^-]*||g"`" ]
                then
                    nondefaultca=$2
                    shift
                else
                    echo "nondefaultca=true"
                    nondefaultca="true"
                fi
                set_non_default_ca
                shift
                ;;
            *)
                globus_args_unrecognized_option "$1"
                ;;
        esac
    done

    if test -z "${SERVICE}"; then 
        if test -z "${SERVICE_HOST}"; then
            SERVICE="user"
        else
            SERVICE="host"
        fi
    else
        if test -z "${SERVICE_HOST}"; then
            globus_args_option_error "-service" "Requires thes -host option"
        fi
    fi

    if test -z "${TARGET_DIR}"; then
        case ${SERVICE} in
            user)
                TARGET_DIR="$HOME/.globus"
                ;;
            host)
                TARGET_DIR="${secconfdir}"
                ;;
            *)
                TARGET_DIR="${secconfdir}/${SERVICE}"
                ;;
        esac
    fi

    if test -z "${PREFIX}"; then
        REQUEST_FILE="${TARGET_DIR}/${SERVICE}cert_request.pem"
        CERT_FILE="${TARGET_DIR}/${SERVICE}cert.pem"
        KEY_FILE="${TARGET_DIR}/${SERVICE}key.pem"
    else
        REQUEST_FILE="${TARGET_DIR}/${PREFIX}cert_request.pem"
        CERT_FILE="${TARGET_DIR}/${PREFIX}cert.pem"
        KEY_FILE="${TARGET_DIR}/${PREFIX}key.pem"
    fi
}



###########################################################
# create_input_file
#   Generate a inputfile to be given to SSL that fully 
#   specifies the DN of the user
#   files
###########################################################

create_input_file () 
{
    _common_name="$1"
    _config_file="$2"

    # Parse the ssleay configuration file, to determine the
    # correct default parameters

    ${GLOBUS_SH_AWK-awk}  < $_config_file '
 
  /^\[ req_distinguished_name \]/ {
     start_parsing=1;
     next;
  }

  /^\[ .*/ {
     start_parsing=0;
     next;
  }

  /^[a-zA-Z0-9\.]*_default[ \t]*=/ && start_parsing==1 {
     split($0, a, "=");
     # default value is in a[2], but we should strip of leading ws
     for(i=1;substr(a[2],i,1) == " " || substr(a[2],i,1) == "\t"; i++);
     print substr(a[2], i);
     next;
}
'

    echo ${_common_name}
}


###########################################################
# create_request_header
###########################################################

create_request_header () 
{

    ${GLOBUS_SH_CAT-cat} <<EOF
This is a Certificate Request file:

It should be mailed to ${GSI_CA_EMAIL_ADDR}

   
=========================================================================
Certificate Subject:

    ${SUBJECT}

The above string is known as your ${SERVICE} certificate subject, and it 
uniquely identifies this ${SERVICE}.

To install this ${SERVICE} certificate, please save this e-mail message 
into the following file.

 
${CERT_FILE}


      You need not edit this message in any way. Simply 
      save this e-mail message to the file.



If you have any questions about the certificate contact
the ${GSI_CA_NAME} at ${GSI_CA_EMAIL_ADDR}

EOF
  
}

###########################################################
# check4certs:  
#   Ensure that the user does not overwrite their
#   security files.
###########################################################
check4certs () 
{
    _exists="FALSE"

    if [ -r ${REQUEST_FILE} ] ; then
        ${GLOBUS_SH_PRINTF-printf} "\n    ${REQUEST_FILE} already exists" 1>&2
        ${GLOBUS_SH_CHMOD-chmod} u+w ${REQUEST_FILE}
        _exists=TRUE
    fi
    
    if [ -r ${CERT_FILE} ] ; then
        ${GLOBUS_SH_PRINTF-printf} "\n    ${CERT_FILE} already exists" 1>&2
        ${GLOBUS_SH_CHMOD-chmod} u+w ${CERT_FILE}
        _exists=TRUE
    fi
    
    if [ -r ${KEY_FILE} ] ; then
        ${GLOBUS_SH_PRINTF-printf} "\n    ${KEY_FILE} already exists" 1>&2
        ${GLOBUS_SH_CHMOD-chmod} u+w ${KEY_FILE}
        _exists=TRUE
    fi

  
    if [ "X$_exists" = "XTRUE" ] ; then
        if [ "X$FORCE" = "XTRUE" ] ; then
            ${GLOBUS_SH_RM-rm} -f ${CERT_FILE} ${KEY_FILE} ${REQUEST_FILE}
            echo
            echo
        else 
            ${GLOBUS_SH_PRINTF-printf} "\n\nIf you wish to overwrite, run the script again with -force.\n" 1>&2
            exit 1
        fi
    fi
}


###########################################################
# setup_target_dir:  
#   Create a directory in the HOME directory of the user
#   to store globus related stuff.
###########################################################

setup_target_dir () 
{
    if test ! -d ${TARGET_DIR} ; then
        ${GLOBUS_SH_MKDIR-mkdir} ${TARGET_DIR}
        if [ $? -ne 0 ] ; then
            ${GLOBUS_SH_PRINTF-printf} "\n\nUnable to create ${TARGET_DIR}. Pleas make sure that you have the right to create this directory before running this script.\n" 1>&2
            exit $?
        fi 
    fi

    if test ! -w ${TARGET_DIR} ; then
        ${GLOBUS_SH_PRINTF-printf} "\n\nUnable to access ${TARGET_DIR}. Pleas make sure that you have the right to create files in this directory before running this script.\n" 1>&2
        exit 1
    fi
}


###########################################################
# get_user_CN
#   Determine the name of the user
###########################################################
get_user_CN () 
{
    _common_name="${COMMON_NAME}"

    # 1. Command line argument
    # 2. Query the system
    # 3. Prompt the user

    if [ -z "${_common_name}" ] ; then 
        ${GLOBUS_SH_FINGER-finger} -lm ${USERID}  >/dev/null 2>&1
        if [ $? -eq 0 ] ; then 
            _common_name="`${GLOBUS_SH_FINGER-finger} -lm ${USERID}      |\
                           ${GLOBUS_SH_GREP-grep} ${USERID}              |\
                           ${GLOBUS_SH_AWK-awk} -F: '{ print $3; exit }' |\
                           ${GLOBUS_SH_CUT-cut} -c2- `"
        fi
    fi

   if [ -z "${_common_name}" ] || [ "${_common_name}" = "(null)" ];  then
      ${GLOBUS_SH_PRINTF-printf} "Enter your name, e.g., John Smith: " 1>&2
      read _common_name
   fi

   echo ${_common_name}
}



###########################################################
# get_host_CN
#   Determine the name of the host for a host certificate
###########################################################

get_host_CN () 
{
   _common_name="${COMMON_NAME}"

   # 1. Command line -cn argument
   # 2. Create from -host argument

   if [ -z "${_common_name}" ] ; then 
        # Check SERVICE_HOST and make sure it looks like a FQDN
        ${GLOBUS_SH_ECHO-echo} ${SERVICE_HOST} | \
            ${GLOBUS_SH_GREP-grep} "\." >/dev/null 2>&1
        if [ $? -eq 1 ] ; then
            ${GLOBUS_SH_ECHO-echo} "The hostname ${SERVICE_HOST} does not appear to be fully qualified." 1>&2
            ${GLOBUS_SH_PRINTF-printf} "Do you wish to continue? [n] " 1>&2
            read _response

            case X${_response} in
                Xy|XY|Xyes|XYES|XYes)
                    ;;

                *)
                    ${GLOBUS_SH_ECHO-echo} "Aborting" 1>&2
                    exit 1
                    ;;
            esac
        fi
      
        _common_name="${SERVICE}/${SERVICE_HOST}"
    fi

    echo ${_common_name}
}



###########################################################
# create_certs
#   Create the certificate, key, and certificate request
#   files
###########################################################
create_certs () {
  
    if [ -z "${NO_DES}" ] ; then
        echo "A certificate request and private key is being created."
        echo "You will be asked to enter a PEM pass phrase."
        echo "This pass phrase is akin to your account password, "
        echo "and is used to protect your key file."
        echo "If you forget your pass phrase, you will need to"
        echo "obtain a new certificate."
        echo
    fi

    #------------------------
    # Create the Certificate File
    umask 022
    ${GLOBUS_SH_TOUCH-touch} ${CERT_FILE}


    #------------------------
    # Create some semi random data for key generation 
    umask 066
    ${GLOBUS_SH_TOUCH-touch} ${RAND_TEMP}
    if [ -r /dev/urandom ] ; then
        head -c 1000 /dev/urandom >> ${RAND_TEMP} 2>&1
    fi
    date >> ${RAND_TEMP} 2>&1
    netstat -in >> ${RAND_TEMP} 2>&1
    ps -ef >> ${RAND_TEMP} 2>&1
    ls -ln ${HOME} >> ${RAND_TEMP} 2>&1
    ls -ln /tmp >> ${RAND_TEMP} 2>&1

    umask 266
    #------------------------
    # Create the Key and Request Files

    if test ! -z "${INTERACTIVE}" ; then
        ${SSL_EXEC} req -new -keyout ${KEY_FILE} -out ${REQ_OUTPUT} \
              -rand ${RAND_TEMP}:/var/adm/wtmp:/var/log/messages \
              -config ${SSL_USER_CONFIG} ${NO_DES}
        RET=$?
        ${GLOBUS_SH_RM-rm} -f ${RAND_TEMP}
    else
        if test "${SERVICE}" = "user" ; then
            used_config="${SSL_USER_CONFIG}"
        else
            used_config="${SSL_HOST_CONFIG}"
        fi

        create_input_file "${COMMON_NAME}" "${used_config}" > ${REQ_INPUT}

        ${SSL_EXEC} req -new -keyout ${KEY_FILE} \
              -rand ${RAND_TEMP}:/var/adm/wtmp:/var/log/messages \
              -out ${REQ_OUTPUT} -config ${used_config} \
              ${NO_DES} < ${REQ_INPUT} 
        RET=$?

        ${GLOBUS_SH_RM-rm} -f ${RAND_TEMP}
        ${GLOBUS_SH_RM-rm} -f ${REQ_INPUT}

        # You can't separate the SSL output, it all goes to stderr
        # including the prompts.
        # Don't remove ssleay output on error as it may be useful
        
        if test ${RET} -eq 0 -a -z "${VERBOSE}" ; then
            ${GLOBUS_SH_CLEAR-clear}
        fi
    fi

    if [ ${RET} -ne 0 ] ; then
        echo "Error number ${RET} was returned by " 1>&2
        echo "   ${SSL_EXEC}"                    1>&2
        exit ${RET}
    fi

    umask 022
    
    #------------------------
    # Insert instructions into the request file


    SUBJECT="`${SSL_EXEC} req -text -noout < ${REQ_OUTPUT} 2>&1 |\
              ${GLOBUS_SH_GREP-grep} 'Subject:' | ${GLOBUS_SH_AWK-awk} -F: '{print $2}' |\
              ${GLOBUS_SH_CUT-cut} -c2- `"

    #Convert the subject to the correct form.
    SUBJECT=`echo "/"${SUBJECT} | ${GLOBUS_SH_SED-sed} -e 's|, |/|g'`

    create_request_header >${REQ_HEAD}

    # Finalize the Request file.
    ${GLOBUS_SH_CAT-cat} ${REQ_HEAD} ${REQ_OUTPUT} >${REQUEST_FILE}
    ${GLOBUS_SH_RM-rm} -f ${REQ_HEAD} ${REQ_OUTPUT}
}

###########################################################
# emit_directions
#   Provide instructions to the end user
###########################################################
emit_directions () {

  if test "${SERVICE}" = "user" ; then
    echo 
    echo "A private key and a certificate request has been generated with the subject:" 
 
    echo 
    echo "${SUBJECT}"
    echo
    echo "If the CN=${COMMON_NAME} is not appropriate, rerun this"
    echo "script with the -force -cn \"Common Name\" options."
    echo
    echo "Your private key is stored in ${KEY_FILE}"
    echo "Your request is stored in ${REQUEST_FILE}"
    echo
    echo "Please e-mail the request to the ${GSI_CA_NAME} ${GSI_CA_EMAIL_ADDR}"
    echo "You may use a command similar to the following:" 
    echo
    echo "  cat ${REQUEST_FILE} | mail ${GSI_CA_EMAIL_ADDR}"
    echo
    echo "Only use the above if this machine can send AND receive e-mail. if not, please"
    echo "mail using some other method."
    echo
    echo "Your certificate will be mailed to you within two working days."
    echo "If you receive no response, contact ${GSI_CA_NAME} at ${GSI_CA_EMAIL_ADDR}"

  else 
    echo 
    echo "A private ${SERVICE} key and a certificate request has been generated"
    echo "with the subject:" 
    echo 
    echo "${SUBJECT}"
    echo
    echo "----------------------------------------------------------"
    echo
    echo "The private key is stored in ${KEY_FILE}"
    echo "The request is stored in ${REQUEST_FILE}"
    echo
    echo "Please e-mail the request to the ${GSI_CA_NAME} ${GSI_CA_EMAIL_ADDR}"
    echo "You may use a command similar to the following:"
    echo
    echo " cat ${REQUEST_FILE} | mail ${GSI_CA_EMAIL_ADDR}"
    echo
    echo "Only use the above if this machine can send AND receive e-mail. if not, please"
    echo "mail using some other method."
    echo
    echo "Your certificate will be mailed to you within two working days."
    echo "If you receive no response, contact ${GSI_CA_NAME} at ${GSI_CA_EMAIL_ADDR}"
  
  fi

}


###########################################################
# MAIN
###########################################################


#SSL related variables
PATH=${GLOBUS_LOCATION}/bin:${PATH}
SSL_EXEC="${GLOBUS_LOCATION}/bin/openssl"
SSL_CONFIG="${secconfdir}/globus-user-ssl.conf"
SSL_USER_CONFIG="${secconfdir}/globus-user-ssl.conf"
SSL_HOST_CONFIG="${secconfdir}/globus-host-ssl.conf"
security_conf="${secconfdir}/grid-security.conf"

# Info sent with the certificate
SUBJECT=
USERID="`${GLOBUS_SH_WHOAMI-whoami}`"
HOST="`${bindir}/globus-hostname`"
SERVICE=
SERVICE_HOST=
INTERACTIVE=
NO_DES=
TARGET_DIR=
CERT_FILE=
KEY_FILE=
REQUEST_FILE=

CERTREQ="${bindir}/grid-cert-request"

# Parse the command line

read_command_line "$@"

# set temporary files
REQ_HEAD=${TARGET_DIR}/$PROGRAM_NAME.$$.head
REQ_INPUT=${TARGET_DIR}/$PROGRAM_NAME.$$.input
REQ_OUTPUT=${TARGET_DIR}/$PROGRAM_NAME.$$.output
RAND_TEMP=${TARGET_DIR}/$PROGRAM_NAME.$$.random

# Source the security.conf file
. ${security_conf}

# Check the target directory
setup_target_dir

# Check for preexisting credential files
check4certs

# Derive the Common Name
if test "${SERVICE}" = "user"; then
    COMMON_NAME="`get_user_CN`"
else
    COMMON_NAME="`get_host_CN`"
fi

# do clean up on abort

trap abort_cleanup 1 2 3 6 9 13 15

# create the certificate request and private key
create_certs

RET=$?

COMMON_NAME="`echo ${SUBJECT} | ${GLOBUS_SH_SED-sed} -e 's|^.*/CN=||'`"

# print directions to screen
emit_directions ${SERVICE}

exit ${RET}
