#!/bin/bash # Authors: # Daniel Kahn Gillmor # Thomas Klute set -e . ${srcdir}/common.bash netns_reexec ${@} testid="${1##t-}" if [ -z "$testid" ] ; then echo -e "No test case selected.\nUsage: ${0} t-N" >&2 exit 1 else testid=${srcdir}/tests/"$(printf "%02d" "$testid")"_* fi BADVARS=0 for v in APACHE2 TEST_HOST TEST_PORT TEST_QUERY_DELAY TEST_MSVA_WAIT \ MSVA_PORT; do if [ ! -v "$v" ]; then printf "You need to set the %s environment variable\n" "$v" >&2 BADVARS=1 fi done if [ 0 != "$BADVARS" ]; then exit 1 fi # write script file and line to stderr on error function pinpoint_error() { echo "${1} failed at line ${2}!" >&2 } trap 'pinpoint_error ${BASH_SOURCE} ${LINENO}' ERR function stop_msva() { kill_by_pidfile "${msva_pidfile}" unset msva_pidfile } # Compare expected/actual outputs, filtering out headers from actual # output that are expected to change between runs or builds (currently # "Date" and "Server"). The headers must be excluded in the expected # output. # # Parameters: # $1: path to expected output # $2: path to actual output # $3: additional options for diff (optional) function diff_output_filter_headers() { local expected="$1" local actual="$2" diff $3 -u "${expected}" <( cat "${actual}" | \ grep -v -P '^Date:\s.*GMT\s?$' | \ grep -v -P '^Server:\sApache' | \ tail -n "$(wc -l < ${expected})" ) } # Run a command, storing its PID in the given file # Usage: run_with_pidfile PIDFILE COMMAND [ARGS] function run_with_pidfile() { local pidfile=$1 local cmd=$2 shift 2 echo $BASHPID >${pidfile} exec ${cmd} $* } # Kills the process with the PID contained in a given file, then # deletes the file. # Usage: kill_by_pidfile PIDFILE function kill_by_pidfile() { local pidfile="${1}" # In some testcases with expected failure, gnutls-cli sometimes # failed before the subshell in front of the pipe (see gnutls-cli # call below) got so far as to write the PID, much less exec # sleep. So we need to check if there actually is anything to # kill. if [ -n "${pidfile}" ]; then local pid=$(cat "${pidfile}") if [ -n "${pid}" ] && ps -p "${pid}"; then kill "${pid}" else echo "No running process with PID ${pid} (${pidfile})." fi rm "${pidfile}" fi } function apache_down_err() { printf "FAILURE: %s\n" "$TEST_NAME" ${APACHE2} -f "${t}/apache.conf" -k stop || true if [ -e output ]; then printf "\ngnutls-cli outputs:\n" diff_output_filter_headers "output" "$output" || true fi if [ -n "${sleep_pidfile}" ]; then kill_by_pidfile "${sleep_pidfile}" fi local errlog="logs/${TEST_NAME}.error.log" if [ -r "${errlog}" ]; then printf "\nApache error logs:\n" tail "${errlog}" fi if [ -n "${USE_MSVA}" ]; then stop_msva fi } if [ -n "${USE_MSVA}" ]; then msva_pidfile="$(mktemp mod_gnutls_test-XXXXXX.pid)" GNUPGHOME=msva.gnupghome MSVA_KEYSERVER_POLICY=never run_with_pidfile "${msva_pidfile}" monkeysphere-validation-agent & trap stop_msva EXIT printf "TESTING: initial MSVA verification\n" # set to 0 if MSVA is up ret=1 export MONKEYSPHERE_VALIDATION_AGENT_SOCKET="http://127.0.0.1:$MSVA_PORT" # convert TEST_MSVA_WAIT to seconds because that's what "sleep" expects TEST_MSVA_SLEEP="$((${TEST_MSVA_WAIT} / 1000)).$((${TEST_MSVA_WAIT} % 1000))" # wait at most TEST_MSVA_MAX_WAIT milliseconds for MSVA to get ready waited=0 until [ ${ret} -eq 0 ] \ || [ ${waited} -ge ${TEST_MSVA_MAX_WAIT} ]; do if msva-query-agent https "$(cat client.uid)" x509pem client < client/x509.pem then ret=0 else echo "MSVA not ready yet" fi sleep "${TEST_MSVA_SLEEP}" waited=$((${waited} + ${TEST_MSVA_WAIT})) done # check if MSVA is up, fail if not if [ ${ret} -eq 0 ]; then printf "\nSUCCESS: initial MSVA verification\n" else printf "\nFAIL: initial MSVA verification\n" exit 1 fi fi TEST_PID="apache2.pid" # configure locking for the Apache process if [ -n "${USE_TEST_NAMESPACE}" ]; then echo "Using namespaces to isolate tests, no need for locking." flock_cmd="" elif [ -n "${TEST_LOCK}" ]; then flock_cmd="${FLOCK} --verbose -w ${TEST_LOCK_WAIT} $(realpath ${TEST_LOCK})" else echo "Locking disabled, using wait based on Apache PID file." wait_pid_gone "${TEST_PID}" flock_cmd="" fi t="$(realpath ${testid})" export srcdir="$(realpath ${srcdir})" export TEST_NAME="$(basename "$t")" output="outputs/${TEST_NAME}.output" rm -f "$output" if [ -e ${t}/fail.* ]; then EXPECTED_FAILURE="$(printf " (expected: %s)" fail.*)" else unset EXPECTED_FAILURE fi printf "TESTING: %s%s\n" "$TEST_NAME" "$EXPECTED_FAILURE" trap apache_down_err EXIT if [ -n "${USE_MSVA}" ]; then export MONKEYSPHERE_VALIDATION_AGENT_SOCKET="http://127.0.0.1:$MSVA_PORT" fi if ! ${flock_cmd} ${APACHE2} -f "${t}/apache.conf" -k start; then if [ -e "${t}/fail.server" ]; then echo "Apache HTTPD failed to start as expected." exit 0 else echo "Apache HTTPD unexpectedly failed to start." exit 1 fi fi # check OCSP server if [ -n "${CHECK_OCSP_SERVER}" ]; then if [ -n "${OCSP_RESPONSE_FILE}" ]; then store_ocsp="--outfile ${OCSP_RESPONSE_FILE}" fi echo "---- Testing OCSP server ----" ocsptool --ask --nonce --load-issuer authority/x509.pem --load-cert server/x509.pem ${store_ocsp} echo "---- OCSP test done ----" fi # PID file for sleep command (explanation below) sleep_pidfile="$(mktemp mod_gnutls_test-XXXXXX.pid)" # The sleep call keeps the pipe from the subshell to gnutls-cli # open. Without it gnutls-cli would terminate as soon as sed is # done, and not wait for a response from the server, leading to # failing tests. Sending sleep to the background allows the test # case to proceed instead of waiting for it to return. The sleep # process is stopped after gnutls-cli terminates. # # The line end manipulation in sed guarantees that all header lines # end with CRLF as required by RFC 7230, Section 3.1.1 regardless of # the line ends in the input file. if (sed -r "s/__HOSTNAME__/${TEST_HOST}/;s/\r?$/\r/" <${t}/input && \ run_with_pidfile "${sleep_pidfile}" sleep "${TEST_QUERY_DELAY}" &) | \ gnutls-cli -p "${TEST_PORT}" $(cat ${t}/gnutls-cli.args) "${TEST_HOST}" \ | tee "$output" && test "${PIPESTATUS[1]}" -eq 0; then if [ -e ${t}/fail* ]; then printf "%s should have failed but succeeded\n" "$(basename "$t")" >&2 exit 1 fi else if [ ! -e ${t}/fail* ]; then printf "%s should have succeeded but failed\n" "$(basename "$t")" >&2 exit 1 fi fi kill_by_pidfile "${sleep_pidfile}" unset sleep_pidfile if [ -e ${t}/output ] ; then diff_output_filter_headers "${t}/output" "$output" >&2 fi if [ -n "${USE_MSVA}" ]; then trap stop_msva EXIT else trap - EXIT fi ${APACHE2} -f "${t}/apache.conf" -k stop || [ -e ${t}/fail.server ] printf "SUCCESS: %s\n" "$TEST_NAME" if [ -n "${USE_MSVA}" ]; then stop_msva # Without explicitly resetting the trap function, it would be # called again on exit. Of course, we could just not stop MSVA and # let the trap do the work, but I think the code is easier to # understand like this. trap - EXIT fi