# -*- makefile -*-
##
## Makefile -- Build and run tests for the server.
##
##	http://www.freeradius.org/
##	$Id$
##
#
include ../../Make.inc

BUILD_PATH := $(top_builddir)/build

#
#  Build eapol_test if requested to.
#
.PHONY: eapol_test
eapol_test: $(BUILD_PATH)/tests/eapol_test/eapol_test.mk
	@echo EAPOL_TEST=$(EAPOL_TEST)

#
#  If we're doing anything resembling EAP, then make sure that
#  EAPOL_TEST is defined.
#
ifneq "(findstring eap,$(MAKECMDGOALS))" ""
$(BUILD_PATH)/tests/eapol_test:
	@mkdir -p $@

TEST_PATH := $(top_builddir)/src/tests
DICT_PATH := $(TEST_PATH)
BIN_PATH := $(BUILD_PATH)/bin/local
RADIUSD_BIN := $(BIN_PATH)/radiusd

ifeq "$(DICT_PATH)" "$(TEST_PATH)"
LIB_PATH := $(BUILD_PATH)/lib/local/.libs/
DYLD_LIBRARY_PATH := $(DYLD_LIBRARY_PATH):$(LIB_PATH)
export DYLD_LIBRARY_PATH
endif

ifneq "$(OPENSSL_LIBS)" ""
#
#  Build eapol_test, and cache its output.  Note that EAPOL_TEST may not be
#  defined, so we have to run the shell script for the second line, too.
#
#  Normal expansion will still run the script if EAPOL_TEST_BIN is
#  set but empty, which we don't want.
#
ifeq "$(EAPOL_TEST_BIN)" ""
override EAPOL_TEST_BIN := $(shell $(top_builddir)/scripts/ci/eapol_test-build.sh)
endif

$(BUILD_PATH)/tests/eapol_test/eapol_test.mk: | $(BUILD_PATH)/tests/eapol_test
	@echo "EAPOL_TEST=$(EAPOL_TEST_BIN)" > $@
	@echo "TLS1_3=$(shell openssl ciphers -s -v 'ECDHE:!COMPLEMENTOFDEFAULT'| grep -q 'TLSv1.3' && echo yes)" >> $@
	@echo "OPENSSL_OK=$(shell openssl version | grep -v ' 1\.0' >/dev/null && echo yes)" >> $@
	@echo "OPENSSL3_OK=$(shell openssl version | grep -q ' OpenSSL 3\.0' && echo yes)" >> $@
else
#
#  No OpenSSL means that we don't even try to build eapol_test
#
.PHONY: $(BUILD_PATH)/tests/eapol_test/eapol_test.mk
$(BUILD_PATH)/tests/eapol_test/eapol_test.mk: | $(BUILD_PATH)/tests/eapol_test
	@touch $@
endif

-include $(BUILD_PATH)/tests/eapol_test/eapol_test.mk
endif

#
#  OpenSSL 1.0.x doesn't support cipher_list="DEFAULT@SECLEVEL=1"
#
#  If the variable is empty, then OpenSSL isn't OK.
#
ifeq "$(OPENSSL_OK)" ""
SECLEVEL=
else
SECLEVEL=@SECLEVEL=1
endif

#
#  For OpenSSL 3.0.x, as described in https://github.com/openssl/openssl/blob/master/doc/man7/migration_guide.pod
#
#  "The security strength of SHA1 and MD5 based signatures in TLS has been reduced.
#   This results in SSL 3, TLS 1.0, TLS 1.1 and DTLS 1.0 no longer working at the
#   default security level of 1 and instead requires security level 0."
#
ifeq "$(OPENSSL3_OK)" "yes"
SECLEVEL=@SECLEVEL=0
endif

RADDB_PATH := $(top_builddir)/raddb/

TESTS	= mschapv1 digest-01/digest* \
	test.example.com

PORT	 = 12340
ACCTPORT = $(shell expr $(PORT) + 1)

#	example.com stripped.example.com

SECRET	= testing123

.PHONY: all eap dictionary clean

#
#	Build the directory for testing the server
#
all: tests

clean:
	@rm -f test.conf dictionary *.ok *.log $(BUILD_DIR)/tests/eap

dictionary:
	@echo "# test dictionary. Do not install.  Delete at any time." > dictionary;  \
	echo '$$INCLUDE ' $(top_builddir)/share/dictionary >> dictionary;          \
	echo '$$INCLUDE ' $(top_builddir)/src/tests/dictionary.test >> dictionary; \
	if [ "$(DICT_PATH)" = "$(TEST_PATH)" ]; then                               \
	    echo '$$INCLUDE ' $(top_builddir)/share/dictionary.dhcp >> dictionary; \
	    echo '$$INCLUDE ' $(top_builddir)/share/dictionary.vqp >> dictionary;  \
	fi

test.conf: dictionary config/eap-test
	@echo "# test configuration file.  Do not install.  Delete at any time." > $@
	@if [ -n "$(LIB_PATH)" ]; then  \
	    echo "libdir =" $(LIB_PATH) >> $@; \
	fi
	@echo "testdir =" $(TEST_PATH) >> $@
	@echo 'logdir = $${testdir}' >> $@
	@echo "maindir =" $(RADDB_PATH) >> $@
	@echo 'radacctdir = $${testdir}' >> $@
	@echo 'pidfile = $${testdir}/radiusd.pid' >> $@
	@echo 'panic_action = "gdb -batch -x $${testdir}/panic.gdb %e %p > $${testdir}/gdb.log 2>&1; cat $${testdir}/gdb.log"' >> $@
	@echo 'security {' >> $@
	@echo '        allow_vulnerable_openssl = yes' >> $@
	@echo '}' >> $@
	@echo >> $@
	@echo 'modconfdir = $${maindir}mods-config' >> $@
	@echo 'certdir = $${maindir}/certs' >> $@
	@echo 'cadir   = $${maindir}/certs' >> $@
	@echo '$$INCLUDE $${testdir}/config/' >> $@
	@echo '$$INCLUDE $${maindir}/radiusd.conf' >> $@

#
#  Rename "inner-tunnel", and ensure that it only uses the "eap-test" module.
#
config/eap-test-inner-tunnel: $(RADDB_PATH)sites-available/inner-tunnel
	@sed 's/eap/eap-test/;s/server inner-tunnel/server eap-test-inner-tunnel/' < $< > $@

#
#  * Same renames as above
#  * enable caching
#  * uncomment caching directory
#  * set the minimum TLS version to 1.0 for testing
#  * set the maximum TLS version to 1.2 or 1.3, depending if 1.3 is available
#  * always enable TLS 1.3 for the tests, via the super-secret magic flag.
#  * tell OpenSSL to enable insecure ciphers TLS 1.0 and TLS 1.1
#
config/eap-test: $(RADDB_PATH)mods-available/eap config/eap-test-inner-tunnel
	@sed -e 's/eap {/eap eap-test {/' \
	     -e 's/= inner-tunnel/= eap-test-inner-tunnel/;s/use_tunneled_reply = no/use_tunneled_reply = yes/' \
	     -e 's/enable = no/enable = yes/' \
	     -e 's/^\(.*\)persist_dir =/  persist_dir =/' \
	     -e 's/tls_min_version = "1.2"/tls_min_version = "1.0"/' \
	     -e '$(if $(TLS1_3),s/tls_max_version = "1.2"/tls_max_version = "1.3"/)' \
	     -e 's/cipher_list = "DEFAULT"/cipher_list = "DEFAULT${SECLEVEL}"/' \
		 < $< > $@

radiusd.pid: test.conf
	@rm -rf $(TEST_PATH)/gdb.log $(TEST_PATH)/radius.log $(TEST_PATH)/tlscache
	@mkdir -p $(TEST_PATH)/tlscache
	@printf "Starting server... "
	@if ! $(RADIUSD_BIN) -Pxxxxml $(TEST_PATH)/radius.log -d ${top_builddir}/src/tests -n test -i 127.0.0.1 -p $(PORT) -D $(DICT_PATH); then \
		echo "failed"; \
		echo "Last log entries were:"; \
		tail -n 20 "$(TEST_PATH)/radius.log"; \
	fi
	@echo "ok"

# We can't make this depend on radiusd.pid, because then make will create
# radiusd.pid when we make radiusd.kill, which we don't want.
.PHONY: radiusd.kill
radiusd.kill:
	@if [ -f radiusd.pid ]; then \
	    ret=0; \
	    if ! ps `cat $(TEST_PATH)/radiusd.pid` >/dev/null 2>&1; then \
		rm -f radiusd.pid; \
		echo "FreeRADIUS terminated during test"; \
		echo "GDB output was:"; \
		cat "$(TEST_PATH)/gdb.log"; \
		echo "Last log entries were:"; \
		tail -n 20 $(TEST_PATH)/radius.log; \
		ret=1; \
	    fi; \
		if ! kill -TERM `cat $(TEST_PATH)/radiusd.pid` >/dev/null 2>&1; then \
		    ret=1; \
		fi; \
		exit $$ret; \
	fi
	@rm -f radiusd.pid

#
#  Run eapol_test if it exists and we built with openssl support.
#  Otherwise do nothing.
#
ifneq "$(EAPOL_TEST)" ""
EAP_FILES        = eap-md5.conf
EAP_TLS_FILES    = eap-ttls-pap.conf eap-ttls-mschapv2.conf peap-mschapv2.conf
EAP_TLS_VERSIONS = 1.1 1.2
EAP_TLS_DISABLE_STRING = tls_disable_tlsv1_0=1 tls_disable_tlsv1_1=1 tls_disable_tlsv1_2=1

ifneq "$(TLS1_3)" ""
EAP_TLS_VERSIONS       += 1.3
EAP_TLS_DISABLE_STRING += tls_disable_tlsv1_3=1
endif

.PHONY: $(BUILD_PATH)/tests/eap
$(BUILD_PATH)/tests/eap:
	@mkdir -p $@

.PHONY: clean.tests.eap
clean.tests.eap:
	@rm -rf $(BUILD_PATH)/tests/eap config/tlscache config/eap-test config/eap-test-inner-tunnel

#
#  Set target-specific variables, so that the later shell scripts are rather more understandable.
#
#  MD5 doesn't use MPPE keys
#
$(BUILD_PATH)/tests/eap/%.ok: NO_MPPE = $(filter eap-md5,$(basename $(notdir $@)))
$(BUILD_PATH)/tests/eap/%.ok: CMD =  $(EAPOL_TEST) -c $< -p $(PORT) -s $(SECRET) $(if $(NO_MPPE),-n)
$(BUILD_PATH)/tests/eap/%.ok: LOG =  $(patsubst %.ok,%,$@).log

$(BUILD_PATH)/tests/eap/%.ok: $(top_builddir)/src/tests/%.conf | radiusd.kill $(BUILD_PATH)/tests/eap radiusd.pid radiusd.kill
	@printf 'EAPOL_TEST %s ' $(notdir $(patsubst %.conf,%,$<))
	@if ! $(CMD) > $(LOG) 2>&1; then \
		echo " - " FAILED - command failed; \
		echo ">>> cmd -" $(CMD); \
		echo ">>> log -" $(LOG); \
		echo "===================="; \
		tail -10 $(LOG); \
		echo "===================="; \
		$(MAKE) radiusd.kill; \
		exit 1; \
	fi
	@echo
	@touch $@

#
#  Don't run the full TLS version tests for CI post-install.
#
ifneq "$(prefix)" ""
#
#  ${1} is the config file
#  ${2} is the TLS version to use.
#
#  Update the phase1 configuration to enable/disable various TLS versions
#  insert an OpenSSL cipher configuration line by cloning "password" and editing it.
#
define EAP_TLS_CONFIG
$(BUILD_PATH)/tests/eap/${1}-${2}.conf: $(top_builddir)/src/tests/${1}.conf
	@sed -e 's/phase1="/phase1="$(subst $(subst .,_,${2})=1,$(subst .,_,${2})=0,$(EAP_TLS_DISABLE_STRING)) /' \
	     -e  '/password/s/^//p; /password/s/^.*/        openssl_ciphers="DEFAULT${SECLEVEL}"/' \
		< $$< > $$@

$(BUILD_PATH)/tests/eap/${1}-${2}.ok: $(BUILD_PATH)/tests/eap/${1}-${2}.conf
	@printf 'EAPOL_TEST %s' $$(notdir $$(patsubst %.ok,%,$$@))
	@if ! $$(CMD) -r 1 > $$(LOG) 2>&1; then \
		echo " - " FAILED  - command failed; \
		echo ">>> cmd -" $$(CMD) -r 1; \
		echo ">>> log -" $$(LOG); \
		echo "===================="; \
		tail -10 $$(LOG); \
		echo "===================="; \
		$(MAKE) radiusd.kill; \
		exit 1; \
	elif ! grep -q '^SSL: Using TLS version TLSv${2}$$$$' $$(patsubst %.ok,%,$$@).log; then \
		echo " - " FAILED - not using TLS version ${2}; \
		echo ">>> cmd -" $$(CMD) -r 1; \
		echo ">>> log -" $$(LOG); \
		$(MAKE) radiusd.kill; \
		exit 1; \
	elif ! grep -q '^OpenSSL: Handshake finished - resumed=1$$$$' $$(patsubst %.ok,%,$$@).log; then \
		echo " - " FAILED - did not use resumption; \
		echo ">>> cmd -" $$(CMD) -r 1; \
		echo ">>> log -" $$(LOG); \
		$(MAKE) radiusd.kill; \
		exit 1; \
	fi
	@echo
	@touch $$@

# EAP-FAST doesn't do TLS 1.3
ifneq "${1}-${2}" "eap-fast-1.3"
EAP_TLS_VERSION_FILES += $(BUILD_PATH)/tests/eap/${1}-${2}.ok
endif
endef

$(foreach FILE,$(patsubst %.conf,%,$(EAP_TLS_FILES)),$(foreach TLS,$(EAP_TLS_VERSIONS),$(eval $(call EAP_TLS_CONFIG,${FILE},${TLS}))))
endif # there's no "prefix", so we don't run the full EAP tests

EAPOL_OK_FILES := $(sort $(addprefix $(BUILD_PATH)/tests/eap/,$(patsubst %.conf,%.ok, $(notdir $(EAP_TLS_FILES) $(EAP_FILES)))) $(EAP_TLS_VERSION_FILES))

tests.eap: $(EAPOL_OK_FILES) | radiusd.kill radiusd.pid
	@$(MAKE) radiusd.kill

endif # we have eapol_test built

# kill the server (if it's running)
# start the server
# run the tests (ignoring any failures)
# kill the server
# remove the changes to raddb/
tests: test.conf | radiusd.kill radiusd.pid
	@chmod a+x runtests.sh
	@BIN_PATH="$(BIN_PATH)" PORT="$(PORT)" ./runtests.sh $(TESTS)
ifneq "$(EAPOL_TEST)" ""
	@$(MAKE) tests.eap
endif
