#!/bin/sh # # Copyright (C) Internet Systems Consortium, Inc. ("ISC") # # SPDX-License-Identifier: MPL-2.0 # # 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 https://mozilla.org/MPL/2.0/. # # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. testsock6() { if test -n "$PERL" && $PERL -e "use IO::Socket::IP;" 2> /dev/null then $PERL "$TOP_SRCDIR/bin/tests/system/testsock6.pl" "$@" else false fi } export LANG=C # # Set up color-coded test output # if [ ${SYSTEMTEST_FORCE_COLOR:-0} -eq 1 ] || test -t 1 && type tput > /dev/null 2>&1 && tput setaf 7 > /dev/null 2>&1 ; then export COLOR_END=$(tput setaf 4) # blue export COLOR_FAIL=$(tput setaf 1) # red export COLOR_INFO=$(tput bold) # bold export COLOR_NONE=$(tput sgr0) export COLOR_PASS=$(tput setaf 2) # green export COLOR_START=$(tput setaf 4) # blue export COLOR_WARN=$(tput setaf 3) # yellow else # set to empty strings so printf succeeds export COLOR_END='' export COLOR_FAIL='' export COLOR_INFO='' export COLOR_NONE='' export COLOR_PASS='' export COLOR_START='' export COLOR_WARN='' fi export SYSTESTDIR="$(basename $PWD)" if type printf > /dev/null 2>&1 then echofail () { printf "${COLOR_FAIL}%s${COLOR_NONE}\n" "$*" } echowarn () { printf "${COLOR_WARN}%s${COLOR_NONE}\n" "$*" } echopass () { printf "${COLOR_PASS}%s${COLOR_NONE}\n" "$*" } echoinfo () { printf "${COLOR_INFO}%s${COLOR_NONE}\n" "$*" } echostart () { printf "${COLOR_START}%s${COLOR_NONE}\n" "$*" } echoend () { printf "${COLOR_END}%s${COLOR_NONE}\n" "$*" } echo_i() { printf '%s\n' "$*" | while IFS= read -r __LINE ; do echoinfo "I:$SYSTESTDIR:$__LINE" done } echo_ic() { printf '%s\n' "$*" | while IFS= read -r __LINE ; do echoinfo "I:$SYSTESTDIR: $__LINE" done } echo_d() { printf '%s\n' "$*" | while IFS= read -r __LINE ; do echoinfo "D:$SYSTESTDIR:$__LINE" done } else echofail () { echo "$*" } echowarn () { echo "$*" } echopass () { echo "$*" } echoinfo () { echo "$*" } echostart () { echo "$*" } echoend () { echo "$*" } echo_i() { echo "$@" | while IFS= read -r __LINE ; do echoinfo "I:$SYSTESTDIR:$__LINE" done } echo_ic() { echo "$@" | while IFS= read -r __LINE ; do echoinfo "I:$SYSTESTDIR: $__LINE" done } echo_d() { echo "$@" | while IFS= read -r __LINE ; do echoinfo "D:$SYSTESTDIR:$__LINE" done } fi cat_i() { while IFS= read -r __LINE ; do echoinfo "I:$SYSTESTDIR:$__LINE" done } cat_d() { while IFS= read -r __LINE ; do echoinfo "D:$SYSTESTDIR:$__LINE" done } digcomp() { { output=$($PERL $TOP_SRCDIR/bin/tests/system/digcomp.pl "$@"); result=$?; } || true [ -n "$output" ] && { echo "digcomp failed:"; echo "$output"; } | cat_i return $result } start_server() { $PERL "$TOP_SRCDIR/bin/tests/system/start.pl" "$SYSTESTDIR" "$@" } stop_server() { $PERL "$TOP_SRCDIR/bin/tests/system/stop.pl" "$SYSTESTDIR" "$@" } send() { $PERL "$TOP_SRCDIR/bin/tests/system/send.pl" "$@" } # # Useful variables in test scripts # # The following script sets the following algorithm-related variables. These # are selected randomly at runtime from a list of supported algorithms. The # randomization is deterministic and remains stable for a period of time for a # given platform. # # Default algorithm for testing. # DEFAULT_ALGORITHM # DEFAULT_ALGORITHM_NUMBER # DEFAULT_BITS # # This is an alternative algorithm for test cases that require more than one # algorithm (for example algorithm rollover). Must be different from # DEFAULT_ALGORITHM. # ALTERNATIVE_ALGORITHM # ALTERNATIVE_ALGORITHM_NUMBER # ALTERNATIVE_BITS # # This is an algorithm that is used for tests against the "disable-algorithms" # configuration option. Must be different from above algorithms. # DISABLED_ALGORITHM # DISABLED_ALGORITHM_NUMBER # DISABLED_BITS # # There are multiple algoritms sets to choose from (see get_algorithms.py). To # override the default choice, set the ALGORITHM_SET env var (see mkeys system # test for example). eval "$($PYTHON "$TOP_SRCDIR/bin/tests/system/get_algorithms.py")" # Default HMAC algorithm. # also update _common/rndc.conf and _common/rndc.key when updating DEFAULT_HMAC export DEFAULT_HMAC=hmac-sha256 # # Useful functions in test scripts # # assert_int_equal: compare two integer variables, $1 and $2 # # If $1 and $2 are equal, return 0; if $1 and $2 are not equal, report # the error using the description of the tested variable provided in $3 # and return 1. assert_int_equal() { found="$1" expected="$2" description="$3" if [ "${expected}" -ne "${found}" ]; then echo_i "incorrect ${description}: got ${found}, expected ${expected}" return 1 fi return 0 } # keyfile_to_keys_section: helper function for keyfile_to_*_keys() which # converts keyfile data into a key-style trust anchor configuration # section using the supplied parameters keyfile_to_keys() { section_name=$1 key_prefix=$2 shift shift echo "$section_name {" for keyname in $*; do awk '!/^; /{ printf "\t\""$1"\" " printf "'"$key_prefix "'" printf $4 " " $5 " " $6 " \"" for (i=7; i<=NF; i++) printf $i printf "\";\n" }' $keyname.key done echo "};" } # keyfile_to_dskeys_section: helper function for keyfile_to_*_dskeys() # converts keyfile data into a DS-style trust anchor configuration # section using the supplied parameters keyfile_to_dskeys() { section_name=$1 key_prefix=$2 shift shift echo "$section_name {" for keyname in $*; do $DSFROMKEY $keyname.key | \ awk '!/^; /{ printf "\t\""$1"\" " printf "'"$key_prefix "'" printf $4 " " $5 " " $6 " \"" for (i=7; i<=NF; i++) printf $i printf "\";\n" }' done echo "};" } # keyfile_to_trusted_keys: convert key data contained in the keyfile(s) # provided to a "trust-keys" section suitable for including in a # resolver's configuration file keyfile_to_trusted_keys() { keyfile_to_keys "trusted-keys" "" $* } # keyfile_to_static_keys: convert key data contained in the keyfile(s) # provided to a *static-key* "trust-anchors" section suitable for including in # a resolver's configuration file keyfile_to_static_keys() { keyfile_to_keys "trust-anchors" "static-key" $* } # keyfile_to_initial_keys: convert key data contained in the keyfile(s) # provided to an *initial-key* "trust-anchors" section suitable for including # in a resolver's configuration file keyfile_to_initial_keys() { keyfile_to_keys "trust-anchors" "initial-key" $* } # keyfile_to_static_ds_keys: convert key data contained in the keyfile(s) # provided to a *static-ds* "trust-anchors" section suitable for including in a # resolver's configuration file keyfile_to_static_ds() { keyfile_to_dskeys "trust-anchors" "static-ds" $* } # keyfile_to_initial_ds_keys: convert key data contained in the keyfile(s) # provided to an *initial-ds* "trust-anchors" section suitable for including # in a resolver's configuration file keyfile_to_initial_ds() { keyfile_to_dskeys "trust-anchors" "initial-ds" $* } # keyfile_to_key_id: convert a key file name to a key ID # # For a given key file name (e.g. "Kexample.+013+06160") provided as $1, # print the key ID with leading zeros stripped ("6160" for the # aforementioned example). keyfile_to_key_id() { echo "$1" | sed "s/.*+0\{0,4\}//" } # private_type_record: write a private type record recording the state of the # signing process # # For a given zone ($1), algorithm number ($2) and key file ($3), print the # private type record with default type value of 65534, indicating that the # signing process for this key is completed. private_type_record() { _zone=$1 _algorithm=$2 _keyfile=$3 _id=$(keyfile_to_key_id "$_keyfile") printf "%s. 0 IN TYPE65534 %s 5 %02x%04x0000\n" "$_zone" "\\#" "$_algorithm" "$_id" } # nextpart*() - functions for reading files incrementally # # These functions aim to facilitate looking for (or waiting for) # messages which may be logged more than once throughout the lifetime of # a given named instance by outputting just the part of the file which # has been appended since the last time we read it. # # Calling some of these functions causes temporary *.prev files to be # created that need to be cleaned up manually (usually by a given system # test's clean.sh script). # # Note that unlike other nextpart*() functions, nextpartread() is not # meant to be directly used in system tests; its sole purpose is to # reduce code duplication below. # # A quick usage example: # # $ echo line1 > named.log # $ echo line2 >> named.log # $ nextpart named.log # line1 # line2 # $ echo line3 >> named.log # $ nextpart named.log # line3 # $ nextpart named.log # $ echo line4 >> named.log # $ nextpartpeek named.log # line4 # $ nextpartpeek named.log # line4 # $ nextpartreset named.log # $ nextpartpeek named.log # line1 # line2 # line3 # line4 # $ nextpart named.log # line1 # line2 # line3 # line4 # $ nextpart named.log # $ # nextpartreset: reset the marker used by nextpart() and nextpartpeek() # so that it points to the start of the given file nextpartreset() { echo "0" > $1.prev } # nextpartread: read everything that's been appended to a file since the # last time nextpart() was called and print it to stdout, print the # total number of lines read from that file so far to file descriptor 3 nextpartread() { [ -f $1.prev ] || nextpartreset $1 prev=$(cat $1.prev) awk "NR > $prev "'{ print } END { print NR > "/dev/stderr" }' $1 2>&3 } # nextpart: read everything that's been appended to a file since the # last time nextpart() was called nextpart() { nextpartread $1 3> $1.prev.tmp mv $1.prev.tmp $1.prev } # nextpartpeek: read everything that's been appended to a file since the # last time nextpart() was called nextpartpeek() { nextpartread $1 3> /dev/null } # _search_log: look for message $1 in file $2 with nextpart(). _search_log() ( msg="$1" file="$2" nextpart "$file" | grep -F -e "$msg" > /dev/null ) # _search_log_re: same as _search_log but the message is an grep -E regex _search_log_re() ( msg="$1" file="$2" nextpart "$file" | grep -E -e "$msg" > /dev/null ) # _search_log_peek: look for message $1 in file $2 with nextpartpeek(). _search_log_peek() ( msg="$1" file="$2" nextpartpeek "$file" | grep -F -e "$msg" > /dev/null ) # wait_for_log: wait until message $2 in file $3 appears. Bail out after # $1 seconds. This needs to be used in conjunction with a prior call to # nextpart() or nextpartreset() on the same file to guarantee the offset is # set correctly. Tests using wait_for_log() are responsible for cleaning up # the created .prev files. wait_for_log() ( timeout="$1" msg="$2" file="$3" retry_quiet "$timeout" _search_log "$msg" "$file" && return 0 echo_i "exceeded time limit waiting for literal '$msg' in $file" return 1 ) # wait_for_log_re: same as wait_for_log, but the message is an grep -E regex wait_for_log_re() ( timeout="$1" msg="$2" file="$3" retry_quiet "$timeout" _search_log_re "$msg" "$file" && return 0 echo_i "exceeded time limit waiting for regex '$msg' in $file" return 1 ) # wait_for_log_peek: similar to wait_for_log() but peeking, so the file offset # does not change. wait_for_log_peek() ( timeout="$1" msg="$2" file="$3" retry_quiet "$timeout" _search_log_peek "$msg" "$file" && return 0 echo_i "exceeded time limit waiting for literal '$msg' in $file" return 1 ) # _retry: keep running a command until it succeeds, up to $1 times, with # one-second intervals, optionally printing a message upon every attempt _retry() { __retries="${1}" shift while :; do if "$@"; then return 0 fi __retries=$((__retries-1)) if [ "${__retries}" -gt 0 ]; then if [ "${__retry_quiet}" -ne 1 ]; then echo_i "retrying" fi sleep 1 else return 1 fi done } # retry: call _retry() in verbose mode retry() { __retry_quiet=0 _retry "$@" } # retry_quiet: call _retry() in silent mode retry_quiet() { __retry_quiet=1 _retry "$@" } # _repeat: keep running command up to $1 times, unless it fails _repeat() ( __retries="${1}" shift while :; do if ! "$@"; then return 1 fi __retries=$((__retries-1)) if [ "${__retries}" -le 0 ]; then break fi done return 0 ) _times() { awk "BEGIN{ for(i = 1; i <= $1; i++) print i}"; } rndc_reload() { $RNDC -c ../_common/rndc.conf -s $2 -p ${CONTROLPORT} reload $3 2>&1 | sed 's/^/'"I:$SYSTESTDIR:$1"' /' # reloading single zone is synchronous, if we're reloading whole server # we need to wait for reload to finish if [ -z "$3" ]; then for _ in $(_times 10); do $RNDC -c ../_common/rndc.conf -s $2 -p ${CONTROLPORT} status | grep "reload/reconfig in progress" > /dev/null || break sleep 1 done fi } rndc_reconfig() { seconds=${3:-10} $RNDC -c ../_common/rndc.conf -s "$2" -p "${CONTROLPORT}" reconfig 2>&1 | sed 's/^/'"I:$SYSTESTDIR:$1"' /' for _ in $(_times "$seconds"); do "$RNDC" -c ../_common/rndc.conf -s "$2" -p "${CONTROLPORT}" status | grep "reload/reconfig in progress" > /dev/null || break sleep 1 done } # rndc_dumpdb: call "rndc dumpdb [...]" and wait until it completes # # The first argument is the name server instance to send the command to, in the # form of "nsX" (where "X" is the instance number), e.g. "ns5". The remaining # arguments, if any, are appended to the rndc command line after "dumpdb". # # Control channel configuration for the name server instance to send the # command to must match the contents of bin/tests/system/_common/rndc.conf. # # rndc output is stored in a file called rndc.out.test${n}; the "n" variable is # required to be set by the calling tests.sh script. # # Return 0 if the dump completes successfully; return 1 if rndc returns an exit # code other than 0 or if the "; Dump complete" string does not appear in the # dump within 10 seconds. rndc_dumpdb() { __ret=0 __dump_complete=0 __server="${1}" __ip="10.53.0.$(echo "${__server}" | tr -c -d "0-9")" shift ${RNDC} -c ../_common/rndc.conf -p "${CONTROLPORT}" -s "${__ip}" dumpdb "$@" > "rndc.out.test${n}" 2>&1 || __ret=1 for _ in 0 1 2 3 4 5 6 7 8 9 do if grep '^; Dump complete$' "${__server}/named_dump.db" > /dev/null; then mv "${__server}/named_dump.db" "${__server}/named_dump.db.test${n}" __dump_complete=1 break fi sleep 1 done if [ ${__dump_complete} -eq 0 ]; then echo_i "timed out waiting for 'rndc dumpdb' to finish" __ret=1 fi return ${__ret} } # get_dig_xfer_stats: extract transfer statistics from dig output stored # in $1, converting them to a format used by some system tests. get_dig_xfer_stats() { LOGFILE="$1" sed -n "s/^;; XFR size: .*messages \([0-9][0-9]*\).*/messages=\1/p" "${LOGFILE}" sed -n "s/^;; XFR size: \([0-9][0-9]*\) records.*/records=\1/p" "${LOGFILE}" sed -n "s/^;; XFR size: .*bytes \([0-9][0-9]*\).*/bytes=\1/p" "${LOGFILE}" } # get_named_xfer_stats: from named log file $1, extract transfer # statistics for the last transfer for peer $2 and zone $3 (from a log # message which has to contain the string provided in $4), converting # them to a format used by some system tests. get_named_xfer_stats() { LOGFILE="$1" PEER="$(echo $2 | sed 's/\./\\./g')" ZONE="$(echo $3 | sed 's/\./\\./g')" MESSAGE="$4" grep " ${PEER}#.*${MESSAGE}:" "${LOGFILE}" | \ sed -n "s/.* '${ZONE}\/.* \([0-9][0-9]*\) messages.*/messages=\1/p" | tail -1 grep " ${PEER}#.*${MESSAGE}:" "${LOGFILE}" | \ sed -n "s/.* '${ZONE}\/.* \([0-9][0-9]*\) records.*/records=\1/p" | tail -1 grep " ${PEER}#.*${MESSAGE}:" "${LOGFILE}" | \ sed -n "s/.* '${ZONE}\/.* \([0-9][0-9]*\) bytes.*/bytes=\1/p" | tail -1 } # copy_setports - Copy Configuration File and Replace Ports # # Convenience function to copy a configuration file, replacing the tokens # QUERYPORT, CONTROLPORT and EXTRAPORT[1-8] with the values of the equivalent # environment variables. (These values are set by test runner, which calls the # scripts invoking this function.) # # Usage: # copy_setports infile outfile # copy_setports() { dir=$(echo "$TMPDIR" | sed 's/\//\\\//g') sed -e "s/@TMPDIR@/${dir}/g" \ -e "s/@PORT@/${PORT}/g" \ -e "s/@TLSPORT@/${TLSPORT}/g" \ -e "s/@HTTPPORT@/${HTTPPORT}/g" \ -e "s/@HTTPSPORT@/${HTTPSPORT}/g" \ -e "s/@EXTRAPORT1@/${EXTRAPORT1}/g" \ -e "s/@EXTRAPORT2@/${EXTRAPORT2}/g" \ -e "s/@EXTRAPORT3@/${EXTRAPORT3}/g" \ -e "s/@EXTRAPORT4@/${EXTRAPORT4}/g" \ -e "s/@EXTRAPORT5@/${EXTRAPORT5}/g" \ -e "s/@EXTRAPORT6@/${EXTRAPORT6}/g" \ -e "s/@EXTRAPORT7@/${EXTRAPORT7}/g" \ -e "s/@EXTRAPORT8@/${EXTRAPORT8}/g" \ -e "s/@CONTROLPORT@/${CONTROLPORT}/g" \ -e "s/@DEFAULT_ALGORITHM@/${DEFAULT_ALGORITHM}/g" \ -e "s/@DEFAULT_ALGORITHM_NUMBER@/${DEFAULT_ALGORITHM_NUMBER}/g" \ -e "s/@DEFAULT_BITS@/${DEFAULT_BITS}/g" \ -e "s/@ALTERNATIVE_ALGORITHM@/${ALTERNATIVE_ALGORITHM}/g" \ -e "s/@ALTERNATIVE_ALGORITHM_NUMBER@/${ALTERNATIVE_ALGORITHM_NUMBER}/g" \ -e "s/@ALTERNATIVE_BITS@/${ALTERNATIVE_BITS}/g" \ -e "s/@DEFAULT_HMAC@/${DEFAULT_HMAC}/g" \ -e "s/@DISABLED_ALGORITHM@/${DISABLED_ALGORITHM}/g" \ -e "s/@DISABLED_ALGORITHM_NUMBER@/${DISABLED_ALGORITHM_NUMBER}/g" \ -e "s/@DISABLED_BITS@/${DISABLED_BITS}/g" \ $1 > $2 } # parse_openssl_config - Parse OpenSSL configuration for HSM settings # # Will set SOFTHSM2_MODULE, OPENSSL_ENGINE and ENGINE_ARG based on openssl configuration. parse_openssl_config() { ENGINE_ARG="" [ -f "$OPENSSL_CONF" ] || return 0 while IFS="=" read key val; do # trim variables key="${key## }" key="${key%% }" val="${val## }" val="${val%% }" case "$key" in "engine_id") OPENSSL_ENGINE="$val" ENGINE_ARG="-E $OPENSSL_ENGINE" ;; "MODULE_PATH"|"pkcs11-module-path") SOFTHSM2_MODULE="$val" ;; esac done < "$OPENSSL_CONF" }