# Copyright (c) 2026, PhoenixDKIM contributors. All rights reserved.

include(GNUInstallDirs)
include(CheckFunctionExists)
include(CheckIncludeFile)

# ── OpenSSL 3+ ────────────────────────────────────────────────────────────────
# Do not pass a version to find_package: OpenSSL 4's config file marks itself
# as incompatible with any major-version < 4 request, causing CMake to silently
# fall back to system OpenSSL.  Enforce the minimum here instead.

find_package(OpenSSL REQUIRED)
if(OPENSSL_VERSION VERSION_LESS "3.0")
    message(FATAL_ERROR "OpenSSL 3.0 or newer required; found ${OPENSSL_VERSION}")
endif()

# ── LMDB ──────────────────────────────────────────────────────────────────────
# find_library/find_path calls return cached results from the libopendkim pass.
# The PkgConfig::LMDB imported target is global if pkg-config found it there.

find_package(PkgConfig QUIET)

if(TARGET PkgConfig::LMDB)
    set(LMDB_TARGET PkgConfig::LMDB)
else()
    find_library(LMDB_LIBRARY NAMES lmdb)
    find_path(LMDB_INCLUDE_DIR NAMES lmdb.h)
    if(LMDB_LIBRARY AND LMDB_INCLUDE_DIR)
        if(NOT TARGET lmdb_imported)
            add_library(lmdb_imported UNKNOWN IMPORTED)
            set_target_properties(lmdb_imported PROPERTIES
                IMPORTED_LOCATION "${LMDB_LIBRARY}"
                INTERFACE_INCLUDE_DIRECTORIES "${LMDB_INCLUDE_DIR}"
            )
        endif()
        set(LMDB_TARGET lmdb_imported)
    else()
        message(FATAL_ERROR "LMDB not found. Install liblmdb-dev / lmdb-devel.")
    endif()
endif()

# ── libmilter (required for daemon) ──────────────────────────────────────────

find_library(MILTER_LIB NAMES milter)
if(NOT MILTER_LIB)
    message(FATAL_ERROR "libmilter not found. Install libmilter-dev / sendmail-devel.")
endif()

find_path(MILTER_INCLUDE_DIR NAMES libmilter/mfapi.h)

# ── Lua 5.4 (optional) ────────────────────────────────────────────────────────

option(WITH_LUA "Build with Lua 5.4 scripting support" OFF)

if(WITH_LUA)
    find_package(Lua 5.4 EXACT REQUIRED)
    set(USE_LUA 1)
endif()

# ── Redis / Valkey — hiredis or libvalkey (optional) ─────────────────────────

option(WITH_REDIS "Build with Redis/Valkey (hiredis or libvalkey) backend support" OFF)

if(WITH_REDIS)
    find_library(HIREDIS_LIBRARY NAMES hiredis valkey)
    find_path(HIREDIS_INCLUDE_DIR NAMES hiredis/hiredis.h valkey/valkey.h)
    if(NOT HIREDIS_LIBRARY OR NOT HIREDIS_INCLUDE_DIR)
        message(FATAL_ERROR
            "hiredis or libvalkey not found. "
            "Install libhiredis-dev / hiredis-devel, or libvalkey-dev / valkey-devel.")
    endif()
    if(EXISTS "${HIREDIS_INCLUDE_DIR}/valkey/valkey.h" AND
       NOT EXISTS "${HIREDIS_INCLUDE_DIR}/hiredis/hiredis.h")
        set(REDIS_IMPL_VALKEY TRUE)
        message(STATUS "Redis backend: using libvalkey")
    else()
        set(REDIS_IMPL_VALKEY FALSE)
        message(STATUS "Redis backend: using hiredis")
    endif()
    set(WITH_REDIS 1)
endif()

# ── libcurl (optional, default OFF) ───────────────────────────────────────────
# Enables SMTP report delivery via the SMTPURI config option.

option(WITH_CURL "Enable libcurl SMTP report delivery (SMTPURI config option)" OFF)

if(WITH_CURL)
    find_package(CURL REQUIRED)
    if(CURL_VERSION_STRING VERSION_LESS "7.20.0")
        message(FATAL_ERROR
            "libcurl >= 7.20.0 required (CURLOPT_MAIL_FROM/MAIL_RCPT); "
            "found ${CURL_VERSION_STRING}")
    endif()
    set(HAVE_LIBCURL 1)
endif()

# ── systemd sd_notify (optional, auto-detected) ───────────────────────────────
# Enables Type=notify readiness signalling and the watchdog keep-alive.  The
# integration degrades gracefully: when libsystemd is absent the daemon simply
# builds without it, so this defaults ON.

option(WITH_SYSTEMD "Enable systemd sd_notify readiness/watchdog integration" ON)

if(WITH_SYSTEMD)
    pkg_check_modules(LIBSYSTEMD QUIET libsystemd)
    if(LIBSYSTEMD_FOUND)
        set(HAVE_LIBSYSTEMD 1)
        message(STATUS "systemd integration: enabled (libsystemd ${LIBSYSTEMD_VERSION})")
    else()
        message(STATUS "systemd integration: libsystemd not found, disabled")
    endif()
endif()

# ── Version string + build configuration for -V / -v diagnostics ─────────────
# DKIMF_VERSION reports the project version, plus the git commit on untagged
# development builds (issue #350).

set(DKIMF_VERSION "${PROJECT_VERSION}")

find_program(GIT_EXECUTABLE git)
if(GIT_EXECUTABLE AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
    execute_process(
        COMMAND ${GIT_EXECUTABLE} rev-parse --short=12 HEAD
        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
        OUTPUT_VARIABLE _git_hash
        OUTPUT_STRIP_TRAILING_WHITESPACE
        ERROR_QUIET
        RESULT_VARIABLE _git_rc
    )
    if(_git_rc EQUAL 0 AND _git_hash)
        execute_process(
            COMMAND ${GIT_EXECUTABLE} status --porcelain --untracked-files=no
            WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
            OUTPUT_VARIABLE _git_dirty
            OUTPUT_STRIP_TRAILING_WHITESPACE
            ERROR_QUIET
        )
        if(_git_dirty)
            set(_git_hash "${_git_hash}-dirty")
        endif()
        set(DKIMF_VERSION "${PROJECT_VERSION}-g${_git_hash}")
    endif()
    # Refresh the version header automatically when HEAD or the index moves,
    # so a rebuild after a commit reports the new hash without reconfiguring.
    foreach(_gitfile HEAD index)
        if(EXISTS "${CMAKE_SOURCE_DIR}/.git/${_gitfile}")
            set_property(DIRECTORY APPEND PROPERTY
                         CMAKE_CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/.git/${_gitfile}")
        endif()
    endforeach()
endif()

# CONFIGURE_ARGS records the build options needed to reproduce this build
# (issue #357).  CMake has no literal equivalent of autoconf's
# $ac_configure_args, so reconstruct the meaningful -D flags from the resolved
# option values.  Normalise each toggle to ON/OFF: a value passed as
# -DWITH_X=1 is stored verbatim as "1", while an option left at its default
# carries "ON"/"OFF", so emit a consistent boolean rather than the raw value.
set(CONFIGURE_ARGS
    "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}")
foreach(_opt WITH_LUA WITH_REDIS WITH_CURL WITH_SYSTEMD WITH_UNBOUND)
    if(${_opt})
        string(APPEND CONFIGURE_ARGS " -D${_opt}=ON")
    else()
        string(APPEND CONFIGURE_ARGS " -D${_opt}=OFF")
    endif()
endforeach()

configure_file(
    ${CMAKE_CURRENT_SOURCE_DIR}/build-version.h.cmake.in
    ${CMAKE_CURRENT_BINARY_DIR}/build-version.h
    @ONLY
)

# ── pthreads ──────────────────────────────────────────────────────────────────

find_package(Threads REQUIRED)

# ── libresolv ─────────────────────────────────────────────────────────────────

find_library(RESOLV_LIBRARY NAMES resolv)
find_library(UNBOUND_LIBRARY NAMES unbound)

# ── strlcpy / strlcat ─────────────────────────────────────────────────────────

check_function_exists(strlcpy HAVE_STRLCPY_IN_LIBC)
check_function_exists(strlcat HAVE_STRLCAT_IN_LIBC)

set(STRL_LIBRARY "")
set(USE_BSD_H FALSE)

if(NOT (HAVE_STRLCPY_IN_LIBC AND HAVE_STRLCAT_IN_LIBC))
    find_library(BSD_LIBRARY NAMES bsd)
    if(BSD_LIBRARY)
        check_include_file("bsd/string.h" HAVE_BSD_STRING_H)
        if(HAVE_BSD_STRING_H)
            set(STRL_LIBRARY "${BSD_LIBRARY}")
            set(USE_BSD_H TRUE)
        endif()
    endif()
endif()

# ── Common configuration for daemon-side targets ──────────────────────────────
# Mirrors configure_opendkim_target() from libopendkim/CMakeLists.txt.

function(configure_daemon_target tgt)
    target_include_directories(${tgt} PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}
        ${CMAKE_CURRENT_BINARY_DIR}        # build-version.h
        ${CMAKE_BINARY_DIR}/libopendkim
    )

    target_compile_definitions(${tgt} PRIVATE
        HAVE_SHA256=1
        USE_MDB=1
    )

    if(HAVE_RES_NINIT)
        target_compile_definitions(${tgt} PRIVATE HAVE_RES_NINIT=1)
    endif()
    if(HAVE_RES_SETSERVERS)
        target_compile_definitions(${tgt} PRIVATE HAVE_RES_SETSERVERS=1)
    endif()
    if(HAVE_LIMITS_H)
        target_compile_definitions(${tgt} PRIVATE HAVE_LIMITS_H=1)
    endif()
    if(HAVE_STDBOOL_H)
        target_compile_definitions(${tgt} PRIVATE HAVE_STDBOOL_H=1)
    endif()
    if(USE_BSD_H)
        target_compile_definitions(${tgt} PRIVATE USE_BSD_H=1)
    endif()

    target_link_libraries(${tgt} PRIVATE
        opendkim
        OpenSSL::Crypto
        ${LMDB_TARGET}
        Threads::Threads
    )

    if(RESOLV_LIBRARY)
        target_link_libraries(${tgt} PRIVATE ${RESOLV_LIBRARY})
    endif()
    if(UNBOUND_LIBRARY)
        target_link_libraries(${tgt} PRIVATE ${UNBOUND_LIBRARY})
    endif()

    if(STRL_LIBRARY)
        target_link_libraries(${tgt} PRIVATE ${STRL_LIBRARY})
    endif()

    if(WITH_LUA)
        target_include_directories(${tgt} PRIVATE ${LUA_INCLUDE_DIR})
        target_link_libraries(${tgt} PRIVATE ${LUA_LIBRARIES})
    endif()

    if(WITH_REDIS)
        target_compile_definitions(${tgt} PRIVATE USE_REDIS=1)
        if(REDIS_IMPL_VALKEY)
            target_compile_definitions(${tgt} PRIVATE USE_LIBVALKEY=1)
        endif()
        target_include_directories(${tgt} PRIVATE ${HIREDIS_INCLUDE_DIR})
        target_link_libraries(${tgt} PRIVATE ${HIREDIS_LIBRARY})
    endif()

    if(WITH_CURL AND HAVE_LIBCURL)
        target_compile_definitions(${tgt} PRIVATE HAVE_LIBCURL=1)
        target_link_libraries(${tgt} PRIVATE CURL::libcurl)
    endif()
endfunction()

# ── opendkim daemon ───────────────────────────────────────────────────────────
# Source list from opendkim/Makefile.am opendkim_SOURCES.
#
# CMake target name is opendkim_daemon because the shared library in
# libopendkim/ already owns the "opendkim" target name.  OUTPUT_NAME
# ensures the installed binary is still called "opendkim".

add_executable(opendkim_daemon
    opendkim.c
    opendkim-ar.c
    opendkim-arf.c
    opendkim-crypto.c
    opendkim-db.c
    opendkim-dns.c
    opendkim-lua.c
    config.c
    test.c
    util.c
)

set_target_properties(opendkim_daemon PROPERTIES OUTPUT_NAME opendkim)

configure_daemon_target(opendkim_daemon)
apply_hardening(opendkim_daemon)
apply_sanitizers(opendkim_daemon)

target_link_libraries(opendkim_daemon PRIVATE ${MILTER_LIB})

# systemd sd_notify is daemon-only; the helper binaries never talk to an init
# manager, so the linkage is attached here rather than in configure_daemon_target().
if(HAVE_LIBSYSTEMD)
    target_compile_definitions(opendkim_daemon PRIVATE HAVE_LIBSYSTEMD=1)
    target_include_directories(opendkim_daemon PRIVATE ${LIBSYSTEMD_INCLUDE_DIRS})
    target_link_libraries(opendkim_daemon PRIVATE ${LIBSYSTEMD_LIBRARIES})
endif()

if(WITH_LUA)
    target_compile_definitions(opendkim_daemon PRIVATE DKIMF_LUA_CONTEXT_HOOKS=1)
    if(MILTER_INCLUDE_DIR)
        target_include_directories(opendkim_daemon PRIVATE ${MILTER_INCLUDE_DIR})
    endif()
endif()

install(TARGETS opendkim_daemon
    RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}
)

# ── opendkim-testkey ──────────────────────────────────────────────────────────
# Source list from opendkim/Makefile.am opendkim_testkey_SOURCES.

add_executable(opendkim-testkey
    config.c
    opendkim-crypto.c
    opendkim-db.c
    opendkim-dns.c
    opendkim-lua.c
    opendkim-testkey.c
    util.c
)

configure_daemon_target(opendkim-testkey)
apply_hardening(opendkim-testkey)
apply_sanitizers(opendkim-testkey)

if(WITH_LUA AND MILTER_INCLUDE_DIR)
    target_include_directories(opendkim-testkey PRIVATE ${MILTER_INCLUDE_DIR})
endif()

install(TARGETS opendkim-testkey
    RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}
)

# ── opendkim-genzone ──────────────────────────────────────────────────────────
# Source list from opendkim/Makefile.am opendkim_genzone_SOURCES.

add_executable(opendkim-genzone
    config.c
    opendkim-db.c
    opendkim-genzone.c
    opendkim-lua.c
    util.c
)

configure_daemon_target(opendkim-genzone)
apply_hardening(opendkim-genzone)
apply_sanitizers(opendkim-genzone)

install(TARGETS opendkim-genzone
    RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}
)

# ── opendkim-testmsg ──────────────────────────────────────────────────────────
# Source list from opendkim/Makefile.am opendkim_testmsg_SOURCES.

add_executable(opendkim-testmsg
    opendkim-testmsg.c
)

configure_daemon_target(opendkim-testmsg)
apply_hardening(opendkim-testmsg)
apply_sanitizers(opendkim-testmsg)

install(TARGETS opendkim-testmsg
    RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}
)

# ── opendkim-genkey (Perl script) ─────────────────────────────────────────────
# Listed in Makefile.am as dist_sbin_SCRIPTS — it is a Perl script generated
# from opendkim-genkey.in via autoconf-style @VAR@ substitution.
# configure_file runs at cmake configure time; the custom target makes
# "make opendkim-genkey" a valid build target.

set(DOMAIN "" CACHE STRING "Default domain for opendkim-genkey")
set(VERSION "${PROJECT_VERSION}")

configure_file(
    opendkim-genkey.in
    ${CMAKE_CURRENT_BINARY_DIR}/opendkim-genkey
    @ONLY
)

add_custom_target(opendkim-genkey
    COMMENT "opendkim-genkey Perl script (generated at configure time)"
)

install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/opendkim-genkey
    DESTINATION ${CMAKE_INSTALL_SBINDIR}
)

# ── Man pages ─────────────────────────────────────────────────────────────────
include(GNUInstallDirs)
install(FILES
    opendkim.8
    opendkim-genkey.8
    opendkim-genzone.8
    opendkim-testkey.8
    opendkim-testmsg.8
    DESTINATION ${CMAKE_INSTALL_MANDIR}/man8
)
install(FILES opendkim.conf.5 DESTINATION ${CMAKE_INSTALL_MANDIR}/man5)
install(FILES opendkim-lua.3 DESTINATION ${CMAKE_INSTALL_MANDIR}/man3)

# ── Test ──────────────────────────────────────────────────────────────────────

if(WITH_LUA)
    add_subdirectory(tests)
endif()

# ── Redis/Valkey DB unit test ─────────────────────────────────────────────────

if(WITH_REDIS)
    add_executable(t-db-redis
        tests/t-db-redis.c
        opendkim-db.c
        opendkim-lua.c
        util.c
    )
    configure_daemon_target(t-db-redis)
    apply_hardening(t-db-redis)
    apply_sanitizers(t-db-redis)
    add_test(
        NAME t-db-redis
        COMMAND t-db-redis
    )
    set_tests_properties(t-db-redis PROPERTIES
        TIMEOUT 10
        SKIP_RETURN_CODE 77
    )
endif()
