#!/bin/bash
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

########################################################################
#
# mozilla/security/nss/tests/cert/chains.sh
#
# Script to test certificate chains validity. 
#
# needs to work on all Unix and Windows platforms
#
# special strings
# ---------------
#   FIXME ... known problems, search for this string
#   NOTE .... unexpected behavior
########################################################################

########################### is_httpserv_alive ##########################
# local shell function to exit with a fatal error if selfserver is not
# running
########################################################################
is_httpserv_alive()
{
  if [ ! -f "${HTTPPID}" ]; then
      echo "$SCRIPTNAME: Error - httpserv PID file ${HTTPPID} doesn't exist"
      sleep 5
      if [ ! -f "${HTTPPID}" ]; then
          Exit 9 "Fatal - httpserv pid file ${HTTPPID} does not exist"
      fi
  fi
  
  if [ "${OS_ARCH}" = "WINNT" ] && \
     [ "$OS_NAME" = "CYGWIN_NT" -o "$OS_NAME" = "MINGW32_NT" ]; then
      PID=${SHELL_HTTPPID}
  else
      PID=`cat ${HTTPPID}`
  fi

  echo "kill -0 ${PID} >/dev/null 2>/dev/null" 
  kill -0 ${PID} >/dev/null 2>/dev/null || Exit 10 "Fatal - httpserv process not detectable"

  echo "httpserv with PID ${PID} found at `date`"
}

########################### wait_for_httpserv ##########################
# local shell function to wait until httpserver is running and initialized
########################################################################
wait_for_httpserv()
{
  echo "trying to connect to httpserv at `date`"
  echo "tstclnt -p ${NSS_AIA_PORT} -h ${HOSTADDR} -q -v"
  ${BINDIR}/tstclnt -p ${NSS_AIA_PORT} -h ${HOSTADDR} -q -v
  if [ $? -ne 0 ]; then
      sleep 5
      echo "retrying to connect to httpserv at `date`"
      echo "tstclnt -p ${NSS_AIA_PORT} -h ${HOSTADDR} -q -v"
      ${BINDIR}/tstclnt -p ${NSS_AIA_PORT} -h ${HOSTADDR} -q -v
      if [ $? -ne 0 ]; then
          html_failed "Waiting for Server"
      fi
  fi
  is_httpserv_alive
}

########################### kill_httpserv ##############################
# local shell function to kill the httpserver after the tests are done
########################################################################
kill_httpserv()
{
  if [ "${OS_ARCH}" = "WINNT" ] && \
     [ "$OS_NAME" = "CYGWIN_NT" -o "$OS_NAME" = "MINGW32_NT" ]; then
      PID=${SHELL_HTTPPID}
  else
      PID=`cat ${HTTPPID}`
  fi

  echo "trying to kill httpserv with PID ${PID} at `date`"

  if [ "${OS_ARCH}" = "WINNT" -o "${OS_ARCH}" = "WIN95" -o "${OS_ARCH}" = "OS2" ]; then
      echo "${KILL} ${PID}"
      ${KILL} ${PID}
  else
      echo "${KILL} -USR1 ${PID}"
      ${KILL} -USR1 ${PID}
  fi
  wait ${PID}

  # On Linux httpserv needs up to 30 seconds to fully die and free
  # the port.  Wait until the port is free. (Bug 129701)
  if [ "${OS_ARCH}" = "Linux" ]; then
      echo "httpserv -b -p ${NSS_AIA_PORT} 2>/dev/null;"
      until ${BINDIR}/httpserv -b -p ${NSS_AIA_PORT} 2>/dev/null; do
          echo "RETRY: httpserv -b -p ${NSS_AIA_PORT} 2>/dev/null;"
          sleep 1
      done
  fi

  echo "httpserv with PID ${PID} killed at `date`"

  rm ${HTTPPID}
  html_detect_core "kill_httpserv core detection step"
}

########################### start_httpserv #############################
# local shell function to start the httpserver with the parameters required 
# for this test and log information (parameters, start time)
# also: wait until the server is up and running
########################################################################
start_httpserv()
{
  HTTP_METHOD=$1

  if [ -n "$testname" ] ; then
      echo "$SCRIPTNAME: $testname ----"
  fi
  echo "httpserv starting at `date`"
  ODDIR="${HOSTDIR}/chains/OCSPD"
  echo "httpserv -D -p ${NSS_AIA_PORT} ${SERVER_OPTIONS} \\"
  echo "         -A OCSPRoot -C ${ODDIR}/OCSPRoot.crl -A OCSPCA1 -C ${ODDIR}/OCSPCA1.crl \\"
  echo "         -A OCSPCA2  -C ${ODDIR}/OCSPCA2.crl  -A OCSPCA3 -C ${ODDIR}/OCSPCA3.crl \\"
  echo "         -O ${HTTP_METHOD} -d ${ODDIR}/ServerDB/ -f ${ODDIR}/ServerDB/dbpasswd \\"
  echo "         -i ${HTTPPID} $verbose &"
  ${PROFTOOL} ${BINDIR}/httpserv -D -p ${NSS_AIA_PORT} ${SERVER_OPTIONS} \
                 -A OCSPRoot -C ${ODDIR}/OCSPRoot.crl -A OCSPCA1 -C ${ODDIR}/OCSPCA1.crl \
                 -A OCSPCA2  -C ${ODDIR}/OCSPCA2.crl  -A OCSPCA3 -C ${ODDIR}/OCSPCA3.crl \
                 -O ${HTTP_METHOD} -d ${ODDIR}/ServerDB/ -f ${ODDIR}/ServerDB/dbpasswd \
                 -i ${HTTPPID} $verbose &
  RET=$?

  # The PID $! returned by the MKS or Cygwin shell is not the PID of
  # the real background process, but rather the PID of a helper
  # process (sh.exe).  MKS's kill command has a bug: invoking kill
  # on the helper process does not terminate the real background
  # process.  Our workaround has been to have httpserv save its PID
  # in the ${HTTPPID} file and "kill" that PID instead.  But this
  # doesn't work under Cygwin; its kill command doesn't recognize
  # the PID of the real background process, but it does work on the
  # PID of the helper process.  So we save the value of $! in the
  # SHELL_HTTPPID variable, and use it instead of the ${HTTPPID}
  # file under Cygwin.  (In fact, this should work in any shell
  # other than the MKS shell.)
  SHELL_HTTPPID=$!
  wait_for_httpserv

  if [ "${OS_ARCH}" = "WINNT" ] && \
     [ "$OS_NAME" = "CYGWIN_NT" -o "$OS_NAME" = "MINGW32_NT" ]; then
      PID=${SHELL_HTTPPID}
  else
      PID=`cat ${HTTPPID}`
  fi

  echo "httpserv with PID ${PID} started at `date`"
}

############################# chains_init ##############################
# local shell function to initialize this script
########################################################################
chains_init()
{
    if [ -z "${CLEANUP}" ] ; then   # if nobody else is responsible for
        CLEANUP="${SCRIPTNAME}"     # cleaning this script will do it
    fi
    if [ -z "${INIT_SOURCED}" ] ; then
        cd ../common
        . ./init.sh
    fi

    SCRIPTNAME="chains.sh"

    CHAINS_DIR="${HOSTDIR}/chains"
    mkdir -p ${CHAINS_DIR}
    cd ${CHAINS_DIR}

    CHAINS_SCENARIOS="${QADIR}/chains/scenarios/scenarios"

    CERT_SN_CNT=$(date '+%m%d%H%M%S' | sed "s/^0*//")
    CERT_SN_FIX=$(expr ${CERT_SN_CNT} - 1000)

    PK7_NONCE=${CERT_SN_CNT}
    SCEN_CNT=${CERT_SN_CNT}

    AIA_FILES="${HOSTDIR}/aiafiles"

    CU_DATA=${HOSTDIR}/cu_data
    CRL_DATA=${HOSTDIR}/crl_data

    DEFAULT_AIA_BASE_PORT=$(expr ${PORT:-8631} + 10)
    NSS_AIA_PORT=${NSS_AIA_PORT:-$DEFAULT_AIA_BASE_PORT}
    DEFAULT_UNUSED_PORT=$(expr ${PORT:-8631} + 11)
    NSS_UNUSED_PORT=${NSS_UNUSED_PORT:-$DEFAULT_UNUSED_PORT}
    NSS_AIA_HTTP=${NSS_AIA_HTTP:-"http://${HOSTADDR}:${NSS_AIA_PORT}"}
    NSS_AIA_PATH=${NSS_AIA_PATH:-$HOSTDIR/aiahttp}
    NSS_AIA_OCSP=${NSS_AIA_OCSP:-$NSS_AIA_HTTP/ocsp}
    NSS_OCSP_UNUSED=${NSS_AIA_OCSP_UNUSED:-"http://${HOSTADDR}:${NSS_UNUSED_PORT}"}

    html_head "Certificate Chains Tests"
}

chains_run_httpserv()
{
    HTTP_METHOD=$1

    if [ -n "${NSS_AIA_PATH}" ]; then
        HTTPPID=${NSS_AIA_PATH}/http_pid.$$
        mkdir -p "${NSS_AIA_PATH}"
        SAVEPWD=`pwd`
        cd "${NSS_AIA_PATH}"
        # Start_httpserv sets environment variables, which are required for
        # correct cleanup. (Running it in a subshell doesn't work, the
        # value of $SHELL_HTTPPID wouldn't arrive in this scope.)
        start_httpserv ${HTTP_METHOD}
        cd "${SAVEPWD}"
    fi
}

chains_stop_httpserv()
{
    if [ -n "${NSS_AIA_PATH}" ]; then
        kill_httpserv
    fi
}

############################ chains_cleanup ############################
# local shell function to finish this script (no exit since it might be
# sourced)
########################################################################
chains_cleanup()
{
    html "</TABLE><BR>"
    cd ${QADIR}
    . common/cleanup.sh
}

############################ print_cu_data #############################
# local shell function to print certutil input data
########################################################################
print_cu_data()
{
    echo "=== Certutil input data ==="
    cat ${CU_DATA}
    echo "==="
}

set_cert_sn()
{
    if [ -z "${SERIAL}" ]; then
        CERT_SN_CNT=$(expr ${CERT_SN_CNT} + 1)
        CERT_SN=${CERT_SN_CNT}
    else
        echo ${SERIAL} | cut -b 1 | grep '+' > /dev/null
        if [ $? -eq 0 ]; then
            CERT_SN=$(echo ${SERIAL} | cut -b 2-)
            CERT_SN=$(expr ${CERT_SN_FIX} + ${CERT_SN})
        else
            CERT_SN=${SERIAL}
        fi 
    fi
}

############################# create_db ################################
# local shell function to create certificate database
########################################################################
create_db()
{
    DB=$1

    [ -d "${DB}" ] && rm -rf ${DB}
    mkdir -p ${DB}

    echo "${DB}passwd" > ${DB}/dbpasswd

    TESTNAME="Creating DB ${DB}"
    echo "${SCRIPTNAME}: ${TESTNAME}"
    echo "certutil -N -d ${DB} -f ${DB}/dbpasswd" 
    ${BINDIR}/certutil -N -d ${DB} -f ${DB}/dbpasswd
    html_msg $? 0 "${SCENARIO}${TESTNAME}" 
}

########################### create_root_ca #############################
# local shell function to generate self-signed root certificate
########################################################################
create_root_ca()
{
    ENTITY=$1
    ENTITY_DB=${ENTITY}DB

    set_cert_sn
    date >> ${NOISE_FILE} 2>&1

    CTYPE_OPT=
    if [ -n "${CTYPE}" ]; then
        CTYPE_OPT="-k ${CTYPE}"
    fi

    echo "5
6
9
n
y
-1
n
5
6
7
9
n
" > ${CU_DATA}

    TESTNAME="Creating Root CA ${ENTITY}"
    echo "${SCRIPTNAME}: ${TESTNAME}"
    echo "certutil -s \"CN=${ENTITY} ROOT CA, O=${ENTITY}, C=US\" -S -n ${ENTITY} ${CTYPE_OPT} -t CTu,CTu,CTu -v 600 -x -d ${ENTITY_DB} -1 -2 -5 -f ${ENTITY_DB}/dbpasswd -z ${NOISE_FILE} -m ${CERT_SN} < ${CU_DATA}"
    print_cu_data
    ${BINDIR}/certutil -s "CN=${ENTITY} ROOT CA, O=${ENTITY}, C=US" -S -n ${ENTITY} ${CTYPE_OPT} -t CTu,CTu,CTu -v 600 -x -d ${ENTITY_DB} -1 -2 -5 -f ${ENTITY_DB}/dbpasswd -z ${NOISE_FILE} -m ${CERT_SN} < ${CU_DATA}
    html_msg $? 0 "${SCENARIO}${TESTNAME}"

    TESTNAME="Exporting Root CA ${ENTITY}.der"
    echo "${SCRIPTNAME}: ${TESTNAME}"
    echo "certutil -L -d ${ENTITY_DB} -r -n ${ENTITY} -o ${ENTITY}.der"
    ${BINDIR}/certutil -L -d ${ENTITY_DB} -r -n ${ENTITY} -o ${ENTITY}.der
    html_msg $? 0 "${SCENARIO}${TESTNAME}"
}

########################### create_cert_req ############################
# local shell function to generate certificate sign request
########################################################################
create_cert_req()
{
    ENTITY=$1
    TYPE=$2

    ENTITY_DB=${ENTITY}DB

    REQ=${ENTITY}Req.der

    date >> ${NOISE_FILE} 2>&1

    CTYPE_OPT=
    if [ -n "${CTYPE}" ]; then
        CTYPE_OPT="-k ${CTYPE}"
    fi

    CA_FLAG=
    EXT_DATA=
    OPTIONS=

    if [ "${TYPE}" != "EE" ]; then
        CA_FLAG="-2"
        EXT_DATA="y
-1
y
"
    fi

    process_crldp

    echo "${EXT_DATA}" > ${CU_DATA}

    TESTNAME="Creating ${TYPE} certifiate request ${REQ}"
    echo "${SCRIPTNAME}: ${TESTNAME}"
    echo "certutil -s \"CN=${ENTITY} ${TYPE}, O=${ENTITY}, C=US\" ${CTYPE_OPT} -R ${CA_FLAG} -d ${ENTITY_DB} -f ${ENTITY_DB}/dbpasswd -z ${NOISE_FILE} -o ${REQ} ${OPTIONS} < ${CU_DATA}"
    print_cu_data
    ${BINDIR}/certutil -s "CN=${ENTITY} ${TYPE}, O=${ENTITY}, C=US" ${CTYPE_OPT} -R ${CA_FLAG} -d ${ENTITY_DB} -f ${ENTITY_DB}/dbpasswd -z ${NOISE_FILE} -o ${REQ} ${OPTIONS} < ${CU_DATA} 
    html_msg $? 0 "${SCENARIO}${TESTNAME}"
}

############################ create_entity #############################
# local shell function to create certificate chain entity
########################################################################
create_entity()
{
    ENTITY=$1
    TYPE=$2

    if [ -z "${ENTITY}" ]; then
        echo "Configuration error: Unnamed entity"
        exit 1
    fi

    DB=${ENTITY}DB
    ENTITY_DB=${ENTITY}DB

    case "${TYPE}" in
    "Root")
        create_db "${DB}"
        create_root_ca "${ENTITY}"
        ;;
    "Intermediate" | "Bridge" | "EE")
        create_db "${DB}"
        create_cert_req "${ENTITY}" "${TYPE}"
        ;;
    "*")
        echo "Configuration error: Unknown type ${TYPE}"
        exit 1
        ;;
    esac
}

########################################################################
# List of global variables related to certificate extensions processing:
#
# Generated by process_extensions and functions called from it:
# OPTIONS - list of command line policy extensions 
# DATA - list of inpud data related to policy extensions
#
# Generated by parse_config:
# POLICY - list of certificate policies
# MAPPING - list of policy mappings 
# INHIBIT - inhibit flag
# AIA - AIA list
########################################################################

############################ process_policy ############################
# local shell function to process policy extension parameters and 
# generate input for certutil
########################################################################
process_policy()
{
    if [ -n "${POLICY}" ]; then
        OPTIONS="${OPTIONS} --extCP"

        NEXT=
        for ITEM in ${POLICY}; do
            if [ -n "${NEXT}" ]; then
                DATA="${DATA}y
"
            fi

            NEXT=1
            DATA="${DATA}${ITEM}
1

n
"
        done

        DATA="${DATA}n
n
"
    fi
}

########################### process_mapping ############################
# local shell function to process policy mapping parameters and 
# generate input for certutil
########################################################################
process_mapping()
{
    if [ -n "${MAPPING}" ]; then
        OPTIONS="${OPTIONS} --extPM"

        NEXT=
        for ITEM in ${MAPPING}; do
            if [ -n "${NEXT}" ]; then
                DATA="${DATA}y
"
            fi

            NEXT=1
            IDP=`echo ${ITEM} | cut -d: -f1`
            SDP=`echo ${ITEM} | cut -d: -f2`
            DATA="${DATA}${IDP}
${SDP}
"
        done

        DATA="${DATA}n
n
"
    fi
}

########################### process_inhibit#############################
# local shell function to process inhibit extension and generate input 
# for certutil
########################################################################
process_inhibit()
{
    if [ -n "${INHIBIT}" ]; then
        OPTIONS="${OPTIONS} --extIA"

        DATA="${DATA}${INHIBIT}
n
"
    fi
}

############################# process_aia ##############################
# local shell function to process AIA extension parameters and 
# generate input for certutil
########################################################################
process_aia()
{
    if [ -n "${AIA}" ]; then
        OPTIONS="${OPTIONS} --extAIA"

        DATA="${DATA}1
"

        for ITEM in ${AIA}; do
            PK7_NONCE=`expr $PK7_NONCE + 1`

            echo ${ITEM} | grep ":" > /dev/null
            if [ $? -eq 0 ]; then
                CERT_NICK=`echo ${ITEM} | cut -d: -f1`
                CERT_ISSUER=`echo ${ITEM} | cut -d: -f2`
                CERT_LOCAL="${CERT_NICK}${CERT_ISSUER}.der"
                CERT_PUBLIC="${HOST}-$$-${CERT_NICK}${CERT_ISSUER}-${PK7_NONCE}.der"
            else
                CERT_LOCAL="${ITEM}.p7"
                CERT_PUBLIC="${HOST}-$$-${ITEM}-${PK7_NONCE}.p7"
            fi

            DATA="${DATA}7
${NSS_AIA_HTTP}/${CERT_PUBLIC}
"

            if [ -n "${NSS_AIA_PATH}" ]; then
                cp ${CERT_LOCAL} ${NSS_AIA_PATH}/${CERT_PUBLIC} 2> /dev/null
                chmod a+r ${NSS_AIA_PATH}/${CERT_PUBLIC}
                echo ${NSS_AIA_PATH}/${CERT_PUBLIC} >> ${AIA_FILES}
            fi
        done

        DATA="${DATA}0
n
n"
    fi
}

process_ocsp()
{
    if [ -n "${OCSP}" ]; then
        OPTIONS="${OPTIONS} --extAIA"
 
	if [ "${OCSP}" = "offline" ]; then
	    MY_OCSP_URL=${NSS_OCSP_UNUSED}
	else
	    MY_OCSP_URL=${NSS_AIA_OCSP}
	fi

        DATA="${DATA}2
7
${MY_OCSP_URL}
0
n
n
"
    fi
}

process_crldp()
{
    if [ -n "${CRLDP}" ]; then
        OPTIONS="${OPTIONS} -4"

        EXT_DATA="${EXT_DATA}1
"

        for ITEM in ${CRLDP}; do
            CRL_PUBLIC="${HOST}-$$-${ITEM}-${SCEN_CNT}.crl"

            EXT_DATA="${EXT_DATA}7
${NSS_AIA_HTTP}/${CRL_PUBLIC}
"
        done

        EXT_DATA="${EXT_DATA}-1
-1
-1
n
n
"
    fi
}

process_ku_ns_eku()
{
    if [ -n "${EXT_KU}" ]; then
        OPTIONS="${OPTIONS} --keyUsage ${EXT_KU}"
    fi
    if [ -n "${EXT_NS}" ]; then
        EXT_NS_KEY=$(echo ${EXT_NS} | cut -d: -f1)
        EXT_NS_CODE=$(echo ${EXT_NS} | cut -d: -f2)

        OPTIONS="${OPTIONS} --nsCertType ${EXT_NS_KEY}"
        DATA="${DATA}${EXT_NS_CODE}
-1
n
"
    fi
    if [ -n "${EXT_EKU}" ]; then
        OPTIONS="${OPTIONS} --extKeyUsage ${EXT_EKU}"
    fi
}

copy_crl()

{
    if [ -z "${NSS_AIA_PATH}" ]; then
        return;
    fi

    CRL_LOCAL="${COPYCRL}.crl"
    CRL_PUBLIC="${HOST}-$$-${COPYCRL}-${SCEN_CNT}.crl"

    cp ${CRL_LOCAL} ${NSS_AIA_PATH}/${CRL_PUBLIC} 2> /dev/null
    chmod a+r ${NSS_AIA_PATH}/${CRL_PUBLIC}
    echo ${NSS_AIA_PATH}/${CRL_PUBLIC} >> ${AIA_FILES}
}

########################## process_extension ###########################
# local shell function to process entity extension parameters and 
# generate input for certutil
########################################################################
process_extensions()
{
    OPTIONS=
    DATA=

    process_policy
    process_mapping
    process_inhibit
    process_aia
    process_ocsp
    process_ku_ns_eku
}

############################## sign_cert ###############################
# local shell function to sign certificate sign reuqest
########################################################################
sign_cert()
{
    ENTITY=$1
    ISSUER=$2
    TYPE=$3

    [ -z "${ISSUER}" ] && return

    ENTITY_DB=${ENTITY}DB
    ISSUER_DB=${ISSUER}DB
    REQ=${ENTITY}Req.der
    CERT=${ENTITY}${ISSUER}.der

    set_cert_sn

    EMAIL_OPT=
    if [ "${TYPE}" = "Bridge" ]; then
        EMAIL_OPT="-7 ${ENTITY}@${ISSUER}"

        [ -n "${EMAILS}" ] && EMAILS="${EMAILS},"
        EMAILS="${EMAILS}${ENTITY}@${ISSUER}"
    fi

    process_extensions 

    echo "${DATA}" > ${CU_DATA}

    TESTNAME="Creating certficate ${CERT} signed by ${ISSUER}"
    echo "${SCRIPTNAME}: ${TESTNAME}"
    echo "certutil -C -c ${ISSUER} -v 60 -d ${ISSUER_DB} -i ${REQ} -o ${CERT} -f ${ISSUER_DB}/dbpasswd -m ${CERT_SN} ${EMAIL_OPT} ${OPTIONS} < ${CU_DATA}"
    print_cu_data
    ${BINDIR}/certutil -C -c ${ISSUER} -v 60 -d ${ISSUER_DB} -i ${REQ} -o ${CERT} -f ${ISSUER_DB}/dbpasswd -m ${CERT_SN} ${EMAIL_OPT} ${OPTIONS} < ${CU_DATA}
    html_msg $? 0 "${SCENARIO}${TESTNAME}"

    TESTNAME="Importing certificate ${CERT} to ${ENTITY_DB} database"
    echo "${SCRIPTNAME}: ${TESTNAME}"
    echo "certutil -A -n ${ENTITY} -t u,u,u -d ${ENTITY_DB} -f ${ENTITY_DB}/dbpasswd -i ${CERT}"
    ${BINDIR}/certutil -A -n ${ENTITY} -t u,u,u -d ${ENTITY_DB} -f ${ENTITY_DB}/dbpasswd -i ${CERT}
    html_msg $? 0 "${SCENARIO}${TESTNAME}"
}

############################# create_pkcs7##############################
# local shell function to package bridge certificates into pkcs7 
# package
########################################################################
create_pkcs7()
{
    ENTITY=$1
    ENTITY_DB=${ENTITY}DB

    TESTNAME="Generating PKCS7 package from ${ENTITY_DB} database"
    echo "${SCRIPTNAME}: ${TESTNAME}"
    echo "cmsutil -O -r \"${EMAILS}\" -d ${ENTITY_DB} > ${ENTITY}.p7"
    ${BINDIR}/cmsutil -O -r "${EMAILS}" -d ${ENTITY_DB} > ${ENTITY}.p7
    html_msg $? 0 "${SCENARIO}${TESTNAME}"
}

############################# import_key ###############################
# local shell function to import private key + cert into database
########################################################################
import_key()
{
    KEY_NAME=$1.p12
    DB=$2

    KEY_FILE=../OCSPD/${KEY_NAME}

    TESTNAME="Importing p12 key ${KEY_NAME} to ${DB} database"
    echo "${SCRIPTNAME}: ${TESTNAME}"
    echo "${BINDIR}/pk12util -d ${DB} -i ${KEY_FILE} -k ${DB}/dbpasswd -W nssnss"
    ${BINDIR}/pk12util -d ${DB} -i ${KEY_FILE} -k ${DB}/dbpasswd -W nssnss
    html_msg $? 0 "${SCENARIO}${TESTNAME}"
}

export_key()
{
    KEY_NAME=$1.p12
    DB=$2

    TESTNAME="Exporting $1 as ${KEY_NAME} from ${DB} database"
    echo "${SCRIPTNAME}: ${TESTNAME}"
    echo "${BINDIR}/pk12util -d ${DB} -o ${KEY_NAME} -n $1 -k ${DB}/dbpasswd -W nssnss"
    ${BINDIR}/pk12util -d ${DB} -o ${KEY_NAME} -n $1 -k ${DB}/dbpasswd -W nssnss
    html_msg $? 0 "${SCENARIO}${TESTNAME}"
}

############################# import_cert ##############################
# local shell function to import certificate into database
########################################################################
import_cert()
{
    IMPORT=$1
    DB=$2

    CERT_NICK=`echo ${IMPORT} | cut -d: -f1`
    CERT_ISSUER=`echo ${IMPORT} | cut -d: -f2`
    CERT_TRUST=`echo ${IMPORT} | cut -d: -f3`

    if [ "${CERT_ISSUER}" = "x" ]; then
        CERT_ISSUER=
        CERT=${CERT_NICK}.cert
        CERT_FILE="${QADIR}/libpkix/certs/${CERT}"
    elif [ "${CERT_ISSUER}" = "d" ]; then
        CERT_ISSUER=
        CERT=${CERT_NICK}.der
        CERT_FILE="../OCSPD/${CERT}"
    else
        CERT=${CERT_NICK}${CERT_ISSUER}.der
        CERT_FILE=${CERT}
    fi

    IS_ASCII=`grep -c -- "-----BEGIN CERTIFICATE-----" ${CERT_FILE}`

    ASCII_OPT=
    if [ "${IS_ASCII}" -gt 0 ]; then
        ASCII_OPT="-a"
    fi
   
    TESTNAME="Importing certificate ${CERT} to ${DB} database"
    echo "${SCRIPTNAME}: ${TESTNAME}"
    echo "certutil -A -n ${CERT_NICK} ${ASCII_OPT} -t \"${CERT_TRUST}\" -d ${DB} -f ${DB}/dbpasswd -i ${CERT_FILE}"
    ${BINDIR}/certutil -A -n ${CERT_NICK} ${ASCII_OPT} -t "${CERT_TRUST}" -d ${DB} -f ${DB}/dbpasswd -i ${CERT_FILE} 
    html_msg $? 0 "${SCENARIO}${TESTNAME}"
}

import_crl()
{
    IMPORT=$1
    DB=$2

    CRL_NICK=`echo ${IMPORT} | cut -d: -f1`
    CRL_FILE=${CRL_NICK}.crl

    if [ ! -f "${CRL_FILE}" ]; then
        return
    fi 

    TESTNAME="Importing CRL ${CRL_FILE} to ${DB} database"
    echo "${SCRIPTNAME}: ${TESTNAME}"
    echo "crlutil -I -d ${DB} -f ${DB}/dbpasswd -i ${CRL_FILE}"
    ${BINDIR}/crlutil -I -d ${DB} -f ${DB}/dbpasswd -i ${CRL_FILE} 
    html_msg $? 0 "${SCENARIO}${TESTNAME}"
}

create_crl()
{
    ISSUER=$1
    ISSUER_DB=${ISSUER}DB

    CRL=${ISSUER}.crl

    DATE=$(date -u '+%Y%m%d%H%M%SZ')
    DATE_LAST="${DATE}"

    UPDATE=$(expr $(date -u '+%Y') + 1)$(date -u '+%m%d%H%M%SZ')

    echo "update=${DATE}" > ${CRL_DATA}
    echo "nextupdate=${UPDATE}" >> ${CRL_DATA}

    TESTNAME="Create CRL for ${ISSUER_DB}"
    echo "${SCRIPTNAME}: ${TESTNAME}"
    echo "crlutil -G -d ${ISSUER_DB} -n ${ISSUER} -f ${ISSUER_DB}/dbpasswd -o ${CRL}"
    echo "=== Crlutil input data ==="
    cat ${CRL_DATA}
    echo "==="
    ${BINDIR}/crlutil -G -d ${ISSUER_DB} -n ${ISSUER} -f ${ISSUER_DB}/dbpasswd -o ${CRL} < ${CRL_DATA}
    html_msg $? 0 "${SCENARIO}${TESTNAME}"
}

revoke_cert()
{
    ISSUER=$1
    ISSUER_DB=${ISSUER}DB

    CRL=${ISSUER}.crl

    set_cert_sn

    DATE=$(date -u '+%Y%m%d%H%M%SZ')
    while [ "${DATE}" = "${DATE_LAST}" ]; do
        sleep 1
        DATE=$(date -u '+%Y%m%d%H%M%SZ')
    done
    DATE_LAST="${DATE}"

    echo "update=${DATE}" > ${CRL_DATA}
    echo "addcert ${CERT_SN} ${DATE}" >> ${CRL_DATA}

    TESTNAME="Revoking certificate with SN ${CERT_SN} issued by ${ISSUER}"
    echo "${SCRIPTNAME}: ${TESTNAME}"
    echo "crlutil -M -d ${ISSUER_DB} -n ${ISSUER} -f ${ISSUER_DB}/dbpasswd -o ${CRL}"
    echo "=== Crlutil input data ==="
    cat ${CRL_DATA}
    echo "==="
    ${BINDIR}/crlutil -M -d ${ISSUER_DB} -n ${ISSUER} -f ${ISSUER_DB}/dbpasswd -o ${CRL} < ${CRL_DATA}
    html_msg $? 0 "${SCENARIO}${TESTNAME}"
}

########################################################################
# List of global variables related to certificate verification:
#
# Generated by parse_config:
# DB - DB used for testing
# FETCH - fetch flag (used with AIA extension)
# POLICY - list of policies
# TRUST - trust anchor
# TRUST_AND_DB - Examine both trust anchors and the cert db for trust
# VERIFY - list of certificates to use as vfychain parameters
# EXP_RESULT - expected result
# REV_OPTS - revocation options
########################################################################

############################# verify_cert ##############################
# local shell function to verify certificate validity
########################################################################
verify_cert()
{
    ENGINE=$1

    DB_OPT=
    FETCH_OPT=
    POLICY_OPT=
    TRUST_OPT=
    VFY_CERTS=
    VFY_LIST=
    TRUST_AND_DB_OPT=

    if [ -n "${DB}" ]; then
        DB_OPT="-d ${DB}"
    fi

    if [ -n "${FETCH}" ]; then
        FETCH_OPT="-f"
        if [ -z "${NSS_AIA_HTTP}" ]; then
            echo "${SCRIPTNAME} Skipping test using AIA fetching, NSS_AIA_HTTP not defined"
            return
        fi
    fi

    if [ -n "${TRUST_AND_DB}" ]; then
        TRUST_AND_DB_OPT="-T"
    fi

    for ITEM in ${POLICY}; do
        POLICY_OPT="${POLICY_OPT} -o ${ITEM}"
    done

    for ITEM in ${TRUST}; do
        echo ${ITEM} | grep ":" > /dev/null
        if [ $? -eq 0 ]; then
            CERT_NICK=`echo ${ITEM} | cut -d: -f1`
            CERT_ISSUER=`echo ${ITEM} | cut -d: -f2`
            CERT=${CERT_NICK}${CERT_ISSUER}.der

            TRUST_OPT="${TRUST_OPT} -t ${CERT}"
        else
            TRUST_OPT="${TRUST_OPT} -t ${ITEM}"
        fi
    done

    for ITEM in ${VERIFY}; do
        CERT_NICK=`echo ${ITEM} | cut -d: -f1`
        CERT_ISSUER=`echo ${ITEM} | cut -d: -f2`

        if [ "${CERT_ISSUER}" = "x" ]; then
            CERT="${QADIR}/libpkix/certs/${CERT_NICK}.cert"
            VFY_CERTS="${VFY_CERTS} ${CERT}"
            VFY_LIST="${VFY_LIST} ${CERT_NICK}.cert"
        elif [ "${CERT_ISSUER}" = "d" ]; then
            CERT="../OCSPD/${CERT_NICK}.der"
            VFY_CERTS="${VFY_CERTS} ${CERT}"
            VFY_LIST="${VFY_LIST} ${CERT_NICK}.cert"
        else
            CERT=${CERT_NICK}${CERT_ISSUER}.der
            VFY_CERTS="${VFY_CERTS} ${CERT}"
            VFY_LIST="${VFY_LIST} ${CERT}"
        fi
    done

    VFY_OPTS_TNAME="${DB_OPT} ${ENGINE} ${TRUST_AND_DB_OPT} ${REV_OPTS} ${FETCH_OPT} ${USAGE_OPT} ${POLICY_OPT} ${TRUST_OPT}"
    VFY_OPTS_ALL="${DB_OPT} ${ENGINE} -vv ${TRUST_AND_DB_OPT} ${REV_OPTS} ${FETCH_OPT} ${USAGE_OPT} ${POLICY_OPT} ${VFY_CERTS} ${TRUST_OPT}"

    TESTNAME="Verifying certificate(s) ${VFY_LIST} with flags ${VFY_OPTS_TNAME}"
    echo "${SCRIPTNAME}: ${TESTNAME}"
    echo "vfychain ${VFY_OPTS_ALL}"

    if [ -z "${MEMLEAK_DBG}" ]; then
        VFY_OUT=$(${BINDIR}/vfychain ${VFY_OPTS_ALL} 2>&1)
        RESULT=$?
        echo "${VFY_OUT}"
    else 
        VFY_OUT=$(${RUN_COMMAND_DBG} ${BINDIR}/vfychain ${VFY_OPTS_ALL} 2>> ${LOGFILE})
        RESULT=$?
        echo "${VFY_OUT}"
    fi

    echo "${VFY_OUT}" | grep "ERROR -5990: I/O operation timed out" > /dev/null
    E5990=$?
    echo "${VFY_OUT}" | grep "ERROR -8030: Server returned bad HTTP response" > /dev/null
    E8030=$?

    if [ $E5990 -eq 0 -o $E8030 -eq 0 ]; then
        echo "Result of this test is not valid due to network time out."
        html_unknown "${SCENARIO}${TESTNAME}"
        return
    fi

    echo "Returned value is ${RESULT}, expected result is ${EXP_RESULT}"
    
    if [ "${EXP_RESULT}" = "pass" -a ${RESULT} -eq 0 ]; then
        html_passed "${SCENARIO}${TESTNAME}"
    elif [ "${EXP_RESULT}" = "fail" -a ${RESULT} -ne 0 ]; then
        html_passed "${SCENARIO}${TESTNAME}"
    else
        html_failed "${SCENARIO}${TESTNAME}"
    fi
}

check_ocsp()
{
    OCSP_CERT=$1

    CERT_NICK=`echo ${OCSP_CERT} | cut -d: -f1`
    CERT_ISSUER=`echo ${OCSP_CERT} | cut -d: -f2`

    if [ "${CERT_ISSUER}" = "x" ]; then
        CERT_ISSUER=
        CERT=${CERT_NICK}.cert
        CERT_FILE="${QADIR}/libpkix/certs/${CERT}"
    elif [ "${CERT_ISSUER}" = "d" ]; then
        CERT_ISSUER=
        CERT=${CERT_NICK}.der
        CERT_FILE="../OCSPD/${CERT}"
    else
        CERT=${CERT_NICK}${CERT_ISSUER}.der
        CERT_FILE=${CERT}
    fi

    # sample line:
    #   URI: "http://ocsp.server:2601"
    OCSP_HOST=$(${BINDIR}/pp -w -t certificate -i ${CERT_FILE} | grep URI | sed "s/.*:\/\///" | sed "s/:.*//")
    OCSP_PORT=$(${BINDIR}/pp -w -t certificate -i ${CERT_FILE} | grep URI | sed "s/^.*:.*:\/\/.*:\([0-9]*\).*$/\1/")

    echo "tstclnt -h ${OCSP_HOST} -p ${OCSP_PORT} -q -t 20"
    tstclnt -h ${OCSP_HOST} -p ${OCSP_PORT} -q -t 20
    return $?
}

############################ parse_result ##############################
# local shell function to process expected result value
# this function was created for case that expected result depends on
# some conditions - in our case type of cert DB
#
# default results are pass and fail
# this function added parsable values in format:
# type1:value1 type2:value2 .... typex:valuex
#
# allowed types are dbm, sql, all (all means all other cases)
# allowed values are pass and fail
#
# if this format is not used, EXP_RESULT will stay unchanged (this also
# covers pass and fail states)
########################################################################
parse_result()
{
    for RES in ${EXP_RESULT}
    do
        RESTYPE=$(echo ${RES} | cut -d: -f1)
        RESSTAT=$(echo ${RES} | cut -d: -f2)

        if [ "${RESTYPE}" = "${NSS_DEFAULT_DB_TYPE}" -o "${RESTYPE}" = "all" ]; then
            EXP_RESULT=${RESSTAT}
            break
        fi
    done
}

############################ parse_config ##############################
# local shell function to parse and process file containing certificate
# chain configuration and list of tests
########################################################################
parse_config()
{
    SCENARIO=
    LOGNAME=

    while read KEY VALUE
    do
        case "${KEY}" in
        "entity")
            ENTITY="${VALUE}"
            TYPE=
            ISSUER=
            CTYPE=
            POLICY=
            MAPPING=
            INHIBIT=
            AIA=
            CRLDP=
            OCSP=
            DB=
            EMAILS=
            EXT_KU=
            EXT_NS=
            EXT_EKU=
            SERIAL=
	    EXPORT_KEY=
            ;;
        "type")
            TYPE="${VALUE}"
            ;;
        "issuer")
            if [ -n "${ISSUER}" ]; then
                if [ -z "${DB}" ]; then
                    create_entity "${ENTITY}" "${TYPE}"
                fi
                sign_cert "${ENTITY}" "${ISSUER}" "${TYPE}"
            fi

            ISSUER="${VALUE}"
            POLICY=
            MAPPING=
            INHIBIT=
            AIA=
            EXT_KU=
            EXT_NS=
            EXT_EKU=
            ;;
        "ctype") 
            CTYPE="${VALUE}"
            ;;
        "policy")
            POLICY="${POLICY} ${VALUE}"
            ;;
        "mapping")
            MAPPING="${MAPPING} ${VALUE}"
            ;;
        "inhibit")
            INHIBIT="${VALUE}"
            ;;
        "aia")
            AIA="${AIA} ${VALUE}"
            ;;
        "crldp")
            CRLDP="${CRLDP} ${VALUE}"
            ;;
        "ocsp")
            OCSP="${VALUE}"
            ;;
        "db")
            DB="${VALUE}DB"
            create_db "${DB}"
            ;;
        "import")
            IMPORT="${VALUE}"
            import_cert "${IMPORT}" "${DB}"
            import_crl "${IMPORT}" "${DB}"
            ;;
        "import_key")
            IMPORT="${VALUE}"
            import_key "${IMPORT}" "${DB}"
            ;;
        "crl")
            ISSUER="${VALUE}"
            create_crl "${ISSUER}"
            ;;
        "revoke")
            REVOKE="${VALUE}"
            ;;
        "serial")
            SERIAL="${VALUE}"
            ;;
	"export_key")
	    EXPORT_KEY=1
	    ;;
        "copycrl")
            COPYCRL="${VALUE}"
            copy_crl "${COPYCRL}"
            ;;
        "verify")
            VERIFY="${VALUE}"
            TRUST=
            TRUST_AND_DB=
            POLICY=
            FETCH=
            EXP_RESULT=
            REV_OPTS=
            USAGE_OPT=
            ;;
        "cert")
            VERIFY="${VERIFY} ${VALUE}"
            ;;
        "testdb")
            if [ -n "${VALUE}" ]; then
                DB="${VALUE}DB"
            else
                DB=
            fi
            ;;
        "trust")
            TRUST="${TRUST} ${VALUE}"
            ;;
        "trust_and_db")
            TRUST_AND_DB=1
            ;;
        "fetch")
            FETCH=1
            ;;
        "result")
            EXP_RESULT="${VALUE}"
            parse_result
            ;;
        "rev_type")
            REV_OPTS="${REV_OPTS} -g ${VALUE}"
            ;;
        "rev_flags")
            REV_OPTS="${REV_OPTS} -h ${VALUE}"
            ;;
        "rev_mtype")
            REV_OPTS="${REV_OPTS} -m ${VALUE}"
            ;;
        "rev_mflags")
            REV_OPTS="${REV_OPTS} -s ${VALUE}"
            ;;
        "scenario")
            SCENARIO="${VALUE}: "

            CHAINS_DIR="${HOSTDIR}/chains/${VALUE}"
            mkdir -p ${CHAINS_DIR}
            cd ${CHAINS_DIR}

            if [ -n "${MEMLEAK_DBG}" ]; then
                LOGNAME="libpkix-${VALUE}"
                LOGFILE="${LOGDIR}/${LOGNAME}"
            fi

            SCEN_CNT=$(expr ${SCEN_CNT} + 1)
            ;;
        "sleep")
            sleep ${VALUE}
            ;;
        "break")
            break
            ;;
        "check_ocsp")
            TESTNAME="Test that OCSP server is reachable"
            check_ocsp ${VALUE}
            if [ $? -ne 0 ]; then
                html_failed "$TESTNAME"
                break;
            else
                html_passed "$TESTNAME"
            fi
            ;;
        "ku")
            EXT_KU="${VALUE}"
            ;;
        "ns")
            EXT_NS="${VALUE}"
            ;;
        "eku")
            EXT_EKU="${VALUE}"
            ;;
        "usage")
            USAGE_OPT="-u ${VALUE}"
            ;;
        "")
            if [ -n "${ENTITY}" ]; then
                if [ -z "${DB}" ]; then
                    create_entity "${ENTITY}" "${TYPE}"
                fi
                sign_cert "${ENTITY}" "${ISSUER}" "${TYPE}"
                if [ "${TYPE}" = "Bridge" ]; then
                    create_pkcs7 "${ENTITY}"
                fi
		if [ -n "${EXPORT_KEY}" ]; then
		    export_key "${ENTITY}" "${DB}"
		fi
                ENTITY=
            fi

            if [ -n "${VERIFY}" ]; then
                verify_cert "-pp"
		if [ -n "${VERIFY_CLASSIC_ENGINE_TOO}" ]; then
		    verify_cert ""
		    verify_cert "-p"
		fi
                VERIFY=
            fi

            if [ -n "${REVOKE}" ]; then
                revoke_cert "${REVOKE}" "${DB}"
                REVOKE=
            fi
            ;;
        *)
            if [ `echo ${KEY} | cut -b 1` != "#" ]; then
                echo "Configuration error: Unknown keyword ${KEY}"
                exit 1
            fi
            ;;
        esac
    done

    if [ -n "${MEMLEAK_DBG}" ]; then
        log_parse
        html_msg $? 0 "${SCENARIO}Memory leak checking" 
    fi
}

process_scenario()
{
    SCENARIO_FILE=$1

    > ${AIA_FILES}

    parse_config < "${QADIR}/chains/scenarios/${SCENARIO_FILE}"

    while read AIA_FILE
    do
	rm ${AIA_FILE} 2> /dev/null
    done < ${AIA_FILES}
    rm ${AIA_FILES}
}

# process ocspd.cfg separately
chains_ocspd()
{
    process_scenario "ocspd.cfg"
}

# process ocsp.cfg separately
chains_method()
{
    process_scenario "method.cfg"
}

############################# chains_main ##############################
# local shell function to process all testing scenarios
########################################################################
chains_main()
{
    while read LINE 
    do
        [ `echo ${LINE} | cut -b 1` != "#" ] || continue

	[ ${LINE} != 'ocspd.cfg' ] || continue
	[ ${LINE} != 'method.cfg' ] || continue

	process_scenario ${LINE}
    done < "${CHAINS_SCENARIOS}"
}

################################ main ##################################

chains_init
VERIFY_CLASSIC_ENGINE_TOO=
chains_ocspd
VERIFY_CLASSIC_ENGINE_TOO=1
chains_run_httpserv get
chains_method
chains_stop_httpserv
chains_run_httpserv post
chains_method
chains_stop_httpserv
VERIFY_CLASSIC_ENGINE_TOO=
chains_run_httpserv random
chains_main
chains_stop_httpserv
chains_run_httpserv get-unknown
chains_main
chains_stop_httpserv
chains_cleanup
