cmake_minimum_required(VERSION 3.10)
project(Hiawatha VERSION 12.1 LANGUAGES C)

# Compiler
set(CMAKE_C_FLAGS "-O2 -Wall -Wextra ${CMAKE_C_FLAGS} -gdwarf-4 -Wno-array-bounds")
set(CMAKE_BUILD_TYPE "RelWithDebInfo")

# Options
option(ENABLE_CACHE    "Enable cache support in Hiawatha." on)
option(ENABLE_HTTP2    "Enable HTTP/2 support in Hiawatha (requires TLS)." off)
option(ENABLE_MONITOR  "Enable support for the Hiawatha Monitor." off)
option(ENABLE_RPROXY   "Enable reverse proxy support in Hiawatha." on)
option(ENABLE_SANDBOX  "Enable OS sandbox." off)
option(ENABLE_TLS      "Enable TLS (mbed TLS) support in Hiawatha." on)
option(ENABLE_TOMAHAWK "Enable Tomahawk in Hiawatha" off)
option(ENABLE_TOOLKIT  "Enable the URL toolkit in Hiawatha" on)
option(ENABLE_XSLT     "Enable XSLT support in Hiawatha." on)

option(USE_SYSTEM_MBEDTLS "Use the system's mbed TLS (>=3.0.0) library." off)

# Includes
include(CMakeFiles.txt)
include(CheckIncludeFile)
include(CheckIncludeFiles)
include(CheckFunctionExists)
include(CheckLibraryExists)
include(CheckSymbolExists)
include(GNUInstallDirs)
if(ENABLE_XSLT)
	include(FindLibXml2)
	include(FindLibXslt)
	if (NOT LIBXSLT_FOUND)
		message(FATAL_ERROR "The XSLT library (libxslt1-dev) has not been found.")
		return()
	endif()
endif()
if(ENABLE_HTTP2)
	if(NOT ENABLE_TLS)
		message(FATAL_ERROR "HTTP/2 support requires TLS. Enable ENABLE_TLS or disable ENABLE_HTTP2.")
	endif()
	find_library(NGHTTP2_LIBRARY nghttp2)
	find_path(NGHTTP2_INCLUDE_DIR nghttp2/nghttp2.h)
	if(NOT NGHTTP2_LIBRARY OR NOT NGHTTP2_INCLUDE_DIR)
		message(FATAL_ERROR "nghttp2 library not found. Install libnghttp2-dev or disable ENABLE_HTTP2.")
	endif()
	include_directories(${NGHTTP2_INCLUDE_DIR})
endif()
include(FindZLIB)
include(cmake/CopyIfNotExists.cmake)

# Settings
check_function_exists(getloadavg HAVE_GETLOADAVG)
if(HAVE_GETLOADAVG OR EXISTS "/proc/loadavg")
	option(ENABLE_LOADCHECK "Enable the ability to check for server load." on)
endif()
set(CONFIG_DIR ${CMAKE_INSTALL_FULL_SYSCONFDIR}/hiawatha CACHE STRING "Configuration directory")
set(LOG_DIR ${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/log/hiawatha CACHE STRING "Log directory")
set(PID_DIR ${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/run CACHE STRING "PID directory")
set(WEBROOT_DIR ${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/www/hiawatha CACHE STRING "Webroot directory")
set(WORK_DIR ${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/lib/hiawatha CACHE STRING "Work directory")

# Compiler directives
check_include_file(crypt.h HAVE_CRYPT_H)
check_include_file(arpa/inet.h HAVE_ARPA_INET_H)
check_include_files("sys/types.h;netinet/in.h" HAVE_NETINET_IN_H)
check_include_files("sys/types.h;netinet/tcp.h" HAVE_NETINET_TCP_H)
check_include_file(rpcsvc/crypt.h HAVE_RPCSVC_CRYPT_H)

check_function_exists(setenv HAVE_SETENV)
check_function_exists(unsetenv HAVE_UNSETENV)
check_function_exists(clearenv HAVE_CLEARENV)
check_function_exists(strcasecmp HAVE_STRCASECMP)
check_function_exists(strncasecmp HAVE_STRNCASECMP)
check_function_exists(strnstr HAVE_STRNSTR)
check_function_exists(strcasestr HAVE_STRCASESTR)
check_function_exists(strncasestr HAVE_STRNCASESTR)
check_function_exists(strlcpy HAVE_STRLCPY)

check_library_exists(crypt crypt_r "" HAVE_CRYPT_R)
check_library_exists(crypt crypt "" HAVE_CRYPT_LIBRARY)
check_library_exists(${ZLIB_LIBRARIES} gzdopen "" HAVE_Z_LIBRARY)

check_symbol_exists("SO_ACCEPTFILTER" "sys/socket.h" HAVE_ACCF)

if(HAVE_CRYPT_LIBRARY)
	set(CRYPT_LIBRARY "crypt")
endif()
if(HAVE_Z_LIBRARY)
	set(Z_LIBRARY ${ZLIB_LIBRARIES})
	include_directories(${ZLIB_INCLUDE_DIR})
endif()
if(APPLE OR CYGWIN)
	set(CIFS 1)
endif()

# CPack
set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR})
set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR})
set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH})
set(CPACK_SOURCE_GENERATOR "TGZ")
string(TOLOWER ${PROJECT_NAME} PROJECT_NAME)
set(CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}")
set(CPACK_SOURCE_IGNORE_FILES "/build(/|_.*/)")
include(CPack)

# mbed TLS / PSA crypto
if(NOT USE_SYSTEM_MBEDTLS)
	set(CMAKE_INSTALL_NAME_DIR ${CMAKE_INSTALL_FULL_LIBDIR}/hiawatha)
	set(CMAKE_INSTALL_LIBDIR ${CMAKE_INSTALL_LIBDIR}/hiawatha)
	if (APPLE)
		set(CMAKE_MACOSX_RPATH ${CMAKE_MACOSX_RPATH};${CMAKE_INSTALL_FULL_LIBDIR}/hiawatha)
	endif()
	option(USE_STATIC_MBEDTLS_LIBRARY "Build mbed TLS static library." off)
	option(USE_SHARED_MBEDTLS_LIBRARY "Build mbed TLS shared library." on)
	option(LINK_WITH_PTHREAD "Explicitly link mbed TLS library to pthread." on)
	cmake_policy(SET CMP0048 NEW)
	set(ENABLE_TESTING OFF CACHE BOOL "Build mbed TLS tests." FORCE)
	set(ENABLE_PROGRAMS OFF CACHE BOOL "Build mbed TLS programs." FORCE)
	set(MBEDTLS_FATAL_WARNINGS OFF CACHE BOOL "Disable -Werror in mbed TLS (GCC 14 false positives)." FORCE)
	option(INSTALL_MBEDTLS_HEADERS "Install Mbed TLS headers." OFF)
	option(INSTALL_TF_PSA_CRYPTO_HEADERS "Install TF PSA Crypto headers." OFF)
	if(ENABLE_TLS)
		add_subdirectory(mbedtls)
	else()
		add_subdirectory(mbedtls/tf-psa-crypto)
	endif()
endif()

if(ENABLE_TLS)
	if(USE_SYSTEM_MBEDTLS)
		message(WARNING "Make sure the mbed TLS shared library has been built with the MBEDTLS_THREADING_PTHREAD and MBEDTLS_THREADING_C flags set. Otherwise, it may crash the Hiawatha webserver.")
		find_library(MBEDTLS mbedtls)
	endif()
	set(MBEDTLS_LIBRARIES "mbedtls" "mbedx509" "tfpsacrypto")
else()
	set(MBEDTLS_LIBRARIES "tfpsacrypto")
endif()

# Hiawatha
include_directories(${CMAKE_CURRENT_BINARY_DIR})
if(NOT USE_SYSTEM_MBEDTLS)
	if(ENABLE_TLS)
		include_directories(mbedtls/include)
	endif()
	include_directories(mbedtls/tf-psa-crypto/include)
	include_directories(mbedtls/tf-psa-crypto/drivers/builtin/include)
endif()
if(ENABLE_XSLT)
	include_directories(${LIBXML2_INCLUDE_DIR} ${LIBXSLT_INCLUDE_DIR})
endif()

# Configure files
configure_file(config.h.in config.h)
foreach (configfile ${config_files_in})
	configure_file(${configfile}.in ${configfile} @ONLY)
endforeach()
foreach(manpage ${manual_pages_in})
	configure_file(${manpage}.in ${manpage})
endforeach()
configure_file(extra/logrotate.in logrotate.d/hiawatha)

# Binaries
add_executable(cgi-wrapper ${cgi_wrapper_src})
add_executable(hiawatha ${hiawatha_src})
add_executable(wigwam ${wigwam_src})
target_link_libraries(cgi-wrapper ${Z_LIBRARY})
target_link_libraries(hiawatha ${CRYPT_LIBRARY} pthread ${Z_LIBRARY})
target_link_libraries(wigwam ${CRYPT_LIBRARY} ${Z_LIBRARY})
target_link_libraries(hiawatha ${MBEDTLS_LIBRARIES})
target_link_libraries(wigwam ${MBEDTLS_LIBRARIES})
if(ENABLE_HTTP2)
	target_link_libraries(hiawatha ${NGHTTP2_LIBRARY})
endif()
if(NOT USE_SYSTEM_MBEDTLS)
	set_target_properties(hiawatha PROPERTIES INSTALL_RPATH ${CMAKE_INSTALL_FULL_LIBDIR}/hiawatha)
	set_target_properties(wigwam PROPERTIES INSTALL_RPATH ${CMAKE_INSTALL_FULL_LIBDIR}/hiawatha)
endif()
if(ENABLE_XSLT)
	target_link_libraries(hiawatha ${LIBXML2_LIBRARIES} ${LIBXSLT_LIBRARIES})
endif()

# Installation
install(TARGETS hiawatha wigwam DESTINATION ${CMAKE_INSTALL_SBINDIR})
install(TARGETS cgi-wrapper DESTINATION ${CMAKE_INSTALL_SBINDIR}
	PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE SETUID)

foreach(configfile ${config_files})
	install(CODE "copy_if_not_exists(\"${CMAKE_SOURCE_DIR}/${configfile}\" \"${CONFIG_DIR}\")")
endforeach()
foreach(configfile ${config_files_in})
	install(CODE "copy_if_not_exists(\"${CMAKE_CURRENT_BINARY_DIR}/${configfile}\" \"${CONFIG_DIR}\")")
endforeach()

install(FILES ${manual_pages} DESTINATION ${CMAKE_INSTALL_FULL_MANDIR}/man1)
foreach(manpage ${manual_pages_in})
	install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${manpage} DESTINATION ${CMAKE_INSTALL_FULL_MANDIR}/man1)
endforeach()

install(FILES extra/index.html DESTINATION ${WEBROOT_DIR})

if(ENABLE_TLS AND NOT CYGWIN)
	configure_file(extra/letsencrypt/lefh.in lefh)
	install(FILES ${PROJECT_BINARY_DIR}/lefh DESTINATION ${CMAKE_INSTALL_SBINDIR}
		PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
	install(DIRECTORY extra/letsencrypt/letsencrypt DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}/hiawatha)
	configure_file(extra/letsencrypt/letsencrypt.conf.in letsencrypt.conf)
	install(FILES ${PROJECT_BINARY_DIR}/letsencrypt.conf DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}/hiawatha/letsencrypt)
	install(FILES man/lefh.1 DESTINATION ${CMAKE_INSTALL_FULL_MANDIR}/man1)
endif()

# Create directories
install(DIRECTORY DESTINATION ${LOG_DIR})
install(DIRECTORY DESTINATION ${PID_DIR})
install(DIRECTORY DESTINATION ${WORK_DIR})
