#
# Copyright (c) 2014 Brent Cook
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

cmake_minimum_required (VERSION 3.16.4)

if(MSVC)
	cmake_policy(SET CMP0091 NEW)
endif()

project(LibreSSL LANGUAGES C ASM)

include(CheckCSourceCompiles)
include(CheckCSourceRuns)
include(CheckFunctionExists)
include(CheckSymbolExists)
include(CheckLibraryExists)
include(CheckIncludeFiles)
include(CheckTypeSize)

set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}" ${CMAKE_MODULE_PATH})
include(cmake_export_symbol)
include(GNUInstallDirs)

enable_testing()
find_program(PERL_EXECUTABLE perl perl5)
set(HAVE_PERL_IO_SOCKET_SSL_UTILS FALSE)
if(PERL_EXECUTABLE)
	execute_process(COMMAND ${PERL_EXECUTABLE} -MIO::Socket::SSL::Utils -e 1
	    RESULT_VARIABLE PERL_IO_SOCKET_SSL_UTILS_RESULT
	    OUTPUT_QUIET ERROR_QUIET)
	if(PERL_IO_SOCKET_SSL_UTILS_RESULT EQUAL 0)
		set(HAVE_PERL_IO_SOCKET_SSL_UTILS TRUE)
	endif()
endif()

file(READ ${CMAKE_CURRENT_SOURCE_DIR}/ssl/VERSION SSL_VERSION)
string(STRIP ${SSL_VERSION} SSL_VERSION)
string(REPLACE ":" "." SSL_VERSION ${SSL_VERSION})
string(REGEX REPLACE "\\..*" "" SSL_MAJOR_VERSION ${SSL_VERSION})

file(READ ${CMAKE_CURRENT_SOURCE_DIR}/crypto/VERSION CRYPTO_VERSION)
string(STRIP ${CRYPTO_VERSION} CRYPTO_VERSION)
string(REPLACE ":" "." CRYPTO_VERSION ${CRYPTO_VERSION})
string(REGEX REPLACE "\\..*" "" CRYPTO_MAJOR_VERSION ${CRYPTO_VERSION})

file(READ ${CMAKE_CURRENT_SOURCE_DIR}/tls/VERSION TLS_VERSION)
string(STRIP ${TLS_VERSION} TLS_VERSION)
string(REPLACE ":" "." TLS_VERSION ${TLS_VERSION})
string(REGEX REPLACE "\\..*" "" TLS_MAJOR_VERSION ${TLS_VERSION})

option(LIBRESSL_SKIP_INSTALL "Skip installation" ${LIBRESSL_SKIP_INSTALL})
option(LIBRESSL_APPS "Build apps" ON)
option(LIBRESSL_TESTS "Build tests" ON)
option(ENABLE_ASM "Enable assembly" ON)
option(ENABLE_EXTRATESTS "Enable extra tests that may be unreliable on some platforms" OFF)
option(ENABLE_NC "Enable installing TLS-enabled nc(1)" OFF)
set(OPENSSLDIR ${OPENSSLDIR} CACHE PATH "Set the default openssl directory" FORCE)
set(TLS_DEFAULT_CA_FILE ${TLS_DEFAULT_CA_FILE} CACHE PATH "Set the default CA file for libtls" FORCE)
set(LIBRESSL_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/LibreSSL" CACHE STRING "Installation directory for the CMake targets")

option(USE_STATIC_MSVC_RUNTIMES "Use /MT instead of /MD in MSVC" OFF)
if(USE_STATIC_MSVC_RUNTIMES)
    set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
endif()

if(NOT LIBRESSL_SKIP_INSTALL)
	set( ENABLE_LIBRESSL_INSTALL ON )
endif(NOT LIBRESSL_SKIP_INSTALL)

# Set a default build type if none was specified
set(default_build_type "Release")

if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
  message(STATUS "Setting build type to '${default_build_type}' as none was specified.")
  set(CMAKE_BUILD_TYPE "${default_build_type}" CACHE
      STRING "Choose the type of build." FORCE)
  # Set the possible values of build type for cmake-gui
  set_property(CACHE CMAKE_BUILD_TYPE PROPERTY
	  STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo")
endif()

# Do not disable assertions based on CMAKE_BUILD_TYPE
foreach(_build_type "Release" "MinSizeRel" "RelWithDebInfo")
	foreach(_lang C CXX)
		string(TOUPPER "CMAKE_${_lang}_FLAGS_${_build_type}" _var)
		string(REGEX REPLACE "(^| )[/-]D *NDEBUG($| )" " " ${_var} "${${_var}}")
	endforeach()
endforeach()

set(BUILD_NC true)

if(CMAKE_SYSTEM_NAME MATCHES "OpenBSD")
	add_definitions(-DHAVE_ATTRIBUTE__BOUNDED__)
	add_definitions(-DHAVE_ATTRIBUTE__DEAD__)
endif()

if(CMAKE_SYSTEM_NAME MATCHES "Linux")
	add_definitions(-D_DEFAULT_SOURCE)
	add_definitions(-D_BSD_SOURCE)
	add_definitions(-D_POSIX_SOURCE)
	add_definitions(-D_GNU_SOURCE)
	set(PLATFORM_LIBS ${PLATFORM_LIBS} pthread)
endif()

if(CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
	set(PLATFORM_LIBS ${PLATFORM_LIBS} pthread)
endif()

if(WIN32)
	set(BUILD_NC false)
	if(MINGW)
		add_definitions(-D_GNU_SOURCE)
		add_definitions(-D_POSIX)
		add_definitions(-D_POSIX_SOURCE)
		add_definitions(-D__USE_MINGW_ANSI_STDIO)
	endif()
endif()

if(CMAKE_SYSTEM_NAME MATCHES "HP-UX")
	if(CMAKE_C_COMPILER MATCHES "gcc")
		set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 -fno-strict-aliasing")
		set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mlp64")
	else()
		set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} +DD64 +Otype_safety=off")
	endif()
	set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_XOPEN_SOURCE=600 -D__STRICT_ALIGNMENT")
	set(PLATFORM_LIBS ${PLATFORM_LIBS} pthread)
endif()

if(CMAKE_SYSTEM_NAME MATCHES "SunOS")
	set(SOLARIS TRUE)
	set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 -fno-strict-aliasing")
	set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D__EXTENSIONS__")
	set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_XOPEN_SOURCE=600")
	set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DBSD_COMP")
	set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fpic")
	set(PLATFORM_LIBS ${PLATFORM_LIBS} dl md nsl socket)
endif()

add_definitions(-DLIBRESSL_INTERNAL)
add_definitions(-DOPENSSL_NO_HW_PADLOCK)
add_definitions(-D__BEGIN_HIDDEN_DECLS=)
add_definitions(-D__END_HIDDEN_DECLS=)

set(CMAKE_POSITION_INDEPENDENT_CODE true)

if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_C_COMPILER_ID MATCHES "Clang")
	add_compile_options($<$<COMPILE_LANGUAGE:C>:-Wno-pointer-sign>)
endif()

# Exploit-mitigation flags, matching the default-on hardening the autotools
# build applies (m4/check-hardening-options.m4). The CMake build shipped none
# of these, so a cmake-built libcrypto/libssl/libtls and openssl(1) had no
# stack protector, no _FORTIFY_SOURCE, no RELRO/BIND_NOW and no CET, and the
# mingw DLLs missed the DEP/ASLR opt-ins. MSVC is left to its own toolchain
# defaults, exactly as that m4 (which only drives gcc/clang/mingw flags) does.
# Every flag is probed before use so unsupported targets simply skip it.
option(ENABLE_HARDENING
	"Enable options to frustrate memory corruption exploits" ON)
option(ENABLE_WINDOWS_SSP
	"Build stack smashing protection on Windows (requires libssp)" OFF)

if(ENABLE_HARDENING AND NOT MSVC AND
    (CMAKE_COMPILER_IS_GNUCC OR CMAKE_C_COMPILER_ID MATCHES "Clang"))
	include(CheckCCompilerFlag)

	macro(add_harden_cflag _flag _var)
		check_c_compiler_flag("${_flag}" ${_var})
		if(${_var})
			add_compile_options($<$<COMPILE_LANGUAGE:C>:${_flag}>)
		endif()
	endmacro()

	# CMake 3.16 predates check_linker_flag, so probe link flags by linking.
	macro(add_harden_ldflag _flag _var)
		set(_saved_link_options "${CMAKE_REQUIRED_LINK_OPTIONS}")
		set(CMAKE_REQUIRED_LINK_OPTIONS "${_flag}")
		check_c_source_compiles("int main(void){return 0;}" ${_var})
		set(CMAKE_REQUIRED_LINK_OPTIONS "${_saved_link_options}")
		if(${_var})
			add_link_options("${_flag}")
		endif()
	endmacro()

	# Do not optimize based on signed arithmetic overflow.
	add_harden_cflag(-fno-strict-overflow HAVE_CFLAG_FNO_STRICT_OVERFLOW)

	if(NOT WIN32)
		# _FORTIFY_SOURCE needs an optimizing build and warns without one,
		# so probe it at -O2 and only apply it to non-Debug C compiles.
		set(_saved_req_flags "${CMAKE_REQUIRED_FLAGS}")
		set(CMAKE_REQUIRED_FLAGS
			"-O2 -Werror -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2")
		check_c_source_compiles("#include <string.h>
int main(void){char b[8];const char *s=\"x\";strcpy(b,s);return b[0];}"
			HAVE_CFLAG_FORTIFY_SOURCE)
		set(CMAKE_REQUIRED_FLAGS "${_saved_req_flags}")
		if(HAVE_CFLAG_FORTIFY_SOURCE)
			add_compile_options(
			    "$<$<AND:$<COMPILE_LANGUAGE:C>,$<NOT:$<CONFIG:Debug>>>:-U_FORTIFY_SOURCE>"
			    "$<$<AND:$<COMPILE_LANGUAGE:C>,$<NOT:$<CONFIG:Debug>>>:-D_FORTIFY_SOURCE=2>")
		endif()

		# Read-only relocations, resolved at load time. wasm has no
		# ELF loader and wasm-ld only warns about unknown -z values,
		# so the probe false-positives on Emscripten; skip it there.
		if(NOT EMSCRIPTEN)
			add_harden_ldflag(-Wl,-z,relro HAVE_LDFLAG_Z_RELRO)
			add_harden_ldflag(-Wl,-z,now HAVE_LDFLAG_Z_NOW)
		endif()
	else()
		# Windows (mingw) DEP, ASLR and high-entropy ASLR opt-ins.
		add_harden_ldflag(-Wl,--nxcompat HAVE_LDFLAG_NXCOMPAT)
		add_harden_ldflag(-Wl,--dynamicbase HAVE_LDFLAG_DYNAMICBASE)
		add_harden_ldflag(-Wl,--high-entropy-va HAVE_LDFLAG_HIGH_ENTROPY_VA)
	endif()

	# Stack smashing protection. On Windows this pulls in libssp, so it is
	# opt-in there, mirroring --enable-windows-ssp. Emscripten accepts the
	# flag (and the frameless probe links), but its libc has no
	# __stack_chk_guard/__stack_chk_fail, so any protected frame fails to
	# link; it provides its own stack checks (-sSTACK_OVERFLOW_CHECK).
	if(NOT EMSCRIPTEN AND (NOT WIN32 OR ENABLE_WINDOWS_SSP))
		check_c_compiler_flag(-fstack-protector-strong
			HAVE_CFLAG_STACK_PROTECTOR_STRONG)
		if(HAVE_CFLAG_STACK_PROTECTOR_STRONG)
			add_compile_options(
				$<$<COMPILE_LANGUAGE:C>:-fstack-protector-strong>)
		else()
			add_harden_cflag(-fstack-protector-all
				HAVE_CFLAG_STACK_PROTECTOR_ALL)
		endif()
		if(WIN32)
			set(PLATFORM_LIBS ${PLATFORM_LIBS} ssp)
		endif()
	endif()

	# Control-flow integrity (Intel CET); unsupported on Darwin.
	if(NOT APPLE)
		add_harden_cflag(-fcf-protection=full HAVE_CFLAG_CF_PROTECTION)
	endif()
endif()

if(WIN32)
	add_definitions(-D_CRT_SECURE_NO_WARNINGS)
	add_definitions(-D_CRT_DEPRECATED_NO_WARNINGS)
	add_definitions(-D_REENTRANT -D_POSIX_THREAD_SAFE_FUNCTIONS)
	add_definitions(-DNO_SYSLOG)
	add_definitions(-DWIN32_LEAN_AND_MEAN)
	if(NOT CMAKE_SYSTEM_NAME MATCHES "WindowsStore")
		add_definitions(-D_WIN32_WINNT=0x0600)
	endif()
	set(PLATFORM_LIBS ${PLATFORM_LIBS} ws2_32 ntdll bcrypt)
endif()

if(MSVC)
	add_definitions(-Dinline=__inline)
	message(STATUS "Using [${CMAKE_C_COMPILER_ID}] compiler")

	include(TestBigEndian)
	TEST_BIG_ENDIAN(HAVE_BIG_ENDIAN)
	if(HAVE_BIG_ENDIAN)
		add_definitions(-DHAVE_BIG_ENDIAN)
	else()
		add_definitions(-DHAVE_LITTLE_ENDIAN)
	endif()

	if(CMAKE_C_COMPILER_ID MATCHES "MSVC" OR CMAKE_C_COMPILER_ID MATCHES "Clang")
		set(MSVC_DISABLED_WARNINGS_LIST
			"C4018" # 'expression' : signed/unsigned mismatch
			"C4057" # 'operator' : 'identifier1' indirection to
			        # slightly different base types from 'identifier2'
			"C4100" # 'identifier' : unreferenced formal parameter
			"C4127" # conditional expression is constant
			"C4132" # 'object' : const object should be initialized
			"C4146" # unary minus operator applied to unsigned type,
			        # result still unsigned
			"C4206" # nonstandard extension used : translation unit is empty
			"C4244" # 'argument' : conversion from 'type1' to 'type2',
			        # possible loss of data
			"C4245" # 'conversion' : conversion from 'type1' to 'type2',
			        # signed/unsigned mismatch
			"C4267" # 'var' : conversion from 'size_t' to 'type',
			        # possible loss of data
			"C4295" # 'array' : array is too small to include a terminating
			        # null character
			"C4389" # 'operator' : signed/unsigned mismatch
			"C4706" # assignment within conditional expression
			"C4996" # The POSIX name for this item is deprecated.
			        # Instead, use the ISO C and C++ conformant name
		)
	elseif(CMAKE_C_COMPILER_ID MATCHES "Intel")
		add_definitions(-D_CRT_SUPPRESS_RESTRICT)
		set(MSVC_DISABLED_WARNINGS_LIST
			"C111"  # Unreachable statement
			"C128"  # Unreachable loop
			"C167"  # Unexplict casting unsigned to signed
			"C186"  # Pointless comparison of unsigned int with zero
			"C188"  # Enumerated type mixed with another type
			"C344"  # Redeclared type
			"C556"  # Unexplict casting signed to unsigned
			"C869"  # Unreferenced parameters
			"C1786" # Deprecated functions
			"C2545" # Empty else statement
			"C2557" # Comparing signed to unsigned
			"C2722" # List init syntax is c++11 feature
			"C3280" # Declaration hides variable
		)
	endif()
	string(REPLACE "C" " -wd" MSVC_DISABLED_WARNINGS_STR
		${MSVC_DISABLED_WARNINGS_LIST})
	string(REGEX REPLACE "[/-]W[1234][ ]?" "" CMAKE_C_FLAGS ${CMAKE_C_FLAGS})
	set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -MP -W4 ${MSVC_DISABLED_WARNINGS_STR}")
else()
	set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall")
endif()

# XXX - needs _GNU_SOURCE on linux
check_function_exists(asprintf HAVE_ASPRINTF)
if(HAVE_ASPRINTF)
	add_definitions(-DHAVE_ASPRINTF)
endif()

check_symbol_exists(ftruncate "unistd.h" HAVE_FTRUNCATE)
if(HAVE_FTRUNCATE)
	add_definitions(-DHAVE_FTRUNCATE)
endif()

check_symbol_exists(getdelim "stdio.h" HAVE_GETDELIM)
if(HAVE_GETDELIM)
	add_definitions(-DHAVE_GETDELIM)
endif()

check_symbol_exists(getline "stdio.h" HAVE_GETLINE)
if(HAVE_GETLINE)
	add_definitions(-DHAVE_GETLINE)
endif()

check_symbol_exists(getopt "unistd.h" HAVE_GETOPT)
if(HAVE_GETOPT)
	add_definitions(-DHAVE_GETOPT)
endif()

check_symbol_exists(reallocarray "stdlib.h" HAVE_REALLOCARRAY)
if(HAVE_REALLOCARRAY)
	add_definitions(-DHAVE_REALLOCARRAY)
endif()

# XXX strcasecmp() is in strings.h which isn't available everywhere
check_function_exists(strcasecmp HAVE_STRCASECMP)
if(HAVE_STRCASECMP)
	add_definitions(-DHAVE_STRCASECMP)
endif()

# Emscripten's strlcat and strlcpy triggers ASAN errors
if(NOT EMSCRIPTEN)
	check_symbol_exists(strlcat "string.h" HAVE_STRLCAT)
	if(HAVE_STRLCAT)
		add_definitions(-DHAVE_STRLCAT)
	endif()

	check_symbol_exists(strlcpy "string.h" HAVE_STRLCPY)
	if(HAVE_STRLCPY)
		add_definitions(-DHAVE_STRLCPY)
	endif()
endif()

check_symbol_exists(strndup "string.h" HAVE_STRNDUP)
if(HAVE_STRNDUP)
	add_definitions(-DHAVE_STRNDUP)
endif()

if(WIN32)
	set(HAVE_STRNLEN true)
	add_definitions(-DHAVE_STRNLEN)
else()
	check_symbol_exists(strnlen "string.h" HAVE_STRNLEN)
	if(HAVE_STRNLEN)
		add_definitions(-DHAVE_STRNLEN)
	endif()
endif()

check_symbol_exists(strsep "string.h" HAVE_STRSEP)
if(HAVE_STRSEP)
	add_definitions(-DHAVE_STRSEP)
endif()

check_symbol_exists(strtonum "stdlib.h" HAVE_STRTONUM)
if(HAVE_STRTONUM)
	add_definitions(-DHAVE_STRTONUM)
endif()

#
# arc4random on these platform versions falls back to a weak seed when it
# cannot open /dev/random, so the builtin one is used there instead. Same
# versions as the USE_BUILTIN_ARC4RANDOM checks in m4/check-os-options.m4.
#
set(USE_BUILTIN_ARC4RANDOM FALSE)
if(APPLE)
	# getentropy(2) arrived in 10.12 but is not tagged as introduced
	# there, so the deployment target has to be tested directly.
	check_c_source_compiles("
		#include <AvailabilityMacros.h>
		#include <unistd.h>
		#include <sys/random.h>
		#ifndef MAC_OS_X_VERSION_10_12
		#define MAC_OS_X_VERSION_10_12 101200
		#endif
		#if defined(MAC_OS_X_VERSION_MIN_REQUIRED)
		#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_12
		#error \"Targeting Mac OS X 10.11 or earlier\"
		#endif
		#endif
		int main(void) { char buf[1]; return getentropy(buf, 1); }"
		HAVE_MACOS_GETENTROPY)
	if(NOT HAVE_MACOS_GETENTROPY)
		set(USE_BUILTIN_ARC4RANDOM TRUE)
	endif()
elseif(CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
	check_c_source_compiles("
		#include <sys/param.h>
		#if __FreeBSD_version < 1200000
		#error \"FreeBSD 11 or earlier\"
		#endif
		int main(void) { return 0; }"
		HAVE_FREEBSD_ARC4RANDOM)
	if(NOT HAVE_FREEBSD_ARC4RANDOM)
		set(USE_BUILTIN_ARC4RANDOM TRUE)
	endif()
elseif(CMAKE_SYSTEM_NAME MATCHES "NetBSD")
	check_c_source_compiles("
		#include <sys/param.h>
		#if __NetBSD_Version__ < 700000001
		#error \"NetBSD 6 or earlier\"
		#endif
		int main(void) { return 0; }"
		HAVE_NETBSD_ARC4RANDOM)
	if(NOT HAVE_NETBSD_ARC4RANDOM)
		set(USE_BUILTIN_ARC4RANDOM TRUE)
	endif()
endif()

check_symbol_exists(arc4random_buf "stdlib.h" HAVE_ARC4RANDOM_BUF)
if(USE_BUILTIN_ARC4RANDOM)
	set(HAVE_ARC4RANDOM_BUF FALSE)
endif()
if(HAVE_ARC4RANDOM_BUF)
	add_definitions(-DHAVE_ARC4RANDOM_BUF)
endif()

check_symbol_exists(arc4random_uniform "stdlib.h" HAVE_ARC4RANDOM_UNIFORM)
if(HAVE_ARC4RANDOM_UNIFORM)
	add_definitions(-DHAVE_ARC4RANDOM_UNIFORM)
endif()

check_symbol_exists(explicit_bzero "string.h" HAVE_EXPLICIT_BZERO)
if(HAVE_EXPLICIT_BZERO)
	add_definitions(-DHAVE_EXPLICIT_BZERO)
endif()

check_symbol_exists(getauxval "sys/auxv.h" HAVE_GETAUXVAL)
if(HAVE_GETAUXVAL)
	add_definitions(-DHAVE_GETAUXVAL)
endif()

# XXX macos fails to find getentropy with check_symbol_exists()
check_function_exists(getentropy HAVE_GETENTROPY)
if(APPLE AND NOT HAVE_MACOS_GETENTROPY)
	# Weakly linked against the SDK, but absent at runtime before 10.12.
	set(HAVE_GETENTROPY FALSE)
endif()
if(HAVE_GETENTROPY)
	add_definitions(-DHAVE_GETENTROPY)
endif()

check_symbol_exists(getpagesize "unistd.h" HAVE_GETPAGESIZE)
if(HAVE_GETPAGESIZE)
	add_definitions(-DHAVE_GETPAGESIZE)
endif()

check_symbol_exists(getprogname "stdlib.h" HAVE_GETPROGNAME)
if(HAVE_GETPROGNAME)
	add_definitions(-DHAVE_GETPROGNAME)
endif()

check_symbol_exists(pledge "unistd.h" HAVE_PLEDGE)
if(HAVE_PLEDGE)
	add_definitions(-DHAVE_PLEDGE)
endif()

check_symbol_exists(unveil "unistd.h" HAVE_UNVEIL)
if(HAVE_UNVEIL)
	add_definitions(-DHAVE_UNVEIL)
endif()

check_symbol_exists(syslog_r "syslog.h;stdarg.h" HAVE_SYSLOG_R)
if(HAVE_SYSLOG_R)
	add_definitions(-DHAVE_SYSLOG_R)
endif()

# XXX - needs _GNU_SOURCE on linux
check_function_exists(syslog HAVE_SYSLOG)
if(HAVE_SYSLOG)
	add_definitions(-DHAVE_SYSLOG)
endif()

check_function_exists(vsyslog HAVE_VSYSLOG)
if(HAVE_VSYSLOG)
	add_definitions(-DHAVE_VSYSLOG)
endif()

check_symbol_exists(timespecsub sys/time.h HAVE_TIMESPECSUB)
if(HAVE_TIMESPECSUB)
	add_definitions(-DHAVE_TIMESPECSUB)
endif()

check_symbol_exists(timingsafe_bcmp "string.h" HAVE_TIMINGSAFE_BCMP)
if(HAVE_TIMINGSAFE_BCMP)
	add_definitions(-DHAVE_TIMINGSAFE_BCMP)
endif()

check_symbol_exists(timingsafe_memcmp "string.h" HAVE_TIMINGSAFE_MEMCMP)
if(HAVE_TIMINGSAFE_MEMCMP)
	add_definitions(-DHAVE_TIMINGSAFE_MEMCMP)
endif()

# XXX - needs _GNU_SOURCE on linux
check_function_exists(memmem HAVE_MEMMEM)
if(HAVE_MEMMEM)
	add_definitions(-DHAVE_MEMMEM)
endif()

check_include_files(endian.h HAVE_ENDIAN_H)
if(HAVE_ENDIAN_H)
	add_definitions(-DHAVE_ENDIAN_H)
endif()

check_include_files(machine/endian.h HAVE_MACHINE_ENDIAN_H)
if(HAVE_MACHINE_ENDIAN_H)
	add_definitions(-DHAVE_MACHINE_ENDIAN_H)
endif()

check_include_files(err.h HAVE_ERR_H)
if(HAVE_ERR_H)
	add_definitions(-DHAVE_ERR_H)
endif()

check_include_files("sys/types.h;arpa/inet.h;netinet/ip.h" HAVE_NETINET_IP_H)
if(HAVE_NETINET_IP_H)
	add_definitions(-DHAVE_NETINET_IP_H)
endif()

check_include_files(resolv.h HAVE_RESOLV_H)
if(HAVE_RESOLV_H)
	add_definitions(-DHAVE_RESOLV_H)
endif()

check_include_files(arpa/nameser.h HAVE_ARPA_NAMESER_H)
if(HAVE_ARPA_NAMESER_H)
	add_definitions(-DHAVE_ARPA_NAMESER_H)
endif()

# This isn't ready for universal binaries yet, since we do conditional
# compilation based on the architecture, but this makes cross compiling for a
# single architecture work on macOS at least.
#
# Don't set CMAKE_OSX_ARCHITECTURES to more than a single value for now.
if(APPLE AND (NOT CMAKE_OSX_ARCHITECTURES STREQUAL ""))
	set(CMAKE_SYSTEM_PROCESSOR "${CMAKE_OSX_ARCHITECTURES}")
endif()

# CMAKE_SYSTEM_PROCESSOR is not consistently set to the target architecture.
# https://cmake.org/cmake/help/latest/variable/CMAKE_SYSTEM_PROCESSOR.html
if (WIN32 AND (NOT CMAKE_GENERATOR_PLATFORM STREQUAL ""))
	message("CMAKE_SYSTEM_PROCESSOR: ${CMAKE_SYSTEM_PROCESSOR}")
	message("CMAKE_GENERATOR_PLATFORM: ${CMAKE_GENERATOR_PLATFORM}")
	set(CMAKE_SYSTEM_PROCESSOR "${CMAKE_GENERATOR_PLATFORM}")
endif()

if(CMAKE_SYSTEM_PROCESSOR MATCHES "(aarch64|arm64|ARM64)")
	set(HOST_AARCH64 true)
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "arm")
	set(HOST_ARM true)
	if(APPLE)
		set(ENABLE_ASM false)
	endif()
elseif(SOLARIS AND CMAKE_SYSTEM_PROCESSOR MATCHES "i386")
	set(HOST_X86_64 true)
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "(x86_64|x64|amd64|AMD64)")
	set(HOST_X86_64 true)
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "(i[3-6]86|[xX]86|Win32)")
	set(ENABLE_ASM false)
	set(HOST_I386 true)
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "loongarch64")
	set(HOST_LOONGARCH64 true)
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "mips64")
	set(HOST_MIPS64 true)
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "mips")
	set(HOST_MIPS true)
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "powerpc")
	set(HOST_POWERPC true)
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "ppc64")
	set(HOST_POWERPC64 true)
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "riscv64")
	set(HOST_RISCV64 true)
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "sparc64")
	set(HOST_SPARC64 true)
else()
	set(ENABLE_ASM false)
endif()

if(MSVC AND HOST_AARCH64 AND NOT CMAKE_CROSSCOMPILING AND
    CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "(aarch64|arm64|ARM64)")
	set(BN_CT_TEST_SOURCE "${CMAKE_CURRENT_BINARY_DIR}/bn_ct_test.c")
	file(WRITE "${BN_CT_TEST_SOURCE}" [=[
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>

typedef uint64_t BN_ULONG;

#define BN_BITS2 64

static __inline int
bn_ct_ne_zero(BN_ULONG w)
{
	return (w | ~(w - 1)) >> (BN_BITS2 - 1);
}

static __inline void
bn_addw(BN_ULONG a, BN_ULONG b, BN_ULONG *out_r1, BN_ULONG *out_r0)
{
	BN_ULONG c1, c2, r1, r0;

	c1 = a | b;
	c2 = a & b;
	r0 = a + b;
	r1 = ((c1 & ~r0) | c2) >> (BN_BITS2 - 1);

	*out_r1 = r1;
	*out_r0 = r0;
}

static __inline void
bn_addw_addw(BN_ULONG a, BN_ULONG b, BN_ULONG c, BN_ULONG *out_r1,
    BN_ULONG *out_r0)
{
	BN_ULONG carry, r1, r0;

	bn_addw(a, b, &r1, &r0);
	bn_addw(r0, c, &carry, &r0);
	r1 += carry;

	*out_r1 = r1;
	*out_r0 = r0;
}

static BN_ULONG
make_mask(BN_ULONG a, BN_ULONG b, BN_ULONG c, BN_ULONG borrow)
{
	BN_ULONG carry, ignored;

	bn_addw_addw(a, b, c, &carry, &ignored);

	return bn_ct_ne_zero(carry) - borrow;
}

static volatile BN_ULONG input_a = UINT64_MAX;
static volatile BN_ULONG input_b = 1;
static volatile BN_ULONG input_c = 0;
static volatile BN_ULONG input_borrow = 0;

int
main(void)
{
	BN_ULONG actual, expected = 1;

	actual = make_mask(input_a, input_b, input_c, input_borrow);
	if (actual != expected) {
		printf("FAIL: expected 0x%016" PRIx64 ", got 0x%016" PRIx64 "\n",
		    expected, actual);
		return 1;
	}

	return 0;
}
]=])
	set(BN_CT_SAVED_CONFIGURATION "${CMAKE_TRY_COMPILE_CONFIGURATION}")
	set(CMAKE_TRY_COMPILE_CONFIGURATION Release)
	try_run(BN_CT_RUN_RESULT BN_CT_COMPILED
	    "${CMAKE_CURRENT_BINARY_DIR}"
	    "${BN_CT_TEST_SOURCE}")
	set(CMAKE_TRY_COMPILE_CONFIGURATION "${BN_CT_SAVED_CONFIGURATION}")
	if(BN_CT_COMPILED AND BN_CT_RUN_RESULT EQUAL 0)
		message(STATUS "MSVC ARM64 optimizer check passed; workaround disabled")
		add_definitions(-DMSVC_ARM64_BN_CT_NE_ZERO_WORKS)
	else()
		message(STATUS "MSVC ARM64 optimizer check failed; workaround enabled "
		    "(compiled=${BN_CT_COMPILED}, result=${BN_CT_RUN_RESULT})")
	endif()
endif()

if(ENABLE_ASM)
	if(CMAKE_C_COMPILER_ABI STREQUAL "ELF")
		if(HOST_AARCH64)
			set(HOST_ASM_ELF_AARCH64 true)
		elseif(HOST_X86_64)
			set(HOST_ASM_ELF_X86_64 true)
		elseif(SOLARIS AND HOST_I386)
			set(HOST_ASM_ELF_X86_64 true)
		endif()
		add_definitions(-DHAVE_GNU_STACK)
	elseif(APPLE AND HOST_X86_64)
		set(HOST_ASM_MACOSX_X86_64 true)
	elseif(MSVC AND HOST_X86_64)
		set(HOST_ASM_MASM_X86_64 true)
		ENABLE_LANGUAGE(ASM_MASM)
	elseif(MINGW AND HOST_X86_64)
		set(HOST_ASM_MINGW64_X86_64 true)
	endif()
endif()

if(CMAKE_SYSTEM_NAME MATCHES "Linux")
	# Check if we need -lrt to get clock_gettime on Linux
	check_library_exists(rt clock_gettime "time.h" HAVE_CLOCK_GETTIME)
	if (HAVE_CLOCK_GETTIME)
		set(PLATFORM_LIBS ${PLATFORM_LIBS} rt)
	endif()
else()
	# Otherwise, simply check if it exists
	check_function_exists(clock_gettime HAVE_CLOCK_GETTIME)
endif()
if(HAVE_CLOCK_GETTIME)
	add_definitions(-DHAVE_CLOCK_GETTIME)
endif()

check_type_size(time_t SIZEOF_TIME_T)
if(SIZEOF_TIME_T STREQUAL "4")
	set(SMALL_TIME_T true)
	add_definitions(-DSMALL_TIME_T)
	message(WARNING " ** Warning, this system is unable to represent times past 2038\n"
	                " ** It will behave incorrectly when handling valid RFC5280 dates")
endif()

set(OPENSSL_LIBS ssl crypto ${PLATFORM_LIBS})
set(LIBTLS_LIBS tls ${PLATFORM_LIBS})

# libraries for regression test
if(BUILD_SHARED_LIBS)
	set(OPENSSL_TEST_LIBS ssl-static crypto-static ${PLATFORM_LIBS} compat_obj)
	set(LIBTLS_TEST_LIBS tls-static ${OPENSSL_TEST_LIBS} tls_compat_obj)
else()
	set(OPENSSL_TEST_LIBS ssl crypto ${PLATFORM_LIBS} compat_obj)
	set(LIBTLS_TEST_LIBS tls ${PLATFORM_LIBS} compat_obj tls_compat_obj)
endif()

if(OPENSSLDIR STREQUAL "")
	if(WIN32)
		set(OPENSSLDIR "C:/Windows/libressl/ssl")
	else()
		set(OPENSSLDIR "${CMAKE_INSTALL_FULL_SYSCONFDIR}/ssl")
	endif()

	set(CONF_DIR "${CMAKE_INSTALL_SYSCONFDIR}/ssl")
else()
	set(CONF_DIR "${OPENSSLDIR}")
endif()

if(TLS_DEFAULT_CA_FILE STREQUAL "")
	set(TLS_DEFAULT_CA_FILE "${OPENSSLDIR}/cert.pem")
endif()

add_subdirectory(include)
add_subdirectory(crypto)
add_subdirectory(ssl)
if(LIBRESSL_APPS)
	add_subdirectory(apps)
endif()
add_subdirectory(tls)
if(NOT MSVC)
	add_subdirectory(man)
endif()
# Tests require the openssl executable and are unavailable when building shared libraries
if(LIBRESSL_APPS AND LIBRESSL_TESTS)
	add_subdirectory(tests)
endif()

if (BUILD_APPLE_XCFRAMEWORK)
	# Create the super library from object libraries
	add_library(LibreSSL_xcframework
			$<TARGET_OBJECTS:crypto_obj> $<TARGET_OBJECTS:tls_obj> $<TARGET_OBJECTS:ssl_obj>)
	set_target_properties(LibreSSL_xcframework PROPERTIES
			OUTPUT_NAME ressl)

	if(ENABLE_LIBRESSL_INSTALL)
		install(TARGETS LibreSSL_xcframework
				LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
	endif(ENABLE_LIBRESSL_INSTALL)
endif(BUILD_APPLE_XCFRAMEWORK)

file(STRINGS "VERSION" VERSION LIMIT_COUNT 1)
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
	"LibreSSLConfigVersion.cmake"
	VERSION "${VERSION}"
	COMPATIBILITY SameMajorVersion
)

set(INCLUDE_DIRECTORY "${CMAKE_BINARY_DIR}/include")
configure_package_config_file(
  "${CMAKE_CURRENT_LIST_DIR}/LibreSSLConfig.cmake.in"
  "${CMAKE_CURRENT_BINARY_DIR}/LibreSSLConfig.cmake"
	PATH_VARS INCLUDE_DIRECTORY
  INSTALL_DESTINATION "${CMAKE_CURRENT_BINARY_DIR}"
	INSTALL_PREFIX "${CMAKE_CURRENT_BINARY_DIR}"
)

if(ENABLE_LIBRESSL_INSTALL)
	set(INCLUDE_DIRECTORY "${CMAKE_INSTALL_INCLUDEDIR}")
	configure_package_config_file(
  	"${CMAKE_CURRENT_LIST_DIR}/LibreSSLConfig.cmake.in"
  	"${CMAKE_CURRENT_BINARY_DIR}/install-config/LibreSSLConfig.cmake"
		PATH_VARS INCLUDE_DIRECTORY
  	INSTALL_DESTINATION "${LIBRESSL_INSTALL_CMAKEDIR}"
	)
	install(FILES
		"${CMAKE_CURRENT_BINARY_DIR}/install-config/LibreSSLConfig.cmake"
		"${CMAKE_CURRENT_BINARY_DIR}/LibreSSLConfigVersion.cmake"
		DESTINATION "${LIBRESSL_INSTALL_CMAKEDIR}"
	)

	if(NOT MSVC)
		# Create pkgconfig files.
		set(prefix      ${CMAKE_INSTALL_PREFIX})
		set(exec_prefix \${prefix})
		if(IS_ABSOLUTE ${CMAKE_INSTALL_LIBDIR})
			set(libdir      ${CMAKE_INSTALL_LIBDIR})
		else()
			set(libdir      \${exec_prefix}/${CMAKE_INSTALL_LIBDIR})
		endif()
		if(IS_ABSOLUTE ${CMAKE_INSTALL_INCLUDEDIR})
			set(includedir  ${CMAKE_INSTALL_INCLUDEDIR})
 		else()
			set(includedir  \${prefix}/${CMAKE_INSTALL_INCLUDEDIR})
 		endif()
		if(PLATFORM_LIBS)
			string(REGEX REPLACE ";" " -l" PLATFORM_LDADD ";${PLATFORM_LIBS}")
		endif()
		file(GLOB       OPENSSL_PKGCONFIGS "*.pc.in")
		foreach(file ${OPENSSL_PKGCONFIGS})
			get_filename_component(filename ${file} NAME)
			string(REPLACE ".in" "" new_file "${filename}")
			configure_file(${filename} pkgconfig/${new_file} @ONLY)
		endforeach()
		install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/pkgconfig
			DESTINATION ${CMAKE_INSTALL_LIBDIR})
	endif()

	# Like the autotools install hook, leave configuration files and trust
	# anchors that already exist alone, so local changes survive a reinstall.
	if(IS_ABSOLUTE "${CONF_DIR}")
		set(CONF_INSTALL_DIR "${CONF_DIR}")
	else()
		set(CONF_INSTALL_DIR "\${CMAKE_INSTALL_PREFIX}/${CONF_DIR}")
	endif()
	foreach(CONF_FILE cert.pem openssl.cnf x509v3.cnf)
		install(CODE "
			if(EXISTS \"\$ENV{DESTDIR}${CONF_INSTALL_DIR}/${CONF_FILE}\")
				message(STATUS \"\$ENV{DESTDIR}${CONF_INSTALL_DIR}/${CONF_FILE} already exists, install will not overwrite\")
			else()
				file(INSTALL \"${CMAKE_CURRENT_SOURCE_DIR}/${CONF_FILE}\"
					DESTINATION \"${CONF_INSTALL_DIR}\")
			endif()")
	endforeach()
	install(DIRECTORY DESTINATION ${CONF_DIR}/certs)

 	if(NOT TARGET uninstall)
		configure_file(
			"${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in"
			"${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake"
			IMMEDIATE @ONLY)

		add_custom_target(uninstall
			COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)
	endif()
endif()
