From d5c0a20c8e4be35bc4ca93df7eddb14ac7abe1b5 Mon Sep 17 00:00:00 2001 From: Volodymyr Kolesnykov Date: Thu, 18 Jan 2024 09:17:19 +0200 Subject: [PATCH] feat: add TLS support --- .devcontainer/Dockerfile | 2 +- .dockerignore | 1 + CMakeLists.txt | 2 + Dockerfile | 9 +- Dockerfile.alpine | 4 +- cmake/Findlibada.cmake | 55 ++++++++ cmake/Findlibev.cmake | 68 ++++++++++ cmake/Findlibllhttp.cmake | 63 +++++++++ cmake/Findlibsqlite3.cmake | 58 ++++++++ cmake/Findlibtls.cmake | 52 +++++++ src/CMakeLists.txt | 268 ++++++++++++++++++++++++++----------- src/clienthandler.cpp | 4 +- src/clienthandler.h | 4 +- src/clienthandler_p.cpp | 235 +++++++++++++++++++++++++------- src/clienthandler_p.h | 11 +- src/main.cpp | 26 +++- src/server.cpp | 5 + src/server.h | 3 + src/server_p.cpp | 23 +++- src/server_p.h | 4 + src/serversocket.h | 1 + src/stdafx.h | 1 + src/tlsconfigurator.cpp | 24 ++++ src/tlsconfigurator.h | 28 ++++ src/tlsconfigurator_p.cpp | 123 +++++++++++++++++ src/tlsconfigurator_p.h | 40 ++++++ src/tlsexception.h | 19 +++ src/tlsservercontext.cpp | 58 ++++++++ src/tlsservercontext.h | 43 ++++++ src/tlsservercontext_p.cpp | 85 ++++++++++++ src/tlsservercontext_p.h | 30 +++++ src/tlsutils.cpp | 8 ++ src/tlsutils.h | 8 ++ 33 files changed, 1227 insertions(+), 138 deletions(-) create mode 100644 cmake/Findlibada.cmake create mode 100644 cmake/Findlibev.cmake create mode 100644 cmake/Findlibllhttp.cmake create mode 100644 cmake/Findlibsqlite3.cmake create mode 100644 cmake/Findlibtls.cmake create mode 100644 src/tlsconfigurator.cpp create mode 100644 src/tlsconfigurator.h create mode 100644 src/tlsconfigurator_p.cpp create mode 100644 src/tlsconfigurator_p.h create mode 100644 src/tlsexception.h create mode 100644 src/tlsservercontext.cpp create mode 100644 src/tlsservercontext.h create mode 100644 src/tlsservercontext_p.cpp create mode 100644 src/tlsservercontext_p.h create mode 100644 src/tlsutils.cpp create mode 100644 src/tlsutils.h diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 39c37c9..c3cb5ed 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -25,7 +25,7 @@ RUN \ RUN \ export DEBIAN_FRONTEND=noninteractive && \ apt-get update && \ - apt-get install -y --no-install-recommends nlohmann-json3-dev libsqlite3-dev libev-dev && \ + apt-get install -y --no-install-recommends nlohmann-json3-dev libsqlite3-dev libev-dev libtls-dev libssl-dev && \ rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/* RUN \ diff --git a/.dockerignore b/.dockerignore index 872eada..8c64fcc 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,4 @@ * !CMakeLists.txt +!cmake !src diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c19e4a..2923ab3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,8 @@ cmake_minimum_required(VERSION 3.20) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_VERBOSE_MAKEFILE ON) +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + project(tfhttp VERSION 1.0.0 LANGUAGES CXX C) add_subdirectory(src) diff --git a/Dockerfile b/Dockerfile index 4b55a05..0b02c46 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,19 +2,20 @@ FROM ubuntu:mantic-20231128@sha256:cbc171ba52575fec0601f01abf6fdec67f8ed227658ca ENV DEBIAN_FRONTEND=noninteractive -# Do not install libsqlite3-dev because we want to it without dynamic extension support; we need tcl to build amalgamated version +# Do not install libsqlite3-dev because we want to it without dynamic extension support. It requires tcl. +# Do not install libev-dev because we want to use a custom build. RUN \ apt-get update && \ - apt-get install -y --no-install-recommends ca-certificates cmake git clang make python3-minimal python3-whichcraft nlohmann-json3-dev libev-dev pkgconf tcl && \ + apt-get install -y --no-install-recommends ca-certificates cmake file git clang make nlohmann-json3-dev libtls-dev libssl-dev pkgconf tcl && \ rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/* COPY . /app WORKDIR /app RUN \ - cmake -B build -DCMAKE_BUILD_TYPE=MinSizeRel && \ + cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_STATIC_BINARY=off -DBUILD_MOSTLY_STATIC_BINARY=on -DFORCE_EXTERNAL_LIBEV=on && \ cmake --build build && \ strip --strip-unneeded build/src/tfhttp -FROM scratch +FROM cgr.dev/chainguard/glibc-dynamic:latest@sha256:6895a08124484dcab1daa40861ebe814290e0a48aab71cdd3eccc75de0c045ce COPY --from=build /app/build/src/tfhttp /tfhttp ENTRYPOINT ["/tfhttp"] diff --git a/Dockerfile.alpine b/Dockerfile.alpine index 5d40163..c92c07c 100644 --- a/Dockerfile.alpine +++ b/Dockerfile.alpine @@ -1,12 +1,12 @@ FROM alpine:3.19.0@sha256:51b67269f354137895d43f3b3d810bfacd3945438e94dc5ac55fdac340352f48 AS build -RUN apk add --no-cache cmake make libc-dev clang17 git file libev-dev ada-static ada-dev sqlite-dev sqlite-static sqlite3pp nlohmann-json +RUN apk add --no-cache cmake make libc-dev clang17 git file libev-dev ada-static ada-dev sqlite-dev sqlite-static sqlite3pp nlohmann-json libretls-static libretls-dev openssl-libs-static WORKDIR / COPY . /app WORKDIR /app RUN \ - cmake -B build -DCMAKE_BUILD_TYPE=MinSizeRel && \ + cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_STATIC_BINARY=on && \ cmake --build build && \ strip --strip-unneeded build/src/tfhttp diff --git a/cmake/Findlibada.cmake b/cmake/Findlibada.cmake new file mode 100644 index 0000000..17fd941 --- /dev/null +++ b/cmake/Findlibada.cmake @@ -0,0 +1,55 @@ +# - Try to find libev +# Once done this will define +# LIBADA_FOUND - System has libada +# LIBADA_INCLUDE_DIRS - The libada include directories +# LIBADA_LIBRARIES - The libraries needed to use libada +# LIBADA_STATIC_INCLUDE_DIRS - The libada static include directories +# LIBADA_STATIC_LIBRARIES - The libraries needed to use libada statically + +find_path( + LIBADA_INCLUDE_DIR + NAMES ada.h +) + +set(SAVED_CMAKE_FIND_LIBRARY_SUFFIXES "${CMAKE_FIND_LIBRARY_SUFFIXES}") + set(CMAKE_FIND_LIBRARY_SUFFIXES "${CMAKE_STATIC_LIBRARY_SUFFIX}") + find_library(LIBADA_STATIC_LIBRARY NAMES ada) + set(CMAKE_FIND_LIBRARY_SUFFIXES "${CMAKE_SHARED_LIBRARY_SUFFIX}") + find_library(LIBADA_SHARED_LIBRARY NAMES ada) +set(CMAKE_FIND_LIBRARY_SUFFIXES "${SAVED_CMAKE_FIND_LIBRARY_SUFFIXES}") +unset(SAVED_CMAKE_FIND_LIBRARY_SUFFIXES) + +if(LIBADA_SHARED_LIBRARY) + set(LIBADA_LIBRARY ${LIBADA_SHARED_LIBRARY}) +elseif(LIBADA_STATIC_LIBRARY) + set(LIBADA_LIBRARY ${LIBADA_STATIC_LIBRARY}) +endif() + +if(LIBADA_INCLUDE_DIR) + file(STRINGS "${LIBADA_INCLUDE_DIR}/ada/ada_version.h" LIBADA_VERSION REGEX "^#define[ \t]ADA_VERSION[ \t]\"[^\"]+\"$") + string(REGEX REPLACE "\"([^\"]+)\"" "\\1" LIBADA_VERSION "${LIBADA_VERSION}") +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args( + libada + REQUIRED_VARS LIBADA_LIBRARY LIBADA_INCLUDE_DIR + VERSION_VAR LIBADA_VERSION +) + +if(LIBADA_FOUND) + if(LIBADA_SHARED_LIBRARY) + set(LIBADA_LIBRARIES ${LIBADA_SHARED_LIBRARY}) + else() + set(LIBADA_LIBRARIES ${LIBADA_STATIC_LIBRARY}) + endif() + + set(LIBADA_INCLUDE_DIRS ${LIBADA_INCLUDE_DIR}) + + set(LIBADA_STATIC_INCLUDE_DIRS ${LIBADA_INCLUDE_DIRS}) + if(LIBADA_STATIC_LIBRARY) + set(LIBADA_STATIC_LIBRARIES ${LIBADA_STATIC_LIBRARY}) + endif() +endif() + +mark_as_advanced(LIBADA_INCLUDE_DIR LIBADA_SHARED_LIBRARY LIBADA_STATIC_LIBRARY) diff --git a/cmake/Findlibev.cmake b/cmake/Findlibev.cmake new file mode 100644 index 0000000..cdce48a --- /dev/null +++ b/cmake/Findlibev.cmake @@ -0,0 +1,68 @@ +# - Try to find libev +# Once done this will define +# LIBEV_FOUND - System has libev +# LIBEV_INCLUDE_DIRS - The libev include directories +# LIBEV_LIBRARIES - The libraries needed to use libev +# LIBEV_STATIC_INCLUDE_DIRS - The libev static include directories +# LIBEV_STATIC_LIBRARIES - The libraries needed to use libev statically + +find_path( + LIBEV_INCLUDE_DIR + NAMES ev.h +) + +set(SAVED_CMAKE_FIND_LIBRARY_SUFFIXES "${CMAKE_FIND_LIBRARY_SUFFIXES}") + set(CMAKE_FIND_LIBRARY_SUFFIXES "${CMAKE_STATIC_LIBRARY_SUFFIX}") + find_library(LIBEV_STATIC_LIBRARY NAMES ev) + set(CMAKE_FIND_LIBRARY_SUFFIXES "${CMAKE_SHARED_LIBRARY_SUFFIX}") + find_library(LIBEV_SHARED_LIBRARY NAMES ev) +set(CMAKE_FIND_LIBRARY_SUFFIXES "${SAVED_CMAKE_FIND_LIBRARY_SUFFIXES}") +unset(SAVED_CMAKE_FIND_LIBRARY_SUFFIXES) + +if(LIBEV_SHARED_LIBRARY) + set(LIBEV_LIBRARY ${LIBEV_SHARED_LIBRARY}) +elseif(LIBEV_STATIC_LIBRARY) + set(LIBEV_LIBRARY ${LIBEV_STATIC_LIBRARY}) +endif() + +if(LIBEV_INCLUDE_DIR) + file( + STRINGS "${LIBEV_INCLUDE_DIR}/ev.h" + LIBEV_VERSION_MAJOR REGEX "^#define[ \t]+EV_VERSION_MAJOR[ \t]+[0-9]+" + ) + + file( + STRINGS "${LIBEV_INCLUDE_DIR}/ev.h" + LIBEV_VERSION_MINOR REGEX "^#define[ \t]+EV_VERSION_MINOR[ \t]+[0-9]+" + ) + + string(REGEX REPLACE "[^0-9]+" "" LIBEV_VERSION_MAJOR "${LIBEV_VERSION_MAJOR}") + string(REGEX REPLACE "[^0-9]+" "" LIBEV_VERSION_MINOR "${LIBEV_VERSION_MINOR}") + set(LIBEV_VERSION "${LIBEV_VERSION_MAJOR}.${LIBEV_VERSION_MINOR}") + unset(LIBEV_VERSION_MINOR) + unset(LIBEV_VERSION_MAJOR) +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args( + libev + REQUIRED_VARS LIBEV_LIBRARY LIBEV_INCLUDE_DIR + VERSION_VAR LIBEV_VERSION +) + +if(LIBEV_FOUND) + if(LIBEV_SHARED_LIBRARY) + set(LIBEV_LIBRARIES ${LIBEV_SHARED_LIBRARY}) + else() + set(LIBEV_LIBRARIES ${LIBEV_STATIC_LIBRARY};m) + endif() + + set(LIBEV_INCLUDE_DIRS ${LIBEV_INCLUDE_DIR}) + + set(LIBEV_STATIC_INCLUDE_DIRS ${LIBEV_INCLUDE_DIRS}) + if(LIBEV_STATIC_LIBRARY) + set(LIBEV_STATIC_LIBRARIES ${LIBEV_STATIC_LIBRARY};m) + endif() +endif() + +mark_as_advanced(LIBEV_INCLUDE_DIR LIBEV_SHARED_LIBRARY LIBEV_STATIC_LIBRARY) diff --git a/cmake/Findlibllhttp.cmake b/cmake/Findlibllhttp.cmake new file mode 100644 index 0000000..942f449 --- /dev/null +++ b/cmake/Findlibllhttp.cmake @@ -0,0 +1,63 @@ +# - Try to find libev +# Once done this will define +# LIBLLHTTP_FOUND - System has llhttp +# LIBLLHTTP_INCLUDE_DIRS - The llhttp include directories +# LIBLLHTTP_LIBRARIES - The libraries needed to use llhttp +# LIBLLHTTP_STATIC_INCLUDE_DIRS - The llhttp static include directories +# LIBLLHTTP_STATIC_LIBRARIES - The libraries needed to use llhttp statically + +pkg_check_modules(LIBLLHTTP QUIET llhttp) +if (NOT LIBLLHTTP_FOUND) + find_path( + LIBLLHTTP_INCLUDE_DIR + NAMES llhttp.h + ) + + set(SAVED_CMAKE_FIND_LIBRARY_SUFFIXES "${CMAKE_FIND_LIBRARY_SUFFIXES}") + set(CMAKE_FIND_LIBRARY_SUFFIXES "${CMAKE_STATIC_LIBRARY_SUFFIX}") + find_library(LIBLLHTTP_STATIC_LIBRARY NAMES llhttp) + set(CMAKE_FIND_LIBRARY_SUFFIXES "${CMAKE_SHARED_LIBRARY_SUFFIX}") + find_library(LIBLLHTTP_SHARED_LIBRARY NAMES llhttp) + set(CMAKE_FIND_LIBRARY_SUFFIXES "${SAVED_CMAKE_FIND_LIBRARY_SUFFIXES}") + unset(SAVED_CMAKE_FIND_LIBRARY_SUFFIXES) + + if(LIBLLHTTP_SHARED_LIBRARY) + set(LIBLLHTTP_LIBRARY ${LIBLLHTTP_SHARED_LIBRARY}) + elseif(LIBLLHTTP_STATIC_LIBRARY) + set(LIBLLHTTP_LIBRARY ${LIBLLHTTP_STATIC_LIBRARY}) + endif() + + if(LIBLLHTTP_INCLUDE_DIR) + file(STRINGS "${LIBLLHTTP_INCLUDE_DIR}/llhttp.h" LIBLLHTTP_VERSION_MAJOR REGEX "^#define[ \t]+LLHTTP_VERSION_MAJOR[ \t]+[0-9]+") + file(STRINGS "${LIBLLHTTP_INCLUDE_DIR}/llhttp.h" LIBLLHTTP_VERSION_MINOR REGEX "^#define[ \t]+LLHTTP_VERSION_MINOR[ \t]+[0-9]+") + string(REGEX REPLACE "[^0-9]+" "" LIBLLHTTP_VERSION_MAJOR "${LIBLLHTTP_VERSION_MAJOR}") + string(REGEX REPLACE "[^0-9]+" "" LIBLLHTTP_VERSION_MINOR "${LIBLLHTTP_VERSION_MINOR}") + set(LIBLLHTTP_VERSION "${LIBLLHTTP_VERSION_MAJOR}.${LIBLLHTTP_VERSION_MINOR}") + unset(LIBLLHTTP_VERSION_MINOR) + unset(LIBLLHTTP_VERSION_MAJOR) + endif() + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args( + libllhttp + REQUIRED_VARS LIBLLHTTP_LIBRARY LIBLLHTTP_INCLUDE_DIR + VERSION_VAR LIBLLHTTP_VERSION + ) + + if(LIBLLHTTP_FOUND) + if(LIBLLHTTP_SHARED_LIBRARY) + set(LIBLLHTTP_LIBRARIES ${LIBLLHTTP_SHARED_LIBRARY}) + else() + set(LIBLLHTTP_LIBRARIES ${LIBLLHTTP_STATIC_LIBRARY}) + endif() + + set(LIBLLHTTP_INCLUDE_DIRS ${LIBLLHTTP_INCLUDE_DIR}) + + set(LIBLLHTTP_STATIC_INCLUDE_DIRS ${LIBLLHTTP_INCLUDE_DIRS}) + if(LIBLLHTTP_STATIC_LIBRARY) + set(LIBLLHTTP_STATIC_LIBRARIES ${LIBLLHTTP_STATIC_LIBRARY}) + endif() + endif() + + mark_as_advanced(LIBLLHTTP_INCLUDE_DIR LIBLLHTTP_SHARED_LIBRARY LIBLLHTTP_STATIC_LIBRARY) +endif() diff --git a/cmake/Findlibsqlite3.cmake b/cmake/Findlibsqlite3.cmake new file mode 100644 index 0000000..be4bb73 --- /dev/null +++ b/cmake/Findlibsqlite3.cmake @@ -0,0 +1,58 @@ +# - Try to find libev +# Once done this will define +# LIBSQLITE3_FOUND - System has libsqlite3 +# LIBSQLITE3_INCLUDE_DIRS - The libsqlite3 include directories +# LIBSQLITE3_LIBRARIES - The libraries needed to use libsqlite3 +# LIBSQLITE3_STATIC_INCLUDE_DIRS - The libsqlite3 static include directories +# LIBSQLITE3_STATIC_LIBRARIES - The libraries needed to use libsqlite3 statically + +pkg_check_modules(LIBSQLITE3 QUIET sqlite3) +if (NOT LIBSQLITE3_FOUND) + find_path( + LIBSQLITE3_INCLUDE_DIR + NAMES sqlite3.h + ) + + set(SAVED_CMAKE_FIND_LIBRARY_SUFFIXES "${CMAKE_FIND_LIBRARY_SUFFIXES}") + set(CMAKE_FIND_LIBRARY_SUFFIXES "${CMAKE_STATIC_LIBRARY_SUFFIX}") + find_library(LIBSQLITE3_STATIC_LIBRARY NAMES sqlite3) + set(CMAKE_FIND_LIBRARY_SUFFIXES "${CMAKE_SHARED_LIBRARY_SUFFIX}") + find_library(LIBSQLITE3_SHARED_LIBRARY NAMES sqlite3) + set(CMAKE_FIND_LIBRARY_SUFFIXES "${SAVED_CMAKE_FIND_LIBRARY_SUFFIXES}") + + if(LIBSQLITE3_SHARED_LIBRARY) + set(LIBSQLITE3_LIBRARY ${LIBSQLITE3_SHARED_LIBRARY}) + elseif(LIBSQLITE3_STATIC_LIBRARY) + set(LIBSQLITE3_LIBRARY ${LIBSQLITE3_STATIC_LIBRARY}) + endif() + + if(LIBSQLITE3_INCLUDE_DIR) + file(STRINGS ${LIBSQLITE3_INCLUDE_DIR}/sqlite3.h _ver_line REGEX "^#define SQLITE_VERSION *\"[0-9]+\\.[0-9]+\\.[0-9]+\"" LIMIT_COUNT 1) + string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" LIBSQLITE3_VERSION "${_ver_line}") + unset(_ver_line) + endif() + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args( + libsqlite3 + REQUIRED_VARS LIBSQLITE3_LIBRARY LIBSQLITE3_INCLUDE_DIR + VERSION_VAR LIBSQLITE3_VERSION + ) + + if(LIBSQLITE3_FOUND) + if(LIBSQLITE3_SHARED_LIBRARY) + set(LIBSQLITE3_LIBRARIES ${LIBSQLITE3_SHARED_LIBRARY}) + else() + set(LIBSQLITE3_LIBRARIES ${LIBSQLITE3_STATIC_LIBRARY}) + endif() + + set(LIBSQLITE3_INCLUDE_DIRS ${LIBSQLITE3_INCLUDE_DIR}) + + set(LIBSQLITE3_STATIC_INCLUDE_DIRS ${LIBSQLITE3_INCLUDE_DIRS}) + if(LIBSQLITE3_STATIC_LIBRARY) + set(LIBSQLITE3_STATIC_LIBRARIES ${LIBSQLITE3_STATIC_LIBRARY}) + endif() + endif() + + mark_as_advanced(LIBSQLITE3_INCLUDE_DIR LIBSQLITE3_SHARED_LIBRARY LIBSQLITE3_STATIC_LIBRARY) +endif() diff --git a/cmake/Findlibtls.cmake b/cmake/Findlibtls.cmake new file mode 100644 index 0000000..2d0f0e8 --- /dev/null +++ b/cmake/Findlibtls.cmake @@ -0,0 +1,52 @@ +# - Try to find libev +# Once done this will define +# LIBTLS_FOUND - System has libtls +# LIBTLS_INCLUDE_DIRS - The libtls include directories +# LIBTLS_LIBRARIES - The libraries needed to use libtls +# LIBTLS_STATIC_INCLUDE_DIRS - The libtls static include directories +# LIBTLS_STATIC_LIBRARIES - The libraries needed to use libtls statically + +pkg_check_modules(LIBTLS QUIET libtls) +if (NOT LIBTLS_FOUND) + find_path( + LIBTLS_INCLUDE_DIR + NAMES libtls.h + ) + + set(SAVED_CMAKE_FIND_LIBRARY_SUFFIXES "${CMAKE_FIND_LIBRARY_SUFFIXES}") + set(CMAKE_FIND_LIBRARY_SUFFIXES "${CMAKE_STATIC_LIBRARY_SUFFIX}") + find_library(LIBTLS_STATIC_LIBRARY NAMES libtls) + set(CMAKE_FIND_LIBRARY_SUFFIXES "${CMAKE_SHARED_LIBRARY_SUFFIX}") + find_library(LIBTLS_SHARED_LIBRARY NAMES libtls) + set(CMAKE_FIND_LIBRARY_SUFFIXES "${SAVED_CMAKE_FIND_LIBRARY_SUFFIXES}") + unset(SAVED_CMAKE_FIND_LIBRARY_SUFFIXES) + + if(LIBTLS_SHARED_LIBRARY) + set(LIBTLS_LIBRARY ${LIBTLS_SHARED_LIBRARY}) + elseif(LIBTLS_STATIC_LIBRARY) + set(LIBTLS_LIBRARY ${LIBTLS_STATIC_LIBRARY}) + endif() + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args( + liblibtls + REQUIRED_VARS LIBTLS_LIBRARY LIBTLS_INCLUDE_DIR + ) + + if(LIBTLS_FOUND) + if(LIBTLS_SHARED_LIBRARY) + set(LIBTLS_LIBRARIES ${LIBTLS_SHARED_LIBRARY}) + else() + set(LIBTLS_LIBRARIES ${LIBTLS_STATIC_LIBRARY}) + endif() + + set(LIBTLS_INCLUDE_DIRS ${LIBTLS_INCLUDE_DIR}) + + set(LIBTLS_STATIC_INCLUDE_DIRS ${LIBTLS_INCLUDE_DIRS}) + if(LIBTLS_STATIC_LIBRARY) + set(LIBTLS_STATIC_LIBRARIES ${LIBTLS_STATIC_LIBRARY}) + endif() + endif() + + mark_as_advanced(LIBTLS_INCLUDE_DIR LIBTLS_SHARED_LIBRARY LIBTLS_STATIC_LIBRARY) +endif() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 561fc5f..558cc9f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -10,20 +10,28 @@ add_compile_options(-Wall -Wextra -pedantic) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_LINK_WHAT_YOU_USE OFF) set(CMAKE_SKIP_BUILD_RPATH ON) set(CMAKE_CXX_VISIBILITY_PRESET hidden) set(CMAKE_VISIBILITY_INLINES_HIDDEN ON) +set(CMAKE_VERBOSE_MAKEFILE ON) -option(BUILD_STATIC_BINARY "Build static binary" ON) -option(FORCE_EXTERNAL_LIBEV "Do not use system libev" OFF) -option(FORCE_EXTERNAL_LIBADA "Do not use system libada" OFF) -option(FORCE_EXTERNAL_LIBLLHTTP "Do not use system libllhttp" OFF) -option(FORCE_EXTERNAL_SQLITE3 "Do not use system sqlite3" OFF) +option(BUILD_STATIC_BINARY "Build static binary" OFF) +option(BUILD_MOSTLY_STATIC_BINARY "All dependencies except system libraries are static" ON) +option(FORCE_EXTERNAL_LIBEV "Do not use system libev" OFF) +option(FORCE_EXTERNAL_LIBADA "Do not use system libada" OFF) +option(FORCE_EXTERNAL_LIBLLHTTP "Do not use system libllhttp" OFF) +option(FORCE_EXTERNAL_SQLITE3 "Do not use system sqlite3" OFF) +option(FORCE_EXTERNAL_LIBTLS "Do not use system libtls" OFF) if(BUILD_STATIC_BINARY) set(CMAKE_FIND_LIBRARY_SUFFIXES "${CMAKE_STATIC_LIBRARY_SUFFIX}") set(CMAKE_EXE_LINKER_FLAGS "-static") + set(BUILD_MOSTLY_STATIC_BINARY OFF) +elseif(BUILD_MOSTLY_STATIC_BINARY) + set(CMAKE_FIND_LIBRARY_SUFFIXES "${CMAKE_STATIC_LIBRARY_SUFFIX}") + set(CMAKE_EXE_LINKER_FLAGS "-static-libgcc -static-libstdc++") endif() find_program(CCACHE_EXE ccache) @@ -43,121 +51,151 @@ add_executable( server_p.cpp serversocket.cpp serversocket_p.cpp + tlsconfigurator.cpp + tlsconfigurator_p.cpp + tlsservercontext.cpp + tlsservercontext_p.cpp + tlsutils.cpp ) target_precompile_headers(tfhttp PRIVATE stdafx.h) if(NOT FORCE_EXTERNAL_LIBEV) - pkg_check_modules(EV libev) - if(NOT EV_FOUND) - find_path( - EV_INCLUDE_DIRS - NAMES ev.h - ) - - find_library( - EV_LIBRARIES - NAMES ev - ) - - if(EV_INCLUDE_DIRS AND EV_LIBRARIES) - set(EV_FOUND TRUE) - else() - set(EV_FOUND FALSE) - endif() - endif() + find_package(libev) endif() if(NOT FORCE_EXTERNAL_LIBADA) - find_path( - ADA_INCLUDE_DIRS - NAMES ada.h - ) - - find_library( - ADA_LIBRARIES - NAMES ada - ) - - if(ADA_INCLUDE_DIRS AND ADA_LIBRARIES) - set(ADA_FOUND TRUE) - else() - set(ADA_FOUND FALSE) - endif() + find_package(libada) endif() if(NOT FORCE_EXTERNAL_LIBLLHTTP) - pkg_check_modules(LLHTTP libllhttp) + find_package(libllhttp) endif() if(NOT FORCE_EXTERNAL_SQLITE3) - pkg_check_modules(SQLITE3 sqlite3) + find_package(libsqlite3) endif() -set(MATH_LIBS "") -check_library_exists(m floor "" LIBM) -if(LIBM) - list(APPEND MATH_LIBS "m") +if(NOT FORCE_EXTERNAL_LIBTLS) + find_package(libtls) +endif() + +if(BUILD_MOSTLY_STATIC_BINARY) + if (LIBEV_FOUND AND NOT LIBEV_STATIC_LIBRARIES) + message(WARNING "static version of libev not found") + unset(LIBEV_FOUND) + endif() + + if (LIBADA_FOUND AND NOT LIBADA_STATIC_LIBRARIES) + message(WARNING "static version of libada not found") + unset(LIBADA_FOUND) + endif() + + if (LIBLLHTTP_FOUND AND NOT LIBLLHTTP_STATIC_LIBRARIES) + message(WARNING "static version of libllhttp not found") + unset(LIBLLHTTP_FOUND) + endif() + + if (LIBSQLITE3_FOUND AND NOT LIBSQLITE3_STATIC_LIBRARIES) + message(WARNING "static version of libsqlite3 not found") + unset(LIBSQLITE3_FOUND) + endif() endif() find_path(SQLITE3PP sqlite3pp.h) find_path(NLOHMANN_JSON nlohmann/json.hpp) -if(NOT EV_FOUND) +if(NOT LIBEV_FOUND) + set(LIBEV_CPPFLAGS -DEV_COMPAT3=0 -DEV_PREPARE_ENABLE=0 -DEV_CHECK_ENABLE=0 -DEV_IDLE_ENABLE=0 -DEV_FORK_ENABLE=0 -DEV_CLEANUP_ENABLE=0 -DEV_CHILD_ENABLE=0 -DEV_ASYNC_ENABLE=0 -DEV_EMBED_ENABLE=0) + if (CMAKE_BUILD_TYPE STREQUAL "Debug") + list(APPEND LIBEV_CPPFLAGS -DEV_VERIFY=3) + set(LIBEV_CFLAGS -Og -g3) + else() + set(LIBEV_CFLAGS -O3 -g) + endif() + list(JOIN LIBEV_CPPFLAGS " " LIBEV_CPPFLAGS) + list(JOIN LIBEV_CFLAGS " " LIBEV_CFLAGS) + ExternalProject_Add( libev URL http://dist.schmorp.de/libev/Attic/libev-4.33.tar.gz URL_HASH MD5=a3433f23583167081bf4acdd5b01b34f DOWNLOAD_NO_PROGRESS ON - CONFIGURE_COMMAND /configure --quiet --enable-silent-rules --disable-dependency-tracking --prefix=${CMAKE_CURRENT_BINARY_DIR}/libev CC=${CMAKE_C_COMPILER} AR=${CMAKE_AR} RANLIB=${CMAKE_RANLIB} + CONFIGURE_COMMAND /configure --quiet --enable-static --disable-shared --prefix=${CMAKE_CURRENT_BINARY_DIR}/libev CC=${CMAKE_C_COMPILER} AR=${CMAKE_AR} RANLIB=${CMAKE_RANLIB} CPPFLAGS=${LIBEV_CPPFLAGS} CFLAGS=${LIBEV_CFLAGS} ) - set(EV_LIBRARIES "${CMAKE_CURRENT_BINARY_DIR}/libev/lib/${CMAKE_STATIC_LIBRARY_PREFIX}ev${CMAKE_STATIC_LIBRARY_SUFFIX}") - set(EV_INCLUDE_DIRS "${CMAKE_CURRENT_BINARY_DIR}/libev/include") + unset(LIBEV_CPPFLAGS) + unset(LIBEV_CFLAGS) + set(LIBEV_LIBRARIES ev;m) + set(LIBEV_LIBRARY_DIRS "${CMAKE_CURRENT_BINARY_DIR}/libev/lib") + set(LIBEV_INCLUDE_DIRS "${CMAKE_CURRENT_BINARY_DIR}/libev/include") + + set(LIBEV_STATIC_LIBRARIES ${LIBEV_LIBRARIES}) + set(LIBEV_STATIC_LIBRARY_DIRS ${LIBEV_LIBRARY_DIRS}) + set(LIBEV_STATIC_INCLUDE_DIRS ${LIBEV_INCLUDE_DIRS}) + add_dependencies(tfhttp libev) endif() -if(NOT ADA_FOUND) +if(NOT LIBADA_FOUND) ExternalProject_Add( libada GIT_REPOSITORY https://github.com/ada-url/ada.git GIT_TAG v2.7.4 GIT_SHALLOW ON - CMAKE_ARGS -DCMAKE_BUILD_TYPE=MinSizeRel -DADA_TOOLS=off -DADA_TESTING=off -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}/libada + CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DBUILD_SHARED_LIBS=OFF -DADA_TOOLS=off -DADA_TESTING=off -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}/libada ) - set(ADA_LIBRARIES "${CMAKE_CURRENT_BINARY_DIR}/libada/lib/${CMAKE_STATIC_LIBRARY_PREFIX}ada${CMAKE_STATIC_LIBRARY_SUFFIX}") - set(ADA_INCLUDE_DIRS "${CMAKE_CURRENT_BINARY_DIR}/libada/include") + set(LIBADA_LIBRARIES ada) + set(LIBADA_LIBRARY_DIRS "${CMAKE_CURRENT_BINARY_DIR}/libada/lib") + set(LIBADA_INCLUDE_DIRS "${CMAKE_CURRENT_BINARY_DIR}/libada/include") + + set(LIBADA_STATIC_LIBRARIES ${LIBADA_LIBRARIES}) + set(LIBADA_STATIC_LIBRARY_DIRS ${LIBADA_LIBRARY_DIRS}) + set(LIBADA_STATIC_INCLUDE_DIRS ${LIBADA_INCLUDE_DIRS}) + add_dependencies(tfhttp libada) endif() -if(NOT LLHTTP_FOUND) +if(NOT LIBLLHTTP_FOUND) ExternalProject_Add( libllhttp URL https://github.com/nodejs/llhttp/archive/refs/tags/release/v9.1.3.tar.gz URL_HASH MD5=1e87982dab458e5a26259fe36c5c55e7 DOWNLOAD_NO_PROGRESS ON - CMAKE_ARGS -DCMAKE_BUILD_TYPE=MinSizeRel -DBUILD_STATIC_LIBS=on -DBUILD_SHARED_LIBS=off -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}/libllhttp + CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DBUILD_STATIC_LIBS=ON -DBUILD_SHARED_LIBS=OFF -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}/libllhttp ) - set(LLHTTP_LIBRARIES "${CMAKE_CURRENT_BINARY_DIR}/libllhttp/lib/${CMAKE_STATIC_LIBRARY_PREFIX}llhttp${CMAKE_STATIC_LIBRARY_SUFFIX}") - set(LLHTTP_INCLUDE_DIRS "${CMAKE_CURRENT_BINARY_DIR}/libllhttp/include") + set(LIBLLHTTP_LIBRARIES llhttp) + set(LIBLLHTTP_LIBRARY_DIRS "${CMAKE_CURRENT_BINARY_DIR}/libllhttp/lib") + set(LIBLLHTTP_INCLUDE_DIRS "${CMAKE_CURRENT_BINARY_DIR}/libllhttp/include") + + set(LIBLLHTTP_STATIC_LIBRARIES ${LIBLLHTTP_LIBRARIES}) + set(LIBLLHTTP_STATIC_LIBRARY_DIRS ${LIBLLHTTP_LIBRARY_DIRS}) + set(LIBLLHTTP_STATIC_INCLUDE_DIRS ${LIBLLHTTP_INCLUDE_DIRS}) + add_dependencies(tfhttp libllhttp) endif() -if(NOT SQLITE3_FOUND) - set(SQLITE3_CFLAGS "-DSQLITE_DQS=0 -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 -DSQLITE_MAX_EXPR_DEPTH=0 -DSQLITE_OMIT_DECLTYPE -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_PROGRESS_CALLBACK -DSQLITE_OMIT_SHARED_CACHE -DSQLITE_DISABLE_FTS3_UNICODE -DSQLITE_DISABLE_FTS4_DEFERRED") +if(NOT LIBSQLITE3_FOUND) + set(SQLITE3_CPPFLAGS "-DSQLITE_DQS=0 -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 -DSQLITE_MAX_EXPR_DEPTH=0 -DSQLITE_OMIT_DECLTYPE -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_PROGRESS_CALLBACK -DSQLITE_OMIT_SHARED_CACHE -DSQLITE_DISABLE_FTS3_UNICODE -DSQLITE_DISABLE_FTS4_DEFERRED") ExternalProject_Add( - sqlite3 + libsqlite3 GIT_REPOSITORY https://github.com/sqlite/sqlite.git GIT_TAG version-3.44.2 GIT_SHALLOW ON - CONFIGURE_COMMAND /configure --quiet -disable-threadsafe --enable-releasemode --disable-readline --disable-tcl --enable-static --disable-shared --enable-tempstore=yes --disable-load-extension --disable-math --disable-json --prefix=${CMAKE_CURRENT_BINARY_DIR}/sqlite3 CC=${CMAKE_C_COMPILER} CPPFLAGS=${SQLITE3_CFLAGS} + CONFIGURE_COMMAND /configure --quiet -disable-threadsafe --enable-releasemode --disable-readline --disable-amalgamation --disable-tcl --enable-static --disable-shared --enable-tempstore=yes --disable-load-extension --disable-math --disable-json --without-tcl --prefix=${CMAKE_CURRENT_BINARY_DIR}/sqlite3 CC=${CMAKE_C_COMPILER} CPPFLAGS=${SQLITE3_CPPFLAGS} ) - set(SQLITE3_LIBRARIES "${CMAKE_CURRENT_BINARY_DIR}/sqlite3/lib/${CMAKE_STATIC_LIBRARY_PREFIX}sqlite3${CMAKE_STATIC_LIBRARY_SUFFIX}") - set(SQLITE3_INCLUDE_DIRS "${CMAKE_CURRENT_BINARY_DIR}/sqlite3/include") - add_dependencies(tfhttp sqlite3) + set(LIBSQLITE3_LIBRARIES sqlite3) + set(LIBSQLITE3_LIBRARY_DIRS "${CMAKE_CURRENT_BINARY_DIR}/sqlite3/lib") + set(LIBSQLITE3_INCLUDE_DIRS "${CMAKE_CURRENT_BINARY_DIR}/sqlite3/include") + + set(LIBSQLITE3_STATIC_LIBRARIES ${LIBSQLITE3_LIBRARIES}) + set(LIBSQLITE3_STATIC_LIBRARY_DIRS ${LIBSQLITE3_LIBRARY_DIRS}) + set(LIBSQLITE3_STATIC_INCLUDE_DIRS ${LIBSQLITE3_INCLUDE_DIRS}) + + add_dependencies(tfhttp libsqlite3) endif() if(NOT SQLITE3PP) @@ -192,18 +230,98 @@ else() target_include_directories(tfhttp PRIVATE ${NLOHMANN_JSON}) endif() -target_include_directories(tfhttp PRIVATE ${EV_INCLUDE_DIRS}) -target_link_directories(tfhttp PRIVATE ${EV_LIBRARY_DIRS}) -target_link_libraries(tfhttp PRIVATE ${EV_LIBRARIES} ${MATH_LIBS}) +if(NOT LIBTLS_FOUND) + ExternalProject_Add( + libtls + GIT_REPOSITORY https://git.causal.agency/libretls + GIT_TAG 3.8.1 + GIT_SHALLOW ON + CONFIGURE_COMMAND autoreconf -i && /configure --disable-dependency-tracking --disable-shared --enable-static --prefix=${CMAKE_CURRENT_BINARY_DIR}/libtls CC=${CMAKE_C_COMPILER} AR=${CMAKE_AR} RANLIB=${CMAKE_RANLIB} + ) + + set(LIBTLS_LIBRARIES tls;ssl;crypto) + set(LIBTLS_LIBRARY_DIRS "${CMAKE_CURRENT_BINARY_DIR}/libtls/lib") + set(LIBTLS_INCLUDE_DIRS "${CMAKE_CURRENT_BINARY_DIR}/libtls/include") -target_include_directories(tfhttp PRIVATE ${LLHTTP_INCLUDE_DIRS}) -target_link_directories(tfhttp PRIVATE ${LLHTTP_LIBRARY_DIRS}) -target_link_libraries(tfhttp PRIVATE ${LLHTTP_LIBRARIES}) + set(LIBTLS_STATIC_LIBRARIES ${LIBTLS_LIBRARIES}) + set(LIBTLS_STATIC_LIBRARY_DIRS ${LIBTLS_LIBRARY_DIRS}) + set(LIBTLS_STATIC_INCLUDE_DIRS ${LIBTLS_INCLUDE_DIRS}) + + add_dependencies(tfhttp libtls) +endif() -target_include_directories(tfhttp PRIVATE ${ADA_INCLUDE_DIRS}) -target_link_directories(tfhttp PRIVATE ${ADA_LIBRARY_DIRS}) -target_link_libraries(tfhttp PRIVATE ${ADA_LIBRARIES}) +if(BUILD_STATIC_BINARY OR BUILD_MOSTLY_STATIC_BINARY) + target_include_directories(tfhttp PRIVATE ${LIBEV_STATIC_INCLUDE_DIRS}) + target_link_directories(tfhttp PRIVATE ${LIBEV_STATIC_LIBRARY_DIRS}) + if(BUILD_MOSTLY_STATIC_BINARY) + target_link_libraries(tfhttp PRIVATE -Wl,-Bstatic;${LIBEV_STATIC_LIBRARIES};-Wl,-Bdynamic) + else() + target_link_libraries(tfhttp PRIVATE ${LIBEV_STATIC_LIBRARIES}) + endif() +else() + target_include_directories(tfhttp PRIVATE ${LIBEV_INCLUDE_DIRS}) + target_link_directories(tfhttp PRIVATE ${LIBEV_LIBRARY_DIRS}) + target_link_libraries(tfhttp PRIVATE ${LIBEV_LIBRARIES}) +endif() -target_include_directories(tfhttp PRIVATE ${SQLITE3_INCLUDE_DIRS}) -target_link_directories(tfhttp PRIVATE ${SQLITE3_LIBRARY_DIRS}) -target_link_libraries(tfhttp PRIVATE ${SQLITE3_LIBRARIES}) +if(BUILD_STATIC_BINARY OR BUILD_MOSTLY_STATIC_BINARY) + target_include_directories(tfhttp PRIVATE ${LIBLLHTTP_STATIC_INCLUDE_DIRS}) + target_link_directories(tfhttp PRIVATE ${LIBLLHTTP_STATIC_LIBRARY_DIRS}) + if(BUILD_MOSTLY_STATIC_BINARY) + target_link_libraries(tfhttp PRIVATE -Wl,-Bstatic;${LIBLLHTTP_STATIC_LIBRARIES};-Wl,-Bdynamic) + else() + target_link_libraries(tfhttp PRIVATE ${LIBLLHTTP_STATIC_LIBRARIES}) + endif() +else() + target_include_directories(tfhttp PRIVATE ${LIBLLHTTP_INCLUDE_DIRS}) + target_link_directories(tfhttp PRIVATE ${LIBLLHTTP_LIBRARY_DIRS}) + target_link_libraries(tfhttp PRIVATE ${LIBLLHTTP_LIBRARIES}) +endif() + +if(BUILD_STATIC_BINARY OR BUILD_MOSTLY_STATIC_BINARY) + target_include_directories(tfhttp PRIVATE ${LIBADA_STATIC_INCLUDE_DIRS}) + target_link_directories(tfhttp PRIVATE ${LIBADA_STATIC_LIBRARY_DIRS}) + if(BUILD_MOSTLY_STATIC_BINARY) + target_link_libraries(tfhttp PRIVATE -Wl,-Bstatic;${LIBADA_STATIC_LIBRARIES};-Wl,-Bdynamic) + else() + target_link_libraries(tfhttp PRIVATE ${LIBADA_STATIC_LIBRARIES}) + endif() +else() + target_include_directories(tfhttp PRIVATE ${LIBADA_INCLUDE_DIRS}) + target_link_directories(tfhttp PRIVATE ${LIBADA_LIBRARY_DIRS}) + target_link_libraries(tfhttp PRIVATE ${LIBADA_LIBRARIES}) +endif() + +if(BUILD_STATIC_BINARY OR BUILD_MOSTLY_STATIC_BINARY) + target_include_directories(tfhttp PRIVATE ${LIBSQLITE3_STATIC_INCLUDE_DIRS}) + target_link_directories(tfhttp PRIVATE ${LIBSQLITE3_STATIC_LIBRARY_DIRS}) + if(BUILD_MOSTLY_STATIC_BINARY) + target_link_libraries(tfhttp PRIVATE -Wl,-Bstatic;${LIBSQLITE3_STATIC_LIBRARIES};-Wl,-Bdynamic) + else() + target_link_libraries(tfhttp PRIVATE ${LIBSQLITE3_STATIC_LIBRARIES}) + endif() +else() + target_include_directories(tfhttp PRIVATE ${LIBSQLITE3_INCLUDE_DIRS}) + target_link_directories(tfhttp PRIVATE ${LIBSQLITE3_LIBRARY_DIRS}) + target_link_libraries(tfhttp PRIVATE ${LIBSQLITE3_LIBRARIES}) +endif() + +if(BUILD_STATIC_BINARY OR BUILD_MOSTLY_STATIC_BINARY) + target_include_directories(tfhttp PRIVATE ${LIBTLS_STATIC_INCLUDE_DIRS}) + target_link_directories(tfhttp PRIVATE ${LIBTLS_STATIC_LIBRARY_DIRS}) + if(BUILD_MOSTLY_STATIC_BINARY) + target_link_libraries(tfhttp PRIVATE -Wl,-Bstatic;${LIBTLS_STATIC_LIBRARIES};-Wl,-Bdynamic) + else() + target_link_libraries(tfhttp PRIVATE ${LIBTLS_STATIC_LIBRARIES}) + endif() +else() + target_include_directories(tfhttp PRIVATE ${LIBTLS_INCLUDE_DIRS}) + target_link_directories(tfhttp PRIVATE ${LIBTLS_LIBRARY_DIRS}) + target_link_libraries(tfhttp PRIVATE ${LIBTLS_LIBRARIES}) +endif() + +if(BUILD_MOSTLY_STATIC_BINARY) + get_target_property(libs tfhttp LINK_LIBRARIES) + list(REMOVE_ITEM libs m) + set_target_properties(tfhttp PROPERTIES LINK_LIBRARIES "${libs}") +endif() diff --git a/src/clienthandler.cpp b/src/clienthandler.cpp index fae395f..6e72536 100644 --- a/src/clienthandler.cpp +++ b/src/clienthandler.cpp @@ -2,9 +2,9 @@ #include "clienthandler_p.h" ClientHandler::ClientHandler( - const ev::loop_ref& loop, const std::shared_ptr& server, const std::shared_ptr& database + tls* ctx, const ev::loop_ref& loop, const std::shared_ptr& server, const std::shared_ptr& database ) - : d_ptr(std::make_unique(this, loop, server, database)) + : d_ptr(std::make_unique(this, ctx, loop, server, database)) {} ClientHandler::~ClientHandler() = default; diff --git a/src/clienthandler.h b/src/clienthandler.h index 8e5cf0a..6dbf02e 100644 --- a/src/clienthandler.h +++ b/src/clienthandler.h @@ -7,11 +7,13 @@ class ClientHandlerPrivate; class Database; class Server; +struct tls; class ClientHandler { public: ClientHandler( - const ev::loop_ref& loop, const std::shared_ptr& server, const std::shared_ptr& database + tls* context, const ev::loop_ref& loop, const std::shared_ptr& server, + const std::shared_ptr& database ); ClientHandler(const ClientHandler&) = delete; ClientHandler(ClientHandler&&) = delete; diff --git a/src/clienthandler_p.cpp b/src/clienthandler_p.cpp index 8a63484..35131b7 100644 --- a/src/clienthandler_p.cpp +++ b/src/clienthandler_p.cpp @@ -4,10 +4,11 @@ #include "server.h" ClientHandlerPrivate::ClientHandlerPrivate( - ClientHandler* q_ptr, const ev::loop_ref& loop, const std::shared_ptr& server, + ClientHandler* q_ptr, tls* ctx, const ev::loop_ref& loop, const std::shared_ptr& server, const std::shared_ptr& database ) - : q_ptr(q_ptr), m_loop(loop), m_server(server), m_database(database), m_io(loop), m_timer(loop) + : q_ptr(q_ptr), m_ctx(ctx, &dispose_tls_context), m_loop(loop), m_server(server), m_database(database), m_io(loop), + m_timer(loop) { llhttp_settings_init(&this->m_settings); this->m_settings.on_message_complete = ClientHandlerPrivate::on_message_complete; @@ -21,8 +22,17 @@ ClientHandlerPrivate::ClientHandlerPrivate( ClientHandlerPrivate::~ClientHandlerPrivate() { const int fd = this->m_io.fd; - this->stop_watchers(); - close(fd); + this->m_io.stop(); + this->m_timer.stop(); + + if (fd != -1) { + if (this->m_ctx) { + tls_close(this->m_ctx.get()); + } + + shutdown(fd, SHUT_RDWR); + close(fd); + } } int ClientHandlerPrivate::accept(int fd) @@ -31,72 +41,195 @@ int ClientHandlerPrivate::accept(int fd) this->m_io.start(fd, ev::READ); this->m_timer.set(this); - this->m_timer.start(ClientHandlerPrivate::READ_TIMEOUT, 0); + this->m_timer.set(0.0, ClientHandlerPrivate::READ_TIMEOUT); + this->m_timer.again(); return 0; } -void ClientHandlerPrivate::on_read(ev::io& watcher, int) // NOSONAR(cpp:S995) +void ClientHandlerPrivate::on_read(ev::io& watcher, int revents) { + if (revents & ev::ERROR) [[unlikely]] { + this->close_connection("Error: failed to read from socket: EV_ERROR"); + return; + } + constexpr std::size_t BUFFER_SIZE = 8192; std::array buffer{}; - const ssize_t received = recv(watcher.fd, buffer.data(), sizeof(buffer), 0); - if (received <= 0) [[unlikely]] { - std::cerr << std::format( - "Error: failed to read from socket: {}\n", std::error_code(errno, std::system_category()).message() - ); - this->stop_watchers(); - this->terminate(); - return; + int new_events = 0; + ssize_t received; + std::string error; + + if (this->m_ctx) { + received = tls_read(this->m_ctx.get(), buffer.data(), sizeof(buffer)); + if (received == TLS_WANT_POLLIN) { + new_events = ev::READ; + } + else if (received == TLS_WANT_POLLOUT) { + new_events = ev::WRITE; + } + else if (received < 0) [[unlikely]] { + error = tls_error(this->m_ctx.get()); + } + } + else { + received = recv(watcher.fd, buffer.data(), sizeof(buffer), 0); + if (received < 0) [[unlikely]] { + error = std::error_code(errno, std::system_category()).message(); + } } - if (auto status = llhttp_execute(&this->m_parser, buffer.data(), static_cast(received)); - status != HPE_OK) [[unlikely]] { - std::cerr << std::format("Error: failed to parse HTTP request: {}\n", llhttp_errno_name(status)); - this->stop_watchers(); - this->terminate(); - return; + if (received > 0) { + if (auto status = llhttp_execute(&this->m_parser, buffer.data(), static_cast(received)); + status != HPE_OK) [[unlikely]] { + error = std::format("Error: failed to parse HTTP request: {}", llhttp_errno_name(status)); + } + else if (!this->m_done) { + new_events = ev::READ; + } + } + else if (received == 0) { + if (auto status = llhttp_finish(&this->m_parser); status != HPE_OK) [[unlikely]] { + error = std::format("Error: failed to parse HTTP request: {}", llhttp_errno_name(status)); + } + else if (!this->m_done) { + error = "Incomplete HTTP request, closing connection"; + } + } + else if (!error.empty()) { + error = std::format("Error: failed to read from socket: {}", error); } - this->m_timer.again(); + if (!error.empty()) { + this->close_connection(error); + } + else if (!this->m_done && new_events != 0) { + if (new_events != ev::READ) { + watcher.start(watcher.fd, new_events); + } + + this->m_timer.again(); + } } -void ClientHandlerPrivate::on_write(ev::io& watcher, int) +void ClientHandlerPrivate::on_write(ev::io& watcher, int revents) { + if (revents & ev::ERROR) [[unlikely]] { + this->close_connection("Error: failed to write to socket: EV_ERROR"); + return; + } + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) - const auto* buf = this->m_response.data() + this->m_offset; - auto len = this->m_response.size() - this->m_offset; - const ssize_t written = send(watcher.fd, buf, len, 0); - - if (written <= 0) [[unlikely]] { - std::cerr << std::format( - "Error: failed to write to socket: {}\n", std::error_code(errno, std::system_category()).message() - ); - this->stop_watchers(); + const auto* buf = this->m_response.data() + this->m_offset; + auto len = this->m_response.size() - this->m_offset; + int new_events = 0; + std::string error; + ssize_t written; + + if (this->m_ctx) { + written = tls_write(this->m_ctx.get(), buf, len); + if (written == TLS_WANT_POLLIN) { + new_events = ev::READ; + } + else if (written == TLS_WANT_POLLOUT) { + new_events = ev::WRITE; + } + else if (written < 0) [[unlikely]] { + error = tls_error(this->m_ctx.get()); + } + } + else { + written = send(watcher.fd, buf, len, 0); + if (written < 0) [[unlikely]] { + error = std::error_code(errno, std::system_category()).message(); + } + } + + if (written > 0) { + this->m_offset += static_cast(written); + if (this->m_offset != this->m_response.size()) { + new_events = ev::WRITE; + } + } + else if (written == 0) { + if (this->m_offset != this->m_response.size()) { + error = "Warning: failed to write to socket: connection closed"; + } + } + else { + error = std::format("Failed to write to socket: {}", error); + } + + if (!error.empty()) [[unlikely]] { + std::cerr << error << '\n'; + assert(new_events == 0); + } + + if (new_events == 0) { + this->close_connection(); + } + else { + if (new_events != ev::WRITE) { + watcher.start(watcher.fd, new_events); + } + + this->m_timer.again(); + } +} + +void ClientHandlerPrivate::on_tls_close(ev::io& watcher, int revents) +{ + std::cerr << "tls_close poll " << revents << "\n"; + if (revents & ev::ERROR) [[unlikely]] { + watcher.stop(); + std::cerr << "Error: failed to close TLS connection gracefully: EV_ERROR\n"; + this->m_ctx.reset(); this->terminate(); - return; } - this->m_offset += static_cast(written); - if (this->m_offset == this->m_response.size()) { - this->stop_watchers(); + auto res = tls_close(this->m_ctx.get()); + if (res == TLS_WANT_POLLIN || res == TLS_WANT_POLLOUT) { + watcher.start(watcher.fd, res == TLS_WANT_POLLIN ? ev::READ : ev::WRITE); + } + else { + if (res == -1) { + std::cerr << std::format( + "Warning: failed to close TLS connection gracefully: {}\n", tls_error(this->m_ctx.get()) + ); + } + + watcher.stop(); + this->m_ctx.reset(); this->terminate(); - return; } } -void ClientHandlerPrivate::on_timeout(ev::timer&, int) // NOSONAR +void ClientHandlerPrivate::on_timeout(ev::timer& watcher, int) { - this->stop_watchers(); - this->terminate(); + // Stop the watcher here because close_connection() may take some time to complete + watcher.stop(); + this->close_connection(); } -void ClientHandlerPrivate::stop_watchers() +void ClientHandlerPrivate::close_connection(const std::string& error) { - const int fd = this->m_io.fd; - this->m_io.stop(); - this->m_timer.stop(); - close(fd); + if (!error.empty()) { + std::cerr << error << '\n'; + } + + if (this->m_ctx) { + if (int res = tls_close(this->m_ctx.get()); res == TLS_WANT_POLLIN || res == TLS_WANT_POLLOUT) { + this->m_timer.again(); + this->m_io.stop(); + this->m_io.set(this); + this->m_io.start(this->m_io.fd, res == TLS_WANT_POLLIN ? ev::READ : ev::WRITE); + return; + } + + this->m_ctx.reset(); + } + + this->terminate(); } void ClientHandlerPrivate::terminate() @@ -104,6 +237,9 @@ void ClientHandlerPrivate::terminate() if (auto server = this->m_server.lock(); server) [[likely]] { server->remove_handler(this->q_ptr); } + else { + delete this; + } } void ClientHandlerPrivate::handle_get_state(const std::string& slug) @@ -186,6 +322,10 @@ int ClientHandlerPrivate::on_message_complete(llhttp_t* parser) auto path = client->m_url.get_pathname(); auto method = client->m_method; + client->m_io.stop(); + client->m_timer.stop(); + client->m_done = true; + std::cout << std::format( "{0:%F} {0:%X%z} {1} {2}\n", std::chrono::system_clock::now(), llhttp_method_name(method), path ); @@ -235,14 +375,13 @@ int ClientHandlerPrivate::on_message_complete(llhttp_t* parser) } } catch (const std::exception& e) { - std::cerr << std::format( - "Error handling the request {} {}: {}: {}\n", llhttp_method_name(method), path, typeid(e).name(), e.what() - ); + std::cerr << std::format("Error handling the request {} {}: {}\n", llhttp_method_name(method), path, e.what()); client->generate_text_response(HTTP_STATUS_INTERNAL_SERVER_ERROR); } - client->m_io.set(ev::WRITE); client->m_io.set(client); + client->m_io.start(client->m_io.fd, ev::WRITE); + client->m_timer.again(); return HPE_OK; } diff --git a/src/clienthandler_p.h b/src/clienthandler_p.h index 91fac64..4a2bb8d 100644 --- a/src/clienthandler_p.h +++ b/src/clienthandler_p.h @@ -7,14 +7,16 @@ #include #include #include "database.h" +#include "tlsutils.h" class ClientHandler; class Server; +struct tls; class ClientHandlerPrivate { public: ClientHandlerPrivate( - ClientHandler* q_ptr, const ev::loop_ref& loop, const std::shared_ptr& server, + ClientHandler* q_ptr, tls* ctx, const ev::loop_ref& loop, const std::shared_ptr& server, const std::shared_ptr& database ); ClientHandlerPrivate(const ClientHandlerPrivate&) = delete; @@ -25,10 +27,11 @@ class ClientHandlerPrivate { int accept(int fd); - static constexpr ev_tstamp READ_TIMEOUT = 15000; + static constexpr ev_tstamp READ_TIMEOUT = 15; private: ClientHandler* q_ptr; + std::unique_ptr m_ctx; ev::loop_ref m_loop; std::weak_ptr m_server; std::shared_ptr m_database; @@ -41,12 +44,14 @@ class ClientHandlerPrivate { std::string m_body{}; std::string m_response; std::size_t m_offset = 0; + bool m_done = false; void on_read(ev::io& watcher, int revents); void on_write(ev::io& watcher, int revents); + void on_tls_close(ev::io& watcher, int revents); void on_timeout(ev::timer& timer, int revents); - void stop_watchers(); + void close_connection(const std::string& error = std::string()); void terminate(); void handle_get_state(const std::string& slug); diff --git a/src/main.cpp b/src/main.cpp index 435c80e..9d57a8b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,6 +2,9 @@ #include #include "database.h" #include "server.h" +#include "tlsconfigurator.h" +#include "tlsexception.h" +#include "tlsservercontext.h" namespace { void signal_watcher(ev::sig& watcher, int) @@ -55,6 +58,19 @@ void destroy_loop(const ev::loop_ref* loop) ev_loop_destroy(*loop); } +void replace_tls_context(std::shared_ptr& old_ctx, const std::shared_ptr& new_ctx) +{ + try { + new_ctx->get_context(); + } + catch (const TLSException& e) { + std::cerr << std::format("Error: failed to reload TLS context: {}\n", e.what()); + return; + } + + old_ctx = new_ctx; +} + } // namespace int main() @@ -79,14 +95,22 @@ int main() sigterm_watcher.set(); sigterm_watcher.start(SIGTERM); + TLSConfigurator tlsconf("TFHTTP_"); + auto ctx = tlsconf.configure(); + if (ctx) { + ctx->get_context(); + tlsconf.watch([&ctx](std::shared_ptr new_ctx) { replace_tls_context(ctx, new_ctx); }); + } + auto server = Server::create(loop, address, port, database); + server->set_tls_context(ctx); server->run(); loop.run(0); return 0; } catch (const std::exception& e) { - std::cerr << std::format("FATAL ERROR: {}: {}\n", typeid(e).name(), e.what()); + std::cerr << std::format("FATAL ERROR: {}\n", e.what()); return 1; } } diff --git a/src/server.cpp b/src/server.cpp index f7e99e8..19eb476 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -28,3 +28,8 @@ void Server::remove_handler(ClientHandler* handler) { this->d_func()->remove_handler(handler); } + +void Server::set_tls_context(const std::shared_ptr& context) +{ + this->d_func()->set_tls_context(context); +} diff --git a/src/server.h b/src/server.h index cd491d9..ab942f1 100644 --- a/src/server.h +++ b/src/server.h @@ -8,6 +8,7 @@ class ClientHandler; class Database; class ServerPrivate; +class TLSServerContext; class Server : public std::enable_shared_from_this { private: @@ -32,6 +33,8 @@ class Server : public std::enable_shared_from_this { std::uint16_t run(); void remove_handler(ClientHandler* handler); + void set_tls_context(const std::shared_ptr& context); + private: std::unique_ptr d_ptr; diff --git a/src/server_p.cpp b/src/server_p.cpp index 9c86fb5..c8fb5df 100644 --- a/src/server_p.cpp +++ b/src/server_p.cpp @@ -1,8 +1,11 @@ #include "stdafx.h" #include "server_p.h" +#include #include "clienthandler.h" #include "server.h" #include "serversocket.h" +#include "tlsexception.h" +#include "tlsservercontext.h" ServerPrivate::ServerPrivate( Server* q_ptr, const ev::loop_ref& loop, const std::string& ip, std::uint16_t port, @@ -24,11 +27,26 @@ void ServerPrivate::remove_handler(const ClientHandler* handler) this->m_clients.erase(handler); } +void ServerPrivate::set_tls_context(const std::shared_ptr& context) +{ + this->m_tls_context = context; +} + void ServerPrivate::on_accept(ev::io&, int) { try { const int fd = this->m_socket.accept(); - auto handler = std::make_unique(this->m_loop, this->q_ptr->shared_from_this(), this->m_database); + std::experimental::scope_fail close_fd([fd]() { close(fd); }); + + tls* client_ctx = nullptr; + if (this->m_tls_context && tls_accept_socket(this->m_tls_context->get_context(), &client_ctx, fd) != 0) { + throw TLSException(this->m_tls_context->get_context()); + } + + auto handler = std::make_unique( + client_ctx, this->m_loop, this->q_ptr->shared_from_this(), this->m_database + ); + close_fd.release(); handler->accept(fd); this->m_clients[handler.get()] = std::move(handler); @@ -38,4 +56,7 @@ void ServerPrivate::on_accept(ev::io&, int) "Error: failed to accept connection: {}: {} ({})\n", e.what(), e.code().message(), e.code().value() ); } + catch (TLSException& e) { + std::cerr << std::format("Error: failed to accept connection: {}\n", e.what()); + } } diff --git a/src/server_p.h b/src/server_p.h index 488bcaf..0e4feca 100644 --- a/src/server_p.h +++ b/src/server_p.h @@ -10,6 +10,7 @@ class ClientHandler; class Database; class Server; +class TLSServerContext; class ServerPrivate { public: @@ -21,6 +22,8 @@ class ServerPrivate { std::uint16_t run(); void remove_handler(const ClientHandler* handler); + void set_tls_context(const std::shared_ptr& context); + private: Server* q_ptr; ev::loop_ref m_loop; @@ -28,6 +31,7 @@ class ServerPrivate { ev::io m_accept_watcher; std::shared_ptr m_database; std::unordered_map> m_clients; + std::shared_ptr m_tls_context{}; void on_accept(ev::io& watcher, int revents); }; diff --git a/src/serversocket.h b/src/serversocket.h index 6b91eb9..33dad5e 100644 --- a/src/serversocket.h +++ b/src/serversocket.h @@ -5,6 +5,7 @@ #include class ServerSocketPrivate; +class TLSServerContext; class ServerSocket { public: diff --git a/src/stdafx.h b/src/stdafx.h index 39d65ac..9f2ada1 100644 --- a/src/stdafx.h +++ b/src/stdafx.h @@ -33,6 +33,7 @@ #include #include #include +#include #include #include diff --git a/src/tlsconfigurator.cpp b/src/tlsconfigurator.cpp new file mode 100644 index 0000000..80f7283 --- /dev/null +++ b/src/tlsconfigurator.cpp @@ -0,0 +1,24 @@ +#include "tlsconfigurator.h" +#include +#include "tlsconfigurator_p.h" + +TLSConfigurator::TLSConfigurator(const std::string& env_prefix) + : d_ptr(std::make_unique(env_prefix)) +{} + +TLSConfigurator::~TLSConfigurator() = default; + +std::shared_ptr TLSConfigurator::configure() const +{ + return this->d_func()->configure(); +} + +void TLSConfigurator::watch(watch_callback_t callback) +{ + this->d_func()->watch(callback); +} + +void TLSConfigurator::stop() +{ + this->d_func()->watch(nullptr); +} diff --git a/src/tlsconfigurator.h b/src/tlsconfigurator.h new file mode 100644 index 0000000..8fcf450 --- /dev/null +++ b/src/tlsconfigurator.h @@ -0,0 +1,28 @@ +#ifndef A2ED91C9_5DC4_495A_8E7D_3388BFD9DB20 +#define A2ED91C9_5DC4_495A_8E7D_3388BFD9DB20 + +#include +#include + +class TLSConfiguratorPrivate; +class TLSServerContext; + +class TLSConfigurator { +public: + using watch_callback_t = std::function)>; + + explicit TLSConfigurator(const std::string& env_prefix); + ~TLSConfigurator(); + + std::shared_ptr configure() const; + void watch(watch_callback_t callback); + void stop(); + +private: + std::unique_ptr d_ptr; + + [[nodiscard]] inline TLSConfiguratorPrivate* d_func() { return this->d_ptr.get(); } + [[nodiscard]] inline const TLSConfiguratorPrivate* d_func() const { return this->d_ptr.get(); } +}; + +#endif /* A2ED91C9_5DC4_495A_8E7D_3388BFD9DB20 */ diff --git a/src/tlsconfigurator_p.cpp b/src/tlsconfigurator_p.cpp new file mode 100644 index 0000000..48da64e --- /dev/null +++ b/src/tlsconfigurator_p.cpp @@ -0,0 +1,123 @@ +#include "tlsconfigurator_p.h" +#include +#include +#include "tlsexception.h" +#include "tlsservercontext.h" + +namespace { + +std::string getenv(const std::string& prefix, const char* name) +{ + const std::string env_name = prefix + name; + const char* value = std::getenv(env_name.c_str()); // NOLINT(concurrency-mt-unsafe) + return value != nullptr ? value : ""; +} + +} // namespace + +TLSConfiguratorPrivate::TLSConfiguratorPrivate(const std::string& env_prefix) : m_env_prefix(env_prefix) +{ + this->m_https = getenv(this->m_env_prefix, "HTTPS") == "1"; + this->m_certificate = getenv(this->m_env_prefix, "CERTIFICATE"); + this->m_key = getenv(this->m_env_prefix, "PRIVATE_KEY"); + this->m_ca = getenv(this->m_env_prefix, "CA_CERTIFICATE"); + this->m_trusted_certificate = getenv(this->m_env_prefix, "TRUSTED_CERTIFICATE"); + this->m_tls_protocols = getenv(this->m_env_prefix, "TLS_PROTOCOLS"); + this->m_tls_ciphers = getenv(this->m_env_prefix, "TLS_CIPHERS"); + this->m_tls_curves = getenv(this->m_env_prefix, "TLS_CURVES"); + this->m_tls_verify_client = getenv(this->m_env_prefix, "TLS_VERIFY_CLIENT") == "1"; + this->m_tls_enable_dhe = getenv(this->m_env_prefix, "TLS_ENABLE_DHE") == "1"; +} + +std::shared_ptr TLSConfiguratorPrivate::configure() const +{ + if (!this->m_https) { + return std::shared_ptr(nullptr); + } + + auto context = TLSServerContext::create(); + if (!this->m_certificate.empty() && !this->m_key.empty()) { + context->set_keypair(this->m_certificate, this->m_key); + } + + if (!this->m_ca.empty()) { + context->set_ca(this->m_ca); + } + + if (!this->m_trusted_certificate.empty()) { + context->set_ocsp_staple(this->m_trusted_certificate); + } + + if (!this->m_tls_protocols.empty()) { + context->set_protocols(this->m_tls_protocols); + } + + if (!this->m_tls_ciphers.empty()) { + context->set_ciphers(this->m_tls_ciphers); + } + + if (!this->m_tls_curves.empty()) { + context->set_curves(this->m_tls_curves); + } + + if (this->m_tls_verify_client) { + context->set_verify_client(); + } + + if (this->m_tls_enable_dhe) { + context->enable_dhe(); + } + + return context; +} + +void TLSConfiguratorPrivate::watch(TLSConfigurator::watch_callback_t callback) +{ + if (!callback) { + this->m_stat_watcher.stop(); + this->m_timer.stop(); + } + else if (this->m_https) { + this->m_callback = callback; + + if (!this->m_certificate.empty()) { + this->m_stat_watcher.set(this); + this->m_stat_watcher.start(this->m_certificate.c_str()); + + this->m_timer.set(this); + this->m_timer.set(0, TLSConfiguratorPrivate::RECONFIGURATION_DELAY); + } + } +} + +void TLSConfiguratorPrivate::on_stat(ev::stat& watcher, int revents) +{ + if (revents & ev::ERROR) { + std::cerr << std::format("Error: stat watcher error: EV_ERROR\n"); + return; + } + + watcher.stop(); + this->m_timer.again(); +} + +void TLSConfiguratorPrivate::on_timeout(ev::timer& watcher, int revents) +{ + watcher.stop(); + if (revents & ev::ERROR) { + std::cerr << std::format("Error: timer watcher error: EV_ERROR\n"); + return; + } + + if (this->m_callback) { + try { + auto config = this->configure(); + this->m_callback(config); + } + catch (const TLSException& e) { + std::cerr << std::format("Error: unexpected TLS configuration error: {}\n", e.what()); + } + } + + this->m_stat_watcher.start(this->m_certificate.c_str()); +} diff --git a/src/tlsconfigurator_p.h b/src/tlsconfigurator_p.h new file mode 100644 index 0000000..fe18aea --- /dev/null +++ b/src/tlsconfigurator_p.h @@ -0,0 +1,40 @@ +#ifndef C815C459_D8C3_470F_9E71_D840CB52F622 +#define C815C459_D8C3_470F_9E71_D840CB52F622 + +#include +#include +#include +#include "tlsconfigurator.h" + +class TLSServerContext; + +class TLSConfiguratorPrivate { +public: + explicit TLSConfiguratorPrivate(const std::string& env_prefix); + + std::shared_ptr configure() const; + void watch(TLSConfigurator::watch_callback_t callback); + + static constexpr ev_tstamp RECONFIGURATION_DELAY = 15; + +private: + std::string m_env_prefix; + bool m_https = false; + std::string m_certificate{}; + std::string m_key{}; + std::string m_ca{}; + std::string m_trusted_certificate{}; + std::string m_tls_protocols{}; + std::string m_tls_ciphers{}; + std::string m_tls_curves{}; + bool m_tls_verify_client = false; + bool m_tls_enable_dhe = false; + ev::stat m_stat_watcher{}; + ev::timer m_timer{}; + TLSConfigurator::watch_callback_t m_callback = nullptr; + + void on_stat(ev::stat& watcher, int revents); + void on_timeout(ev::timer& timer, int revents); +}; + +#endif /* C815C459_D8C3_470F_9E71_D840CB52F622 */ diff --git a/src/tlsexception.h b/src/tlsexception.h new file mode 100644 index 0000000..c28d393 --- /dev/null +++ b/src/tlsexception.h @@ -0,0 +1,19 @@ +#ifndef BE09C4E0_7C06_4D7A_886A_07FCAB12222C +#define BE09C4E0_7C06_4D7A_886A_07FCAB12222C + +#include +#include + +class TLSException : public std::runtime_error { +public: + using std::runtime_error::runtime_error; + + explicit TLSException(tls* ctx) : std::runtime_error(tls_error(ctx)) {} + explicit TLSException(tls_config* config) : std::runtime_error(tls_config_error(config)) {} + TLSException(const std::string& prefix, tls* ctx) : std::runtime_error(prefix + ": " + tls_error(ctx)) {} + TLSException(const std::string& prefix, tls_config* config) + : std::runtime_error(prefix + ": " + tls_config_error(config)) + {} +}; + +#endif /* BE09C4E0_7C06_4D7A_886A_07FCAB12222C */ diff --git a/src/tlsservercontext.cpp b/src/tlsservercontext.cpp new file mode 100644 index 0000000..66f307e --- /dev/null +++ b/src/tlsservercontext.cpp @@ -0,0 +1,58 @@ +#include "tlsservercontext.h" +#include "tlsservercontext_p.h" + +std::shared_ptr TLSServerContext::create() +{ + return std::make_shared(TLSServerContext::PrivateTag{}); +} + +TLSServerContext::TLSServerContext(const TLSServerContext::PrivateTag&) + : d_ptr(std::make_unique()) +{} + +TLSServerContext::~TLSServerContext() = default; + +void TLSServerContext::set_keypair(const std::string& cert, const std::string& key) +{ + this->d_func()->set_keypair(cert, key); +} + +void TLSServerContext::set_ca(const std::string& ca) +{ + this->d_func()->set_ca(ca); +} + +void TLSServerContext::set_ocsp_staple(const std::string& ocsp) +{ + this->d_func()->set_ocsp_staple(ocsp); +} + +void TLSServerContext::set_verify_client() +{ + this->d_func()->set_verify_client(); +} + +void TLSServerContext::enable_dhe() +{ + this->d_func()->enable_dhe(); +} + +void TLSServerContext::set_protocols(const std::string& protocols) +{ + this->d_func()->set_protocols(protocols); +} + +void TLSServerContext::set_ciphers(const std::string& ciphers) +{ + this->d_func()->set_ciphers(ciphers); +} + +void TLSServerContext::set_curves(const std::string& curves) +{ + this->d_func()->set_curves(curves); +} + +tls* TLSServerContext::get_context() +{ + return this->d_func()->get_context(); +} diff --git a/src/tlsservercontext.h b/src/tlsservercontext.h new file mode 100644 index 0000000..064c049 --- /dev/null +++ b/src/tlsservercontext.h @@ -0,0 +1,43 @@ +#ifndef A2AA408D_BD14_4F04_9950_DE9B21366C2D +#define A2AA408D_BD14_4F04_9950_DE9B21366C2D + +#include +#include + +class TLSServerContextPrivate; + +struct tls; + +class TLSServerContext : public std::enable_shared_from_this { +private: + struct PrivateTag {}; + +public: + static std::shared_ptr create(); + + explicit TLSServerContext(const PrivateTag&); + TLSServerContext(const TLSServerContext&) = delete; + TLSServerContext(TLSServerContext&&) = default; + TLSServerContext& operator=(const TLSServerContext&) = delete; + TLSServerContext& operator=(TLSServerContext&&) = default; + ~TLSServerContext(); + + void set_keypair(const std::string& cert, const std::string& key); + void set_ca(const std::string& ca); + void set_ocsp_staple(const std::string& ocsp); + void set_verify_client(); + void enable_dhe(); + void set_protocols(const std::string& protocols); + void set_ciphers(const std::string& ciphers); + void set_curves(const std::string& curves); + + tls* get_context(); + +private: + std::unique_ptr d_ptr; + + [[nodiscard]] inline TLSServerContextPrivate* d_func() { return this->d_ptr.get(); } + [[nodiscard]] inline const TLSServerContextPrivate* d_func() const { return this->d_ptr.get(); } +}; + +#endif /* A2AA408D_BD14_4F04_9950_DE9B21366C2D */ diff --git a/src/tlsservercontext_p.cpp b/src/tlsservercontext_p.cpp new file mode 100644 index 0000000..38da275 --- /dev/null +++ b/src/tlsservercontext_p.cpp @@ -0,0 +1,85 @@ +#include "tlsservercontext_p.h" +#include "tlsexception.h" + +TLSServerContextPrivate::TLSServerContextPrivate() +{ + if (!this->m_context || !this->m_config) { + throw TLSException("Failed to create TLS context"); + } + + tls_config_prefer_ciphers_server(this->m_config.get()); +} + +void TLSServerContextPrivate::set_keypair(const std::string& cert, const std::string& key) +{ + if (tls_config_set_keypair_file(this->m_config.get(), cert.c_str(), key.c_str()) != 0) { + throw TLSException("Failed to set keypair", this->m_config.get()); + } +} + +void TLSServerContextPrivate::set_ca(const std::string& ca) +{ + if (tls_config_set_ca_file(this->m_config.get(), ca.c_str()) != 0) { + throw TLSException("Failed to set CA", this->m_config.get()); + } +} + +void TLSServerContextPrivate::set_ocsp_staple(const std::string& ocsp) +{ + if (tls_config_set_ocsp_staple_file(this->m_config.get(), ocsp.c_str()) != 0) { + throw TLSException("Failed to set OCSP staple", this->m_config.get()); + } + + tls_config_ocsp_require_stapling(this->m_config.get()); +} + +void TLSServerContextPrivate::set_verify_client() +{ + tls_config_verify_client(this->m_config.get()); +} + +void TLSServerContextPrivate::enable_dhe() +{ + tls_config_set_dheparams(this->m_config.get(), "auto"); +} + +void TLSServerContextPrivate::set_protocols(const std::string& protocols) +{ + std::uint32_t p; + + if (tls_config_parse_protocols(&p, protocols.c_str()) != 0) { + throw TLSException("Failed to parse protocols"); + } + + if (tls_config_set_protocols(this->m_config.get(), p) != 0) { + throw TLSException("Failed to set protocols", this->m_config.get()); + } +} + +void TLSServerContextPrivate::set_ciphers(const std::string& ciphers) +{ + if (tls_config_set_ciphers(this->m_config.get(), ciphers.c_str()) != 0) { + throw TLSException("Failed to set ciphers", this->m_config.get()); + } +} + +void TLSServerContextPrivate::set_curves(const std::string& curves) +{ + if (tls_config_set_ecdhecurves(this->m_config.get(), curves.c_str()) != 0) { + throw TLSException("Failed to set curves", this->m_config.get()); + } +} + +tls* TLSServerContextPrivate::get_context() +{ + if (!this->m_configured) { + if (tls_configure(this->m_context.get(), this->m_config.get()) != 0) { + throw TLSException(std::string("Failed to configure TLS context: ").append(tls_error(this->m_context.get())) + ); + } + + this->m_configured = true; + } + + return this->m_context.get(); +} diff --git a/src/tlsservercontext_p.h b/src/tlsservercontext_p.h new file mode 100644 index 0000000..61592cf --- /dev/null +++ b/src/tlsservercontext_p.h @@ -0,0 +1,30 @@ +#ifndef AA7383D0_F723_456B_A3A6_98A8737337D5 +#define AA7383D0_F723_456B_A3A6_98A8737337D5 + +#include +#include +#include +#include "tlsutils.h" + +class TLSServerContextPrivate { +public: + TLSServerContextPrivate(); + + void set_keypair(const std::string& cert, const std::string& key); + void set_ca(const std::string& ca); + void set_ocsp_staple(const std::string& ocsp); + void set_verify_client(); + void enable_dhe(); + void set_protocols(const std::string& protocols); + void set_ciphers(const std::string& ciphers); + void set_curves(const std::string& curves); + + [[nodiscard]] tls* get_context(); + +private: + std::unique_ptr m_context{tls_server(), &dispose_tls_context}; + std::unique_ptr m_config{tls_config_new(), &tls_config_free}; + bool m_configured = false; +}; + +#endif /* AA7383D0_F723_456B_A3A6_98A8737337D5 */ diff --git a/src/tlsutils.cpp b/src/tlsutils.cpp new file mode 100644 index 0000000..39259c9 --- /dev/null +++ b/src/tlsutils.cpp @@ -0,0 +1,8 @@ +#include "stdafx.h" +#include "tlsutils.h" + +void dispose_tls_context(tls* ctx) +{ + tls_close(ctx); + tls_free(ctx); +} diff --git a/src/tlsutils.h b/src/tlsutils.h new file mode 100644 index 0000000..2626e9a --- /dev/null +++ b/src/tlsutils.h @@ -0,0 +1,8 @@ +#ifndef A37AF032_C5EF_4C99_9772_3BFFC9B854AF +#define A37AF032_C5EF_4C99_9772_3BFFC9B854AF + +struct tls; + +void dispose_tls_context(tls* ctx); + +#endif /* A37AF032_C5EF_4C99_9772_3BFFC9B854AF */