diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index d4420369e..bb8c58927 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,10 +1,20 @@ { - "name": "FTL x86_64 Build Env", - "image": "ghcr.io/pi-hole/ftl-build:x86_64", - "extensions": [ - "jetmartin.bats", - "ms-vscode.cpptools", - "ms-vscode.cmake-tools", - "eamodio.gitlens" - ], -} \ No newline at end of file + "name": "FTL x86_64 Build Env", + "image": "ghcr.io/pi-hole/ftl-build:nightly", + "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ], + "customizations": { + "vscode": { + "extensions": [ + "jetmartin.bats", + "ms-vscode.cpptools", + "ms-vscode.cmake-tools", + "eamodio.gitlens" + ] + } + }, + "mounts": [ + "type=bind,source=/home/${localEnv:USER}/.ssh,target=/root/.ssh,readonly", + "type=bind,source=/var/www/html,target=/var/www/html,readonly" + ] + +} diff --git a/.github/.codespellignore b/.github/.codespellignore index ae1497894..0dd61bef4 100644 --- a/.github/.codespellignore +++ b/.github/.codespellignore @@ -2,5 +2,10 @@ ssudo tre ede nd +doubleclick requestor -requestors \ No newline at end of file +requestors +punycode +bitap +mmapped +dnsmasq diff --git a/.github/.codespellignore_lines b/.github/.codespellignore_lines new file mode 100644 index 000000000..a592a3953 --- /dev/null +++ b/.github/.codespellignore_lines @@ -0,0 +1,3 @@ + self.errors.append("Exception when GETing from FTL: " + str(e)) +// sitten -> sittin (substitution of "i" for "e"), +// sittin -> sitting (insertion of "g" at the end). diff --git a/.github/Dockerfile b/.github/Dockerfile new file mode 100644 index 000000000..046e45f66 --- /dev/null +++ b/.github/Dockerfile @@ -0,0 +1,30 @@ +FROM ghcr.io/pi-hole/ftl-build:v2.6 AS builder + +WORKDIR /app + +COPY . /app + +ARG CI_ARCH="linux/amd64" +ENV CI_ARCH ${CI_ARCH} +ARG GIT_BRANCH="test" +ENV GIT_BRANCH ${GIT_BRANCH} +ARG GIT_TAG="test" +ENV GIT_TAG ${GIT_TAG} +ARG BUILD_OPTS="" +ENV BUILD_OPTS ${BUILD_OPTS} + +# Build FTL +# Remove possible old build files +RUN rm -rf cmake && \ +# Build and test FTL + bash build.sh "-DSTATIC=${STATIC}" test ${BUILD_OPTS} && \ +# Move FTL binary to root directory + cd / &&\ + mv /app/pihole-FTL . && \ +# Create tarball of API docs + tar -C /app/src/api/docs/content/ -czvf /api-docs.tar.gz . + +# Create final image containing only the FTL binary and API docs +FROM scratch AS result +COPY --from=builder /pihole-FTL /pihole-FTL +COPY --from=builder /api-docs.tar.gz /api-docs.tar.gz diff --git a/.github/actions/build-and-test/action.yml b/.github/actions/build-and-test/action.yml new file mode 100644 index 000000000..ce073c59e --- /dev/null +++ b/.github/actions/build-and-test/action.yml @@ -0,0 +1,142 @@ +name: Build and test +description: Builds and tests FTL on all supported platforms + +inputs: + platform: + required: true + description: The platform to build for + build_opts: + required: true + description: Any extra build opts to use + git_branch: + required: true + description: The branch to build from + git_tag: + required: true + description: The tag to build from (if any) + bin_name: + required: true + description: The name of the binary to build + artifact_name: + required: true + description: The name of the artifact to upload + event_name: + required: true + description: The name of the event that triggered the workflow run + actor: + required: true + description: The name of the user or app that initiated the workflow run + target_dir: + required: true + description: The directory to deploy the artifacts to + # Secrets cannot be accessed in the action.yml file so we need to pass them as + # inputs to the action. + SSH_KEY: + required: true + description: The SSH private key to use for authentication + KNOWN_HOSTS: + required: true + description: The SSH known hosts file + SSH_USER: + required: true + description: The SSH user to use for authentication + SSH_HOST: + required: true + description: The SSH host to connect to + +# Both the definition of environment variables and checking out the code +# needs to be done outside of the composite action as +# - environment variables cannot be defined using inputs +# - the checkout action needs to be the first step in the workflow, otherwise we +# cannot use the composite action as the corresponding "action.yml" isn't +# there yet +runs: + using: "composite" + steps: + - + name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3.1.0 + - + name: Print directory contents + shell: bash + run: ls -l + - + name: Build and test FTL in ftl-build container (QEMU) + uses: Wandalen/wretry.action@v1.4.8 + with: + attempt_limit: 3 + action: docker/build-push-action@v5.0.0 + with: | + platforms: ${{ inputs.platform }} + pull: true + push: false + context: . + target: result + file: .github/Dockerfile + outputs: | + type=tar,dest=build.tar + build-args: | + "CI_ARCH=${{ inputs.platform }}" + "GIT_BRANCH=${{ inputs.git_branch }}" + "GIT_TAG=${{ inputs.git_tag }}" + "BUILD_OPTS=${{ inputs.build_opts }}" + - + name: List files in current directory + shell: bash + run: ls -l + - + name: Extract FTL binary from container + shell: bash + run: | + tar -xf build.tar pihole-FTL + - + name: "Generate checksum file" + shell: bash + run: | + mv pihole-FTL "${{ inputs.bin_name }}" + sha1sum pihole-FTL-* > ${{ inputs.bin_name }}.sha1 + - + name: Store binary artifacts for later deployoment + if: inputs.event_name != 'pull_request' + uses: actions/upload-artifact@v4.3.1 + with: + name: ${{ inputs.artifact_name }} + path: '${{ inputs.bin_name }}*' + - + name: Generate artifact attestation + uses: actions/attest-build-provenance@v1 + # Skip attestation if ACTIONS_ID_TOKEN_REQUEST_URL env variable is not + # available (e.g., PR originating from a fork) + if: ${{ env.ACTIONS_ID_TOKEN_REQUEST_URL != '' }} + with: + subject-path: ${{ inputs.bin_name }} + - + name: Extract documentation files from container + if: inputs.event_name != 'pull_request' && inputs.platform == 'linux/amd64' && inputs.build_opts == '' + shell: bash + run: | + tar -xf build.tar api-docs.tar.gz + - + name: Upload documentation artifacts for deployoment + if: inputs.event_name != 'pull_request' && inputs.platform == 'linux/amd64' && inputs.build_opts == '' + uses: actions/upload-artifact@v4.3.1 + with: + name: pihole-api-docs + path: 'api-docs.tar.gz' + - + name: Deploy + # Skip deployment step if: + # - this is a triggered by a PR event (we only push on commit to branch + # events) + # - no SSH key is provided (this is a PR from a fork) + if: inputs.event_name != 'pull_request' && ${{ inputs.SSH_KEY != '' }} + uses: ./.github/actions/deploy + with: + pattern: ${{ inputs.bin_name }}-binary + target_dir: ${{ inputs.target_dir }} + event_name: ${{ inputs.event_name }} + actor: ${{ inputs.actor }} + SSH_KEY: ${{ inputs.SSH_KEY }} + KNOWN_HOSTS: ${{ inputs.KNOWN_HOSTS }} + SSH_USER: ${{ inputs.SSH_USER }} + SSH_HOST: ${{ inputs.SSH_HOST }} diff --git a/.github/actions/deploy/action.yml b/.github/actions/deploy/action.yml new file mode 100644 index 000000000..1e5ceab22 --- /dev/null +++ b/.github/actions/deploy/action.yml @@ -0,0 +1,96 @@ +name: Deploy +description: Deploy the FTL binary and documentation + +inputs: + pattern: + required: true + description: The pattern to match the artifacts to download + target_dir: + required: true + description: The directory to deploy the artifacts to + event_name: + required: true + description: The name of the event that triggered the workflow run + actor: + required: true + description: The name of the user or app that initiated the workflow run + # Secrets cannot be accessed in the action.yml file so we need to pass them as + # inputs to the action. + SSH_KEY: + required: true + description: The SSH private key to use for authentication + KNOWN_HOSTS: + required: true + description: The SSH known hosts file + SSH_USER: + required: true + description: The SSH user to use for authentication + SSH_HOST: + required: true + description: The SSH host to connect to + +runs: + using: "composite" + steps: + - + name: Get binaries built in previous jobs + uses: actions/download-artifact@v4.1.4 + id: download + with: + path: ftl_builds/ + pattern: ${{ inputs.pattern }} + merge-multiple: true + - + name: Get documentation files built in previous jobs + if: inputs.pattern == 'pihole-FTL-amd64-binary' + uses: actions/download-artifact@v4.1.4 + with: + path: ftl_builds/ + name: pihole-api-docs + - + name: Display structure of downloaded files + shell: bash + run: ls -R + working-directory: ${{steps.download.outputs.download-path}} + - + name: Install SSH Key + uses: benoitchantre/setup-ssh-authentication-action@1.0.1 + with: + private-key: ${{ inputs.SSH_KEY }} + private-key-name: id_rsa + known-hosts: ${{ inputs.KNOWN_HOSTS }} + - + name: Set private key permissions + shell: bash + run: chmod 600 ~/.ssh/id_rsa + - + name: Untar documentation files + if: inputs.pattern == 'pihole-FTL-amd64-binary' + working-directory: ftl_builds/ + shell: bash + run: | + mkdir docs/ + tar xzvf api-docs.tar.gz -C docs/ + - + name: Display structure of files ready for upload + working-directory: ftl_builds/ + shell: bash + run: ls -R + - + name: Transfer Builds to Pi-hole server for pihole checkout + if: inputs.actor != 'dependabot[bot]' + env: + USER: ${{ inputs.SSH_USER }} + HOST: ${{ inputs.SSH_HOST }} + TARGET_DIR: ${{ inputs.target_dir }} + SOURCE_DIR: ftl_builds/ + shell: bash + run: | + bash ./deploy.sh + - + name: Attach binaries to release + if: inputs.event_name == 'release' + uses: softprops/action-gh-release@v2 + with: + files: | + ftl_builds/* diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 15a935516..e3d69c3b4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,5 +1,10 @@ name: Build, Test, Deploy +permissions: + id-token: write + contents: read + attestations: write + on: push: branches: @@ -7,6 +12,7 @@ on: pull_request: release: types: [published] + workflow_dispatch: jobs: smoke-tests: @@ -14,6 +20,7 @@ jobs: github.event_name == 'push' || github.event_name == 'release' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) + || github.event_name == 'workflow_dispatch' outputs: GIT_TAG: ${{ steps.variables.outputs.GIT_TAG }} @@ -24,7 +31,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3.6.0 + uses: actions/checkout@v4.1.7 - name: "Calculate required variables" id: variables @@ -48,123 +55,85 @@ jobs: [[ $FAIL == 1 ]] && exit 1 || echo "Branch name depth check passed." shell: bash - build: + gha: runs-on: ubuntu-latest - needs: smoke-tests - - container: ghcr.io/pi-hole/ftl-build:v1.26-${{ matrix.arch }} - strategy: fail-fast: false - matrix: include: - - arch: x86_64 - bin_name: pihole-FTL-linux-x86_64 - - arch: x86_64 - arch_extra: _full - bin_name: pihole-FTL-linux-x86_64_full - - arch: x86_64-musl - bin_name: pihole-FTL-musl-linux-x86_64 - - arch: x86_32 - bin_name: pihole-FTL-linux-x86_32 - - arch: armv4t - bin_name: pihole-FTL-armv4-linux-gnueabi - - arch: armv5te - bin_name: pihole-FTL-armv5-linux-gnueabi - - arch: armv6hf - bin_name: pihole-FTL-armv6-linux-gnueabihf - - arch: armv7hf - bin_name: pihole-FTL-armv7-linux-gnueabihf - - arch: armv8a - bin_name: pihole-FTL-armv8-linux-gnueabihf - - arch: aarch64 - bin_name: pihole-FTL-aarch64-linux-gnu - - arch: riscv64 - bin_name: pihole-FTL-riscv64-linux-gnu - + - platform: linux/amd64 + bin_name: pihole-FTL-amd64 + build_opts: "" + - platform: linux/amd64 + bin_name: pihole-FTL-amd64-clang + build_opts: clang + - platform: linux/386 + bin_name: pihole-FTL-386 + build_opts: "" + - platform: linux/riscv64 + bin_name: pihole-FTL-riscv64 + build_opts: "" env: - CI_ARCH: ${{ matrix.arch }}${{ matrix.arch_extra }} - + CI_ARCH: ${{ matrix.platform }} + GIT_BRANCH: ${{ needs.smoke-tests.outputs.GIT_BRANCH }} + GIT_TAG: ${{ needs.smoke-tests.outputs.GIT_TAG }} steps: - name: Checkout code - uses: actions/checkout@v3.6.0 - - - name: "Fix ownership of repository" - run: chown -R root . - - - name: "Fix ownership of repository" - run: chown -R root . + uses: actions/checkout@v4.1.7 - - name: "Build" - env: - GIT_BRANCH: ${{ needs.smoke-tests.outputs.GIT_BRANCH }} - GIT_TAG: ${{ needs.smoke-tests.outputs.GIT_TAG }} - run: | - bash build.sh "-DSTATIC=${STATIC}" - - - name: "Binary checks" - run: | - bash test/arch_test.sh - - - name: "Test x86_32/64 binaries" - if: matrix.arch == 'x86_64' || matrix.arch == 'x86_64-musl' || matrix.arch == 'x86_32' - run: | - bash test/run.sh - - - name: "Generate checksum file" - run: | - mv pihole-FTL "${{ matrix.bin_name }}" - sha1sum pihole-FTL-* > ${{ matrix.bin_name }}.sha1 - - - name: Upload artifacts to job for later processing - if: github.event_name != 'pull_request' - uses: actions/upload-artifact@v3.1.3 + name: Build and test and deploy FTL + uses: ./.github/actions/build-and-test with: - name: tmp-binary-storage - path: '${{ matrix.bin_name }}*' + platform: ${{ matrix.platform }} + bin_name: ${{ matrix.bin_name }} + build_opts: ${{ matrix.build_opts }} + artifact_name: ${{ matrix.bin_name }}-binary + target_dir: ${{ needs.smoke-tests.outputs.OUTPUT_DIR }} + git_branch: ${{ needs.smoke-tests.outputs.GIT_BRANCH }} + git_tag: ${{ needs.smoke-tests.outputs.GIT_TAG }} + event_name: ${{ github.event_name }} + actor: ${{ github.actor }} + SSH_KEY: ${{ secrets.SSH_KEY }} + KNOWN_HOSTS: ${{ secrets.KNOWN_HOSTS }} + SSH_USER: ${{ secrets.SSH_USER }} + SSH_HOST: ${{ secrets.SSH_HOST }} - deploy: - if: github.event_name != 'pull_request' - needs: [smoke-tests, build] - runs-on: ubuntu-latest + self-hosted: + runs-on: self-hosted + needs: smoke-tests + strategy: + fail-fast: false + matrix: + include: + - platform: linux/arm/v6 + bin_name: pihole-FTL-armv6 + - platform: linux/arm/v7 + bin_name: pihole-FTL-armv7 + - platform: linux/arm64/v8 + bin_name: pihole-FTL-arm64 + env: + CI_ARCH: ${{ matrix.platform }} + GIT_BRANCH: ${{ needs.smoke-tests.outputs.GIT_BRANCH }} + GIT_TAG: ${{ needs.smoke-tests.outputs.GIT_TAG }} steps: - name: Checkout code - uses: actions/checkout@v3.6.0 - - - name: Get Binaries built in previous jobs - uses: actions/download-artifact@v3.0.2 - id: download - with: - name: tmp-binary-storage - path: ftl-builds/ - - - name: Display structure of downloaded files - run: ls -R - working-directory: ${{steps.download.outputs.download-path}} - - - name: Install SSH Key - uses: benoitchantre/setup-ssh-authentication-action@1.0.1 - with: - private-key: ${{ secrets.SSH_KEY }} - known-hosts: ${{ secrets.KNOWN_HOSTS }} - - - name: Transfer Builds to Pi-hole server for pihole checkout - if: github.actor != 'dependabot[bot]' - env: - USER: ${{ secrets.SSH_USER }} - HOST: ${{ secrets.SSH_HOST }} - TARGET_DIR: ${{ needs.smoke-tests.outputs.OUTPUT_DIR }} - SOURCE_DIR: ${{ steps.download.outputs.download-path }} - run: | - bash ./deploy.sh + uses: actions/checkout@v4.1.7 - - name: Attach binaries to release - if: github.event_name == 'release' - uses: softprops/action-gh-release@v1 + name: Build and test and deploy FTL + uses: ./.github/actions/build-and-test with: - files: | - ${{ steps.download.outputs.download-path }}/* + platform: ${{ matrix.platform }} + bin_name: ${{ matrix.bin_name }} + artifact_name: ${{ matrix.bin_name }}-binary + target_dir: ${{ needs.smoke-tests.outputs.OUTPUT_DIR }} + git_branch: ${{ needs.smoke-tests.outputs.GIT_BRANCH }} + git_tag: ${{ needs.smoke-tests.outputs.GIT_TAG }} + event_name: ${{ github.event_name }} + actor: ${{ github.actor }} + SSH_KEY: ${{ secrets.SSH_KEY }} + KNOWN_HOSTS: ${{ secrets.KNOWN_HOSTS }} + SSH_USER: ${{ secrets.SSH_USER }} + SSH_HOST: ${{ secrets.SSH_HOST }} diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 93529cbb7..8ce5dfe2b 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -1,5 +1,8 @@ name: Codespell on: + push: + branches: + - '**' pull_request: types: [opened, synchronize, reopened, ready_for_review] @@ -10,10 +13,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3.6.0 + uses: actions/checkout@v4.1.7 - name: Spell-Checking uses: codespell-project/actions-codespell@master with: ignore_words_file: .github/.codespellignore - skip: ./src/database/sqlite3.c,./src/database/sqlite3.h,./src/database/shell.c,./src/lua,./src/dnsmasq,./src/tre-regex,./.git,./test/libs + skip: ./src/database/sqlite3.c,./src/database/sqlite3.h,./src/database/shell.c,./src/lua,./src/dnsmasq,./src/tre-regex,./.git,./test/libs,./src/webserver/civetweb,./src/zip/miniz,./src/api/docs/content/external + exclude_file: .github/.codespellignore_lines diff --git a/.github/workflows/merge-conflict.yml b/.github/workflows/merge-conflict.yml index 6722d665b..24b299fcb 100644 --- a/.github/workflows/merge-conflict.yml +++ b/.github/workflows/merge-conflict.yml @@ -9,11 +9,11 @@ on: types: [synchronize] jobs: - main: + merge-conflict: runs-on: ubuntu-latest steps: - name: Check if PRs are have merge conflicts - uses: eps1lon/actions-label-merge-conflict@v2.1.0 + uses: eps1lon/actions-label-merge-conflict@v3.0.2 with: dirtyLabel: "Merge conflicts" repoToken: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/openapi-validator.yml b/.github/workflows/openapi-validator.yml new file mode 100644 index 000000000..8827d074a --- /dev/null +++ b/.github/workflows/openapi-validator.yml @@ -0,0 +1,26 @@ +name: API validation + +on: [push] + +env: + CI: true + +jobs: + openapi-validator: + name: Node + runs-on: ubuntu-latest + + steps: + - name: Clone repository + uses: actions/checkout@v4.1.7 + + - name: Set Node.js version + uses: actions/setup-node@v4 + with: + node-version: "20" + + - name: Install npm dependencies + run: npm ci + + - name: Run tests + run: npm test diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 35abcc2c0..41e1793a7 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -40,7 +40,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3.6.0 + uses: actions/checkout@v4.1.7 - name: Remove 'stale' label run: gh issue edit ${{ github.event.issue.number }} --remove-label ${{ env.stale_label }} env: diff --git a/.github/workflows/sync-back-to-dev.yml b/.github/workflows/sync-back-to-dev.yml index 62acaabe6..e15d10494 100644 --- a/.github/workflows/sync-back-to-dev.yml +++ b/.github/workflows/sync-back-to-dev.yml @@ -11,7 +11,7 @@ jobs: name: Syncing branches steps: - name: Checkout - uses: actions/checkout@v3.6.0 + uses: actions/checkout@v4.1.7 - name: Opening pull request run: gh pr create -B development -H master --title 'Sync master back into development' --body 'Created by Github action' --label 'internal' env: diff --git a/.gitignore b/.gitignore index 878214691..a3c7c3179 100644 --- a/.gitignore +++ b/.gitignore @@ -1,25 +1,39 @@ # Generated binary -pihole-FTL +pihole-FTL* # Versioning files (generated by Makefile) -version* +version.h +version~ # CMake files generated during compilation /cmake/ +/cmake_ci/ /cmake-build-debug/ /cmake-build-release/ # IDE files .idea/ *.sw* -.vscode/ -/.vscode/ +.vscode/* +!.vscode/c_cpp_properties.json /build/ +# __pycache__ files (API tests) +__pycache__/ + +# When patch fails to apply a patch segment to the original file, it saves the +# temporary original file copy out durably as *.orig, dumps the rejected segment +# to *.rej, and continues trying to apply patch segments. +*.orig +*.rej + # MAC->Vendor database files tools/manuf.data tools/macvendor.db +# Documentation files generated by cmake +src/api/docs/hex + # Test dependencies /node_modules/ diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 000000000..901ccbdeb --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,18 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/src/**" + ], + "compileCommands": "${workspaceFolder}/build/compile_commands.json", + "defines": [], + "compilerPath": "/usr/bin/gcc", + "cStandard": "gnu17", + "cppStandard": "gnu++17", + "intelliSenseMode": "linux-gcc-x64", + "configurationProvider": "ms-vscode.cmake-tools" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 39c16d3a3..30e2d6245 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,9 +8,14 @@ # This file is copyright under the latest version of the EUPL. # Please see LICENSE file for your rights under this license. -cmake_minimum_required(VERSION 2.8.12) +# C17 supports requires minimum CMake version 3.21 +# GCC 8.1.0 +# LLVM Clang 7.0.0 +cmake_minimum_required(VERSION 3.21) +set(CMAKE_C_STANDARD 17) + project(PIHOLE_FTL C) -set(DNSMASQ_VERSION pi-hole-v2.90+1) +set(DNSMASQ_VERSION pi-hole-v2.90+2) add_subdirectory(src) diff --git a/build.sh b/build.sh index 169cbe044..2df0f8f1b 100755 --- a/build.sh +++ b/build.sh @@ -12,6 +12,10 @@ # Abort script if one command returns a non-zero value set -e +# Set builddir +builddir="cmake/" + +# Parse arguments for var in "$@" do case "${var}" in @@ -19,41 +23,66 @@ do "-C" | "CLEAN" ) clean=1 && nobuild=1;; "-i" | "install" ) install=1;; "-t" | "test" ) test=1;; + "clang" ) clang=1;; + "ci" ) builddir="cmake_ci/";; esac done # Prepare build environment if [[ -n "${clean}" ]]; then echo "Cleaning build environment" - rm -rf cmake/ + # Remove build directory + rm -rf "${builddir}" if [[ -n ${nobuild} ]]; then exit 0 fi fi +# Remove possibly outdated api/docs elements +for filename in src/api/docs/hex/* src/api/docs/hex/**/*; do + # Skip if not a file + if [ ! -f "${filename}" ]; then + continue + fi + + # Get the original filename + original_filename="${filename/"src/api/docs/hex/"/"src/api/docs/content/"}" + + # Remove the file if it is outdated + if [ "${filename}" -ot "${original_filename}" ]; then + rm "${filename}" + fi +done + # Remove compiled LUA scripts if older than the plain ones for scriptname in src/lua/scripts/*.lua; do if [ -f "${scriptname}.hex" ] && [ "${scriptname}.hex" -ot "${scriptname}" ]; then - echo "INFO: ${scriptname} is outdated and will be recompiled" rm "${scriptname}.hex" fi done +# Set compiler to clang if requested +if [[ -n "${clang}" ]]; then + export CC=clang + export CXX=clang++ + export STATIC="false" +fi + # Configure build, pass CMake CACHE entries if present # Wrap multiple options in "" as first argument to ./build.sh: # ./build.sh "-DA=1 -DB=2" install -mkdir -p cmake -cd cmake +mkdir -p "${builddir}" +cd "${builddir}" if [[ "${1}" == "-D"* ]]; then cmake "${1}" .. else cmake .. fi -# Build the sources +# Build the sources with the number of available cores cmake --build . -- -j $(nproc) -# If we are asked to install, we do this here +# If we are asked to install, we do this here (requires root privileges) # Otherwise, we simply copy the binary one level up if [[ -n "${install}" ]]; then echo "Installing pihole-FTL" @@ -64,7 +93,9 @@ else cp pihole-FTL ../ fi +# If we are asked to run tests, we do this here if [[ -n "${test}" ]]; then cd .. - ./test/run.sh + bash test/arch_test.sh + bash test/run.sh fi diff --git a/deploy.sh b/deploy.sh index 832502367..1c0a19b69 100755 --- a/deploy.sh +++ b/deploy.sh @@ -62,4 +62,8 @@ for dir in "${path[@]}"; do done sftp -r -b - "${USER}"@"${HOST}" <<< "cd ${old_path} +-mkdir ./docs +-mkdir ./docs/external +-mkdir ./docs/images +-mkdir ./docs/specs put ${SOURCE_DIR}/* ./" diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..3ac9ba290 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,851 @@ +{ + "name": "pihole-ftl", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "pihole-ftl", + "version": "1.0.0", + "license": "EUPL-1.2", + "devDependencies": { + "openapi-enforcer": "^1.13.1", + "openapi-examples-validator": "^4.2.1" + } + }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz", + "integrity": "sha512-GBD2Le9w2+lVFoc4vswGI/TjkNIZSVp7+9xPf+X3uidBfWnAeUWmquteSyt0+VCrhNMWj/FTABISQrD3Z/YA+w==", + "dev": true, + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.6", + "call-me-maybe": "^1.0.1", + "js-yaml": "^4.1.0" + } + }, + "node_modules/@apidevtools/json-schema-ref-parser/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/@apidevtools/json-schema-ref-parser/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/axios": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "dev": true, + "dependencies": { + "follow-redirects": "^1.14.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/call-me-maybe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", + "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/decimal.js": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.1.tgz", + "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==", + "dev": true + }, + "node_modules/errno": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/errno/-/errno-1.0.0.tgz", + "integrity": "sha512-3zV5mFS1E8/1bPxt/B0xxzI1snsg3uSCIh6Zo1qKg6iMw93hzPANk9oBFzSFBFrwuVoQuE3rLoouAUfwOAj1wQ==", + "dev": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreach": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.6.tgz", + "integrity": "sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==", + "dev": true + }, + "node_modules/format-util": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/format-util/-/format-util-1.0.5.tgz", + "integrity": "sha512-varLbTj0e0yVyRpqQhuWV+8hlePAgaoFRhNFj50BNjEIrw1/DphHSObtqwskVCPWNgzwPoQrZAbfa/SBiicNeg==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-pointer": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/json-pointer/-/json-pointer-0.6.2.tgz", + "integrity": "sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw==", + "dev": true, + "dependencies": { + "foreach": "^2.0.4" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, + "node_modules/lodash.flatmap": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.flatmap/-/lodash.flatmap-4.5.0.tgz", + "integrity": "sha1-74y/QI9uSCaGYzRTBcaswLd4cC4=", + "dev": true + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/ono": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/ono/-/ono-4.0.11.tgz", + "integrity": "sha512-jQ31cORBFE6td25deYeD80wxKBMj+zBmHTrVxnc6CKhx8gho6ipmWM5zj/oeoqioZ99yqBls9Z/9Nss7J26G2g==", + "dev": true, + "dependencies": { + "format-util": "^1.0.3" + } + }, + "node_modules/openapi-enforcer": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/openapi-enforcer/-/openapi-enforcer-1.13.1.tgz", + "integrity": "sha512-NDDmyonl3bgxP+RabJsTPxn8EEza4Aet5zEocfX6nP9LL0J52aNFMYxNHoAuqoY3zQcZG2pnm3DMi2gYiRYbQg==", + "dev": true, + "dependencies": { + "axios": "^0.21.1", + "json-schema-ref-parser": "^6.1.0" + } + }, + "node_modules/openapi-enforcer/node_modules/json-schema-ref-parser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-6.1.0.tgz", + "integrity": "sha512-pXe9H1m6IgIpXmE5JSb8epilNTGsmTb2iPohAXpOdhqGFbQjNeHHsZxU+C8w6T81GZxSPFLeUoqDJmzxx5IGuw==", + "deprecated": "Please switch to @apidevtools/json-schema-ref-parser", + "dev": true, + "dependencies": { + "call-me-maybe": "^1.0.1", + "js-yaml": "^3.12.1", + "ono": "^4.0.11" + } + }, + "node_modules/openapi-examples-validator": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/openapi-examples-validator/-/openapi-examples-validator-4.7.1.tgz", + "integrity": "sha512-/OZZHhJkiaMQhkVfD0vFNOrRCBhkOL+X4/uhC55CJH0giuLUPQgFB5/iU26DCq1sZo2j9L70XYPcdHoM4PS+Xw==", + "dev": true, + "dependencies": { + "ajv": "^6.12.6", + "ajv-oai": "1.2.1", + "commander": "^6.2.1", + "errno": "^1.0.0", + "glob": "^7.2.0", + "json-pointer": "^0.6.2", + "json-schema-ref-parser": "^9.0.9", + "jsonpath-plus": "^6.0.1", + "lodash.clonedeep": "^4.5.0", + "lodash.flatmap": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.merge": "^4.6.2", + "yaml": "^1.10.2" + }, + "bin": { + "openapi-examples-validator": "dist/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/openapi-examples-validator/node_modules/ajv-oai": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ajv-oai/-/ajv-oai-1.2.1.tgz", + "integrity": "sha512-gj7dnSdLyjWKid3uQI16u5wQNpkyqivjtCuvI4BWezeOzYTj5YHt4otH9GOBCaXY3FEbzQeWsp6C2qc18+BXDA==", + "dev": true, + "dependencies": { + "decimal.js": "^10.2.0" + }, + "peerDependencies": { + "ajv": "6.x" + } + }, + "node_modules/openapi-examples-validator/node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/openapi-examples-validator/node_modules/json-schema-ref-parser": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz", + "integrity": "sha512-qcP2lmGy+JUoQJ4DOQeLaZDqH9qSkeGCK3suKWxJXS82dg728Mn3j97azDMaOUmJAN4uCq91LdPx4K7E8F1a7Q==", + "deprecated": "Please switch to @apidevtools/json-schema-ref-parser", + "dev": true, + "dependencies": { + "@apidevtools/json-schema-ref-parser": "9.0.9" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/openapi-examples-validator/node_modules/jsonpath-plus": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-6.0.1.tgz", + "integrity": "sha512-EvGovdvau6FyLexFH2OeXfIITlgIbgZoAZe3usiySeaIDm5QS+A10DKNpaPBBqqRSZr2HN6HVNXxtwUAr2apEw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "dev": true + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + } + }, + "dependencies": { + "@apidevtools/json-schema-ref-parser": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz", + "integrity": "sha512-GBD2Le9w2+lVFoc4vswGI/TjkNIZSVp7+9xPf+X3uidBfWnAeUWmquteSyt0+VCrhNMWj/FTABISQrD3Z/YA+w==", + "dev": true, + "requires": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.6", + "call-me-maybe": "^1.0.1", + "js-yaml": "^4.1.0" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + } + } + }, + "@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "dev": true + }, + "@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "axios": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "dev": true, + "requires": { + "follow-redirects": "^1.14.0" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "call-me-maybe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", + "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "decimal.js": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.1.tgz", + "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==", + "dev": true + }, + "errno": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/errno/-/errno-1.0.0.tgz", + "integrity": "sha512-3zV5mFS1E8/1bPxt/B0xxzI1snsg3uSCIh6Zo1qKg6iMw93hzPANk9oBFzSFBFrwuVoQuE3rLoouAUfwOAj1wQ==", + "dev": true, + "requires": { + "prr": "~1.0.1" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "dev": true + }, + "foreach": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.6.tgz", + "integrity": "sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==", + "dev": true + }, + "format-util": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/format-util/-/format-util-1.0.5.tgz", + "integrity": "sha512-varLbTj0e0yVyRpqQhuWV+8hlePAgaoFRhNFj50BNjEIrw1/DphHSObtqwskVCPWNgzwPoQrZAbfa/SBiicNeg==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "json-pointer": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/json-pointer/-/json-pointer-0.6.2.tgz", + "integrity": "sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw==", + "dev": true, + "requires": { + "foreach": "^2.0.4" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, + "lodash.flatmap": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.flatmap/-/lodash.flatmap-4.5.0.tgz", + "integrity": "sha1-74y/QI9uSCaGYzRTBcaswLd4cC4=", + "dev": true + }, + "lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", + "dev": true + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "ono": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/ono/-/ono-4.0.11.tgz", + "integrity": "sha512-jQ31cORBFE6td25deYeD80wxKBMj+zBmHTrVxnc6CKhx8gho6ipmWM5zj/oeoqioZ99yqBls9Z/9Nss7J26G2g==", + "dev": true, + "requires": { + "format-util": "^1.0.3" + } + }, + "openapi-enforcer": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/openapi-enforcer/-/openapi-enforcer-1.13.1.tgz", + "integrity": "sha512-NDDmyonl3bgxP+RabJsTPxn8EEza4Aet5zEocfX6nP9LL0J52aNFMYxNHoAuqoY3zQcZG2pnm3DMi2gYiRYbQg==", + "dev": true, + "requires": { + "axios": "^0.21.1", + "json-schema-ref-parser": "^6.1.0" + }, + "dependencies": { + "json-schema-ref-parser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-6.1.0.tgz", + "integrity": "sha512-pXe9H1m6IgIpXmE5JSb8epilNTGsmTb2iPohAXpOdhqGFbQjNeHHsZxU+C8w6T81GZxSPFLeUoqDJmzxx5IGuw==", + "dev": true, + "requires": { + "call-me-maybe": "^1.0.1", + "js-yaml": "^3.12.1", + "ono": "^4.0.11" + } + } + } + }, + "openapi-examples-validator": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/openapi-examples-validator/-/openapi-examples-validator-4.7.1.tgz", + "integrity": "sha512-/OZZHhJkiaMQhkVfD0vFNOrRCBhkOL+X4/uhC55CJH0giuLUPQgFB5/iU26DCq1sZo2j9L70XYPcdHoM4PS+Xw==", + "dev": true, + "requires": { + "ajv": "^6.12.6", + "ajv-oai": "1.2.1", + "commander": "^6.2.1", + "errno": "^1.0.0", + "glob": "^7.2.0", + "json-pointer": "^0.6.2", + "json-schema-ref-parser": "^9.0.9", + "jsonpath-plus": "^6.0.1", + "lodash.clonedeep": "^4.5.0", + "lodash.flatmap": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.merge": "^4.6.2", + "yaml": "^1.10.2" + }, + "dependencies": { + "ajv-oai": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ajv-oai/-/ajv-oai-1.2.1.tgz", + "integrity": "sha512-gj7dnSdLyjWKid3uQI16u5wQNpkyqivjtCuvI4BWezeOzYTj5YHt4otH9GOBCaXY3FEbzQeWsp6C2qc18+BXDA==", + "dev": true, + "requires": { + "decimal.js": "^10.2.0" + } + }, + "commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true + }, + "json-schema-ref-parser": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz", + "integrity": "sha512-qcP2lmGy+JUoQJ4DOQeLaZDqH9qSkeGCK3suKWxJXS82dg728Mn3j97azDMaOUmJAN4uCq91LdPx4K7E8F1a7Q==", + "dev": true, + "requires": { + "@apidevtools/json-schema-ref-parser": "9.0.9" + } + }, + "jsonpath-plus": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-6.0.1.tgz", + "integrity": "sha512-EvGovdvau6FyLexFH2OeXfIITlgIbgZoAZe3usiySeaIDm5QS+A10DKNpaPBBqqRSZr2HN6HVNXxtwUAr2apEw==", + "dev": true + } + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 000000000..c99272f9a --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "pihole-ftl", + "private": true, + "version": "1.0.0", + "description": "Source of the Pi-hole FTL daemon", + "main": "", + "repository": { + "type": "git", + "url": "git+https://github.com/pi-hole/FTL.git" + }, + "keywords": [], + "author": "", + "license": "EUPL-1.2", + "bugs": { + "url": "https://github.com/pi-hole/FTL/issues" + }, + "scripts": { + "openapi-enforcer": "node test/api/openapi-enforcer.js", + "validate-examples": "openapi-examples-validator src/api/docs/content/specs/main.yaml", + "test": "npm run openapi-enforcer && npm run validate-examples" + }, + "devDependencies": { + "openapi-enforcer": "^1.13.1", + "openapi-examples-validator": "^4.2.1" + } +} diff --git a/patch/civetweb.sh b/patch/civetweb.sh new file mode 100644 index 000000000..f33dc8de0 --- /dev/null +++ b/patch/civetweb.sh @@ -0,0 +1,13 @@ +#!/bin/sh +set -e + +patch -p1 < patch/civetweb/0001-add-pihole-mods.patch +patch -p1 < patch/civetweb/0001-Add-NO_DLOPEN-option-to-civetweb-s-LUA-routines.patch +patch -p1 < patch/civetweb/0001-Always-Kepler-syntax-for-Lua-server-pages.patch +patch -p1 < patch/civetweb/0001-Add-FTL-URI-rewriting-changes-to-CivetWeb.patch +patch -p1 < patch/civetweb/0001-Add-mbedTLS-debug-logging-hook.patch +patch -p1 < patch/civetweb/0001-Register-CSRF-token-in-conn-request_info.patch +patch -p1 < patch/civetweb/0001-Log-debug-messages-to-webserver.log-when-debug.webse.patch +patch -p1 < patch/civetweb/0001-Allow-extended-ASCII-characters-in-URIs.patch + +echo "ALL PATCHES APPLIED OKAY" diff --git a/patch/civetweb/0001-Add-FTL-URI-rewriting-changes-to-CivetWeb.patch b/patch/civetweb/0001-Add-FTL-URI-rewriting-changes-to-CivetWeb.patch new file mode 100644 index 000000000..f54397919 --- /dev/null +++ b/patch/civetweb/0001-Add-FTL-URI-rewriting-changes-to-CivetWeb.patch @@ -0,0 +1,41 @@ +From 44a0a3a27731bb8577f39743eb034c024be48df2 Mon Sep 17 00:00:00 2001 +From: DL6ER +Date: Thu, 25 May 2023 18:02:13 +0200 +Subject: [PATCH] Add FTL URI rewriting changes to CivetWeb + +Signed-off-by: DL6ER +--- + src/webserver/civetweb/civetweb.c | 2 ++ + src/webserver/civetweb/civetweb.h | 3 +++ + 2 files changed, 5 insertions(+) + +diff --git a/src/webserver/civetweb/civetweb.c b/src/webserver/civetweb/civetweb.c +index 0d293f1f..44f6cf3d 100644 +--- a/src/webserver/civetweb/civetweb.c ++++ b/src/webserver/civetweb/civetweb.c +@@ -7754,6 +7754,8 @@ interpret_uri(struct mg_connection *conn, /* in/out: request (must be valid) */ + mg_snprintf( + conn, &truncated, filename, filename_buf_len - 1, "%s%s", root, uri); + ++ FTL_rewrite_pattern(filename, filename_buf_len - 1, root, uri); ++ + if (truncated) { + goto interpret_cleanup; + } +diff --git a/src/webserver/civetweb/civetweb.h b/src/webserver/civetweb/civetweb.h +index e71dfedc..2ad76693 100644 +--- a/src/webserver/civetweb/civetweb.h ++++ b/src/webserver/civetweb/civetweb.h +@@ -935,6 +935,9 @@ int my_send_http_error_headers(struct mg_connection *conn, + int status, const char* mime_type, + long long content_length); + ++void FTL_rewrite_pattern(char *filename, size_t filename_buf_len, ++ const char *root, const char *uri); ++ + // Buffer used for additional "Set-Cookie" headers + #define PIHOLE_HEADERS_MAXLEN 1024 + extern char pi_hole_extra_headers[PIHOLE_HEADERS_MAXLEN]; +-- +2.34.1 + diff --git a/patch/civetweb/0001-Add-NO_DLOPEN-option-to-civetweb-s-LUA-routines.patch b/patch/civetweb/0001-Add-NO_DLOPEN-option-to-civetweb-s-LUA-routines.patch new file mode 100644 index 000000000..ee48ec546 --- /dev/null +++ b/patch/civetweb/0001-Add-NO_DLOPEN-option-to-civetweb-s-LUA-routines.patch @@ -0,0 +1,35 @@ +From 1b81285fed48df6939d4b2569bba9e572f4c1137 Mon Sep 17 00:00:00 2001 +From: DL6ER +Date: Fri, 13 Jan 2023 21:37:31 +0100 +Subject: [PATCH] Add NO_DLOPEN option to civetweb's LUA routines + +Signed-off-by: DL6ER +--- + src/webserver/civetweb/mod_lua.inl | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/webserver/civetweb/mod_lua.inl b/src/webserver/civetweb/mod_lua.inl +index 5cc94318..59c4f2b3 100644 +--- a/src/webserver/civetweb/mod_lua.inl ++++ b/src/webserver/civetweb/mod_lua.inl +@@ -3634,7 +3634,7 @@ lua_init_optional_libraries(void) + lua_shared_init(); + + /* UUID library */ +-#if !defined(_WIN32) ++#if !defined(_WIN32) && !defined(NO_DLOPEN) + lib_handle_uuid = dlopen("libuuid.so", RTLD_LAZY); + pf_uuid_generate.p = + (lib_handle_uuid ? dlsym(lib_handle_uuid, "uuid_generate") : 0); +@@ -3648,7 +3648,7 @@ static void + lua_exit_optional_libraries(void) + { + /* UUID library */ +-#if !defined(_WIN32) ++#if !defined(_WIN32) && !defined(NO_DLOPEN) + if (lib_handle_uuid) { + dlclose(lib_handle_uuid); + } +-- +2.34.1 + diff --git a/patch/civetweb/0001-Add-mbedTLS-debug-logging-hook.patch b/patch/civetweb/0001-Add-mbedTLS-debug-logging-hook.patch new file mode 100644 index 000000000..ba7e8b2a8 --- /dev/null +++ b/patch/civetweb/0001-Add-mbedTLS-debug-logging-hook.patch @@ -0,0 +1,44 @@ +From f785e181f8b43fa9f77bf7dcc6711f16206c9e89 Mon Sep 17 00:00:00 2001 +From: DL6ER +Date: Thu, 25 May 2023 18:26:45 +0200 +Subject: [PATCH] Add mbedTLS debug logging hook + +Signed-off-by: DL6ER +--- + src/webserver/civetweb/civetweb.h | 4 ++++ + src/webserver/civetweb/mod_mbedtls.inl | 4 ++++ + 2 files changed, 8 insertions(+) + +diff --git a/src/webserver/civetweb/civetweb.h b/src/webserver/civetweb/civetweb.h +index 2ad76693..52724199 100644 +--- a/src/webserver/civetweb/civetweb.h ++++ b/src/webserver/civetweb/civetweb.h +@@ -938,6 +938,10 @@ int my_send_http_error_headers(struct mg_connection *conn, + void FTL_rewrite_pattern(char *filename, size_t filename_buf_len, + const char *root, const char *uri); + ++#define MG_CONFIG_MBEDTLS_DEBUG 3 ++void FTL_mbed_debug(void *user_param, int level, const char *file, ++ int line, const char *message); ++ + // Buffer used for additional "Set-Cookie" headers + #define PIHOLE_HEADERS_MAXLEN 1024 + extern char pi_hole_extra_headers[PIHOLE_HEADERS_MAXLEN]; +diff --git a/src/webserver/civetweb/mod_mbedtls.inl b/src/webserver/civetweb/mod_mbedtls.inl +index e72685f4..00b9280a 100644 +--- a/src/webserver/civetweb/mod_mbedtls.inl ++++ b/src/webserver/civetweb/mod_mbedtls.inl +@@ -83,6 +83,10 @@ mbed_sslctx_init(SSL_CTX *ctx, const char *crt) + mbedtls_ssl_conf_dbg(conf, mbed_debug, (void *)ctx); + #endif + ++ /****************** Pi-hole change ******************/ ++ mbedtls_ssl_conf_dbg(conf, FTL_mbed_debug, NULL); ++ /****************************************************/ ++ + #ifdef MBEDTLS_SSL_PROTO_TLS1_3 + psa_status_t status = psa_crypto_init(); + if (status != PSA_SUCCESS) { +-- +2.34.1 + diff --git a/patch/civetweb/0001-Allow-extended-ASCII-characters-in-URIs.patch b/patch/civetweb/0001-Allow-extended-ASCII-characters-in-URIs.patch new file mode 100644 index 000000000..d54ab29e3 --- /dev/null +++ b/patch/civetweb/0001-Allow-extended-ASCII-characters-in-URIs.patch @@ -0,0 +1,35 @@ +From ebb27741b10ed2eac51ac356708800ae96cdd17a Mon Sep 17 00:00:00 2001 +From: DL6ER +Date: Tue, 31 Oct 2023 08:35:31 +0100 +Subject: [PATCH] Allow extended ASCII characters in URIs + +Signed-off-by: DL6ER +--- + src/webserver/civetweb/civetweb.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/webserver/civetweb/civetweb.c b/src/webserver/civetweb/civetweb.c +index 9b0c6308..5320c4d4 100644 +--- a/src/webserver/civetweb/civetweb.c ++++ b/src/webserver/civetweb/civetweb.c +@@ -10734,7 +10734,7 @@ skip_to_end_of_word_and_terminate(char **ppw, int eol) + { + /* Forward until a space is found - use isgraph here */ + /* See http://www.cplusplus.com/reference/cctype/ */ +- while (isgraph((unsigned char)**ppw)) { ++ while ((unsigned char)**ppw > 127 || isgraph((unsigned char)**ppw)) { + (*ppw)++; + } + +@@ -18473,7 +18473,7 @@ get_uri_type(const char *uri) + * and % encoded symbols. + */ + for (i = 0; uri[i] != 0; i++) { +- if (uri[i] < 33) { ++ if ((unsigned char)uri[i] < 33) { + /* control characters and spaces are invalid */ + return 0; + } +-- +2.34.1 + diff --git a/patch/civetweb/0001-Always-Kepler-syntax-for-Lua-server-pages.patch b/patch/civetweb/0001-Always-Kepler-syntax-for-Lua-server-pages.patch new file mode 100644 index 000000000..48118db8f --- /dev/null +++ b/patch/civetweb/0001-Always-Kepler-syntax-for-Lua-server-pages.patch @@ -0,0 +1,31 @@ +From 19efd3e2e10858b549404e6cae97c20337aafb0b Mon Sep 17 00:00:00 2001 +From: DL6ER +Date: Mon, 22 May 2023 19:11:44 +0200 +Subject: [PATCH] Always Kepler syntax for Lua server pages + +Signed-off-by: DL6ER +--- + src/webserver/civetweb/mod_lua.inl | 7 +++---- + 1 file changed, 3 insertions(+), 4 deletions(-) + +diff --git a/src/webserver/civetweb/mod_lua.inl b/src/webserver/civetweb/mod_lua.inl +index 26f281ee..e9a13835 100644 +--- a/src/webserver/civetweb/mod_lua.inl ++++ b/src/webserver/civetweb/mod_lua.inl +@@ -3208,10 +3208,9 @@ handle_lsp_request(struct mg_connection *conn, + * " +Date: Sat, 14 Oct 2023 15:39:21 +0200 +Subject: [PATCH] Log debug messages to webserver.log when debug.webserver is + true + +Signed-off-by: DL6ER +--- + src/webserver/civetweb/civetweb.c | 5 +++-- + src/webserver/civetweb/mod_mbedtls.inl | 4 ++-- + 2 files changed, 5 insertions(+), 4 deletions(-) + +diff --git a/src/webserver/civetweb/civetweb.c b/src/webserver/civetweb/civetweb.c +index 3df8eab9..9b0c6308 100644 +--- a/src/webserver/civetweb/civetweb.c ++++ b/src/webserver/civetweb/civetweb.c +@@ -239,9 +239,10 @@ static void DEBUG_TRACE_FUNC(const char *func, + #endif + + #else ++#include "log.h" + #define DEBUG_TRACE(fmt, ...) \ +- do { \ +- } while (0) ++ if(debug_flags[DEBUG_WEBSERVER]) {\ ++ log_web("DEBUG: " fmt " (%s:%d)", ##__VA_ARGS__, short_path(__FILE__), __LINE__); } + #endif /* DEBUG */ + #endif /* DEBUG_TRACE */ + +diff --git a/src/webserver/civetweb/mod_mbedtls.inl b/src/webserver/civetweb/mod_mbedtls.inl +index 00b9280a..6a450ba3 100644 +--- a/src/webserver/civetweb/mod_mbedtls.inl ++++ b/src/webserver/civetweb/mod_mbedtls.inl +@@ -213,7 +213,7 @@ mbed_ssl_accept(mbedtls_ssl_context **ssl, + return -1; + } + +- DEBUG_TRACE("TLS connection %p accepted, state: %d", ssl, (*ssl)->state); ++ DEBUG_TRACE("TLS connection %p accepted, state: %d", ssl, (*ssl)->MBEDTLS_PRIVATE(state)); + return 0; + } + +@@ -239,7 +239,7 @@ mbed_ssl_handshake(mbedtls_ssl_context *ssl) + } + } + +- DEBUG_TRACE("TLS handshake rc: %d, state: %d", rc, ssl->state); ++ DEBUG_TRACE("TLS handshake rc: %d, state: %d", rc, ssl->MBEDTLS_PRIVATE(state)); + return rc; + } + +-- +2.34.1 + diff --git a/patch/civetweb/0001-Register-CSRF-token-in-conn-request_info.patch b/patch/civetweb/0001-Register-CSRF-token-in-conn-request_info.patch new file mode 100644 index 000000000..575c5649f --- /dev/null +++ b/patch/civetweb/0001-Register-CSRF-token-in-conn-request_info.patch @@ -0,0 +1,58 @@ +From 4890e1c2586d4a74a3925ede8cfeb1805672a42e Mon Sep 17 00:00:00 2001 +From: DL6ER +Date: Sat, 3 Jun 2023 20:52:02 +0200 +Subject: [PATCH] Register CSRF token and is_authenticated boolean in conn->request_info + +Signed-off-by: DL6ER +--- + src/webserver/civetweb/civetweb.c | 3 +++ + src/webserver/civetweb/civetweb.h | 2 ++ + src/webserver/civetweb/mod_lua.inl | 3 +++ + 3 files changed, 8 insertions(+) + +diff --git a/src/webserver/civetweb/civetweb.c b/src/webserver/civetweb/civetweb.c +index 233b342a..f44b17ba 100644 +--- a/src/webserver/civetweb/civetweb.c ++++ b/src/webserver/civetweb/civetweb.c +@@ -17760,6 +17760,9 @@ reset_per_request_attributes(struct mg_connection *conn) + } + conn->request_info.local_uri = NULL; + ++ /* Pi-hole addition */ ++ memset(conn->request_info.csrf_token, 0, sizeof(conn->request_info.csrf_token)); ++ reg_boolean(L, "is_authenticated", conn->request_info.is_authenticated != 0); ++ + #if defined(USE_SERVER_STATS) + conn->processing_time = 0; + #endif +diff --git a/src/webserver/civetweb/civetweb.h b/src/webserver/civetweb/civetweb.h +index 5b3d596b..291ef683 100644 +--- a/src/webserver/civetweb/civetweb.h ++++ b/src/webserver/civetweb/civetweb.h +@@ -183,6 +183,8 @@ struct mg_request_info { + + const char *acceptedWebSocketSubprotocol; /* websocket subprotocol, + * accepted during handshake */ ++ /* Pi-hole modification */ ++ char csrf_token[32]; ++ int is_authenticated; + }; + + +diff --git a/src/webserver/civetweb/mod_lua.inl b/src/webserver/civetweb/mod_lua.inl +index e9a13835..92066b3f 100644 +--- a/src/webserver/civetweb/mod_lua.inl ++++ b/src/webserver/civetweb/mod_lua.inl +@@ -2603,6 +2603,9 @@ prepare_lua_request_info_inner(const struct mg_connection *conn, lua_State *L) + reg_string(L, "finger", conn->request_info.client_cert->finger); + lua_rawset(L, -3); + } ++ ++ /* Pi-hole addition */ ++ reg_string(L, "csrf_token", conn->request_info.csrf_token); + } + + +-- +2.34.1 + diff --git a/patch/civetweb/0001-add-pihole-mods.patch b/patch/civetweb/0001-add-pihole-mods.patch new file mode 100644 index 000000000..9548eebaf --- /dev/null +++ b/patch/civetweb/0001-add-pihole-mods.patch @@ -0,0 +1,103 @@ +From f59235a1973d13d1271b50546c28cf4d9d2f921a Mon Sep 17 00:00:00 2001 +From: DL6ER +Date: Sat, 31 Dec 2022 07:08:23 +0100 +Subject: [PATCH] Add Pi-hole specific method to send HTTP headers + +Signed-off-by: DL6ER +--- + src/webserver/civetweb/civetweb.c | 53 +++++++++++++++++++++++++++++++++++++++++ + src/webserver/civetweb/civetweb.h | 11 +++++++++ + 2 files changed, 64 insertions(+) + +diff --git a/src/webserver/civetweb/civetweb.c b/src/webserver/civetweb/civetweb.c +index 81f642be..ed360a76 100644 +--- a/src/webserver/civetweb/civetweb.c ++++ b/src/webserver/civetweb/civetweb.c +@@ -4135,6 +4135,14 @@ send_additional_header(struct mg_connection *conn) + if (header && header[0]) { + mg_response_header_add_lines(conn, header); + } ++ ++ /*************** Pi-hole modification ****************/ ++ if (pi_hole_extra_headers[0] != '\0') { ++ mg_response_header_add_lines(conn, pi_hole_extra_headers); ++ // Invalidate extra headers after having sent them to avoid repetitions ++ pi_hole_extra_headers[0] = '\0'; ++ } ++ /*****************************************************/ + } + + +@@ -4530,6 +4538,48 @@ mg_send_http_error_impl(struct mg_connection *conn, + } + + ++/************************************** Pi-hole method **************************************/ ++CIVETWEB_API int ++my_send_http_error_headers(struct mg_connection *conn, ++ int status, const char* mime_type, ++ long long content_length) ++{ ++ if ((mime_type == NULL) || (*mime_type == 0)) { ++ /* No content type defined: default to text/html */ ++ mime_type = "text/html"; ++ } ++ ++ mg_response_header_start(conn, status); ++ send_no_cache_header(conn); ++ send_additional_header(conn); ++ mg_response_header_add(conn, "Content-Type", mime_type, -1); ++ if (content_length < 0) { ++ /* Size not known. Use chunked encoding (HTTP/1.x) */ ++ if (conn->protocol_type == PROTOCOL_TYPE_HTTP1) { ++ /* Only HTTP/1.x defines "chunked" encoding, HTTP/2 does not*/ ++ mg_response_header_add(conn, "Transfer-Encoding", "chunked", -1); ++ } ++ } else { ++ char len[32]; ++ int trunc = 0; ++ mg_snprintf(conn, ++ &trunc, ++ len, ++ sizeof(len), ++ "%" UINT64_FMT, ++ (uint64_t)content_length); ++ if (!trunc) { ++ /* Since 32 bytes is enough to hold any 64 bit decimal number, ++ * !trunc is always true */ ++ mg_response_header_add(conn, "Content-Length", len, -1); ++ } ++ } ++ mg_response_header_send(conn); ++ ++ return 0; ++} ++/********************************************************************************************/ ++ + CIVETWEB_API int + mg_send_http_error(struct mg_connection *conn, int status, const char *fmt, ...) + { +diff --git a/src/webserver/civetweb/civetweb.h b/src/webserver/civetweb/civetweb.h +index 7ea45fb2..f879ff3e 100644 +--- a/src/webserver/civetweb/civetweb.h ++++ b/src/webserver/civetweb/civetweb.h +@@ -963,6 +964,16 @@ CIVETWEB_API int mg_send_http_error(struct mg_connection *conn, + PRINTF_FORMAT_STRING(const char *fmt), + ...) PRINTF_ARGS(3, 4); + ++/************************************** Pi-hole method **************************************/ ++int my_send_http_error_headers(struct mg_connection *conn, ++ int status, const char* mime_type, ++ long long content_length); ++ ++// Buffer used for additional "Set-Cookie" headers ++#define PIHOLE_HEADERS_MAXLEN 1024 ++extern char pi_hole_extra_headers[PIHOLE_HEADERS_MAXLEN]; ++/********************************************************************************************/ ++ + + /* Send "HTTP 200 OK" response header. + * After calling this function, use mg_write or mg_send_chunk to send the +-- +2.34.1 + diff --git a/patch/lua.sh b/patch/lua.sh index 478d45810..9987b2226 100644 --- a/patch/lua.sh +++ b/patch/lua.sh @@ -2,3 +2,5 @@ set -e patch -p1 < patch/lua/0001-add-pihole-library.patch + +echo "ALL PATCHES APPLIED OKAY" diff --git a/patch/sqlite3.sh b/patch/sqlite3.sh index 9b447217c..5b984a3e7 100644 --- a/patch/sqlite3.sh +++ b/patch/sqlite3.sh @@ -3,3 +3,5 @@ set -e patch -p1 < patch/sqlite3/0001-print-FTL-version-in-interactive-shell.patch patch -p1 < patch/sqlite3/0002-make-sqlite3ErrName-public.patch + +echo "ALL PATCHES APPLIED OKAY" diff --git a/patch/sqlite3/0001-print-FTL-version-in-interactive-shell.patch b/patch/sqlite3/0001-print-FTL-version-in-interactive-shell.patch index 02ed43bfd..c7aaa2923 100644 --- a/patch/sqlite3/0001-print-FTL-version-in-interactive-shell.patch +++ b/patch/sqlite3/0001-print-FTL-version-in-interactive-shell.patch @@ -7,7 +7,7 @@ index 6280ebf6..a5e82f70 100644 #include #include +// print_FTL_version() -+#include "../log.h" ++#include "log.h" #if !defined(_WIN32) && !defined(WIN32) # include @@ -25,6 +25,6 @@ index 6280ebf6..a5e82f70 100644 char *zHistory; int nHistory; + print_FTL_version(); - printf( - "SQLite version %s %.19s\n" /*extra-version-info*/ - "Enter \".help\" for usage hints.\n", + #if CIO_WIN_WC_XLATE + # define SHELL_CIO_CHAR_SET (stdout_is_console? " (UTF-16 console I/O)" : "") + #else diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index cee7b1b32..4fb40e3df 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -8,8 +8,6 @@ # This file is copyright under the latest version of the EUPL. # Please see LICENSE file for your rights under this license. -set(CMAKE_C_STANDARD 11) - # Default to a release with debug info build if (NOT EXISTS ${CMAKE_BINARY_DIR}/CMakeCache.txt) if (NOT CMAKE_BUILD_TYPE) @@ -24,13 +22,19 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) # SQLITE_DEFAULT_MEMSTATUS=0: This setting causes the sqlite3_status() interfaces that track memory usage to be disabled. This helps the sqlite3_malloc() routines run much faster, and since SQLite uses sqlite3_malloc() internally, this helps to make the entire library faster. # SQLITE_OMIT_DEPRECATED: Omitting deprecated interfaces and features will not help SQLite to run any faster. It will reduce the library footprint, however. And it is the right thing to do. # SQLITE_OMIT_PROGRESS_CALLBACK: The progress handler callback counter must be checked in the inner loop of the bytecode engine. By omitting this interface, a single conditional is removed from the inner loop of the bytecode engine, helping SQL statements to run slightly faster. +# SQLITE_OMIT_SHARED_CACHE: This option builds SQLite without support for shared cache mode. The sqlite3_enable_shared_cache() is omitted along with a fair amount of logic within the B-Tree subsystem associated with shared cache management. This compile-time option is recommended most applications as it results in improved performance and reduced library footprint. # SQLITE_DEFAULT_FOREIGN_KEYS=1: This macro determines whether enforcement of foreign key constraints is enabled or disabled by default for new database connections. # SQLITE_DQS=0: This setting disables the double-quoted string literal misfeature. # SQLITE_ENABLE_DBPAGE_VTAB: Enables the SQLITE_DBPAGE virtual table. Warning: writing to the SQLITE_DBPAGE virtual table can very easily cause unrecoverably database corruption. -# SQLITE_OMIT_DESERIALIZE: This option causes the the sqlite3_serialize() and sqlite3_deserialize() interfaces to be omitted from the build (was the default before 3.36.0) -# HAVE_READLINE: Enable readline support to allow easy editing, history and auto-completion +# SQLITE_TEMP_STORE=2: Store temporary tables in memory for reduced IO and higher performance (can be overwritten by the user at runtime). # SQLITE_DEFAULT_CACHE_SIZE=-16384: Allow up to 16 MiB of cache to be used by SQLite3 (default is 2000 kiB) -set(SQLITE_DEFINES "-DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_PROGRESS_CALLBACK -DSQLITE_DEFAULT_FOREIGN_KEYS=1 -DSQLITE_DQS=0 -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_OMIT_DESERIALIZE -DHAVE_READLINE -DSQLITE_DEFAULT_CACHE_SIZE=-16384") +# SQLITE_DEFAULT_SYNCHRONOUS=1: Use normal synchronous mode (default is 2) +# SQLITE_LIKE_DOESNT_MATCH_BLOBS: This option causes the LIKE operator to only match BLOB values against BLOB values and TEXT values against TEXT values. This compile-time option makes SQLite run more efficiently when processing queries that use the LIKE operator. +# HAVE_MALLOC_USABLE_SIZE: This option causes SQLite to try to use the malloc_usable_size() function to obtain the actual size of memory allocations from the underlying malloc() system interface. Applications are encouraged to use HAVE_MALLOC_USABLE_SIZE whenever possible. +# HAVE_FDATASYNC: This option causes SQLite to try to use the fdatasync() system call to sync the database file to disk when committing a transaction. Syncing using fdatasync() is faster than syncing using fsync() as fdatasync() does not wait for the file metadata to be written to disk. +# SQLITE_DEFAULT_WORKER_THREADS=4: This option sets the default number of worker threads to use when doing parallel sorting and indexing. The default is 0 which means to use a single thread. The default for SQLITE_MAX_WORKER_THREADS is 8. +# SQLITE_MAX_PREPARE_RETRY=200: This option sets the maximum number of automatic re-preparation attempts that can occur after encountering a schema change. This can be caused by running ANALYZE which is done periodically by FTL. +set(SQLITE_DEFINES "-DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_PROGRESS_CALLBACK -DSQLITE_OMIT_SHARED_CACHE -DSQLITE_DEFAULT_FOREIGN_KEYS=1 -DSQLITE_DQS=0 -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_TEMP_STORE=2 -DSQLITE_DEFAULT_CACHE_SIZE=16384 -DSQLITE_DEFAULT_SYNCHRONOUS=1 -DSQLITE_LIKE_DOESNT_MATCH_BLOBS -DHAVE_MALLOC_USABLE_SIZE -DHAVE_FDATASYNC -DSQLITE_DEFAULT_WORKER_THREADS=4 -DSQLITE_MAX_PREPARE_RETRY=200") # Code hardening and debugging improvements # -fstack-protector-strong: The program will be resistant to having its stack overflowed @@ -46,8 +50,10 @@ set(SQLITE_DEFINES "-DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_DEFAULT_MEMSTATUS=0 -D # -Wl,-z,now: Disable lazy binding # -Wl,-z,relro: Read-only segments after relocation # -fno-common: Emit globals without explicit initializer from `.bss` to `.data`. This causes GCC to reject multiple definitions of global variables. This is the new default from GCC-10 on. -set(HARDENING_FLAGS "-fstack-protector-strong -Wp,-D_FORTIFY_SOURCE=2 -Wl,-z,relro,-z,now -fexceptions -funwind-tables -fasynchronous-unwind-tables -Wl,-z,defs -Wl,-z,now -Wl,-z,relro -fno-common") -set(DEBUG_FLAGS "-rdynamic -fno-omit-frame-pointer") +if (CMAKE_C_COMPILER_ID STREQUAL "GNU") + set(HARDENING_FLAGS "-fstack-protector-strong -Wp,-D_FORTIFY_SOURCE=2 -Wl,-z,relro,-z,now -fexceptions -funwind-tables -fasynchronous-unwind-tables -Wl,-z,defs -Wl,-z,now -Wl,-z,relro -fno-common") + set(DEBUG_FLAGS "-rdynamic -fno-omit-frame-pointer") +endif() # -Wall: This enables all the warnings about constructions that some users consider questionable, and that are easy to avoid (or modify to prevent the warning), even in conjunction with macros. This also enables some language-specific warnings described in C++ Dialect Options and Objective-C and Objective-C++ Dialect Options. # -Wextra: This enables some extra warning flags that are not enabled by -Wall. @@ -55,50 +61,153 @@ set(DEBUG_FLAGS "-rdynamic -fno-omit-frame-pointer") set(WARN_FLAGS "-Wall -Wextra -Wno-unused-parameter") # Extra warning flags we apply only to the FTL part of the code (used not for foreign code such as dnsmasq and SQLite3) -# -Werror: Halt on any warnings, useful for enforcing clean code without any warnings (we use it only for our code part) -# -Waddress: Warn about suspicious uses of memory addresses -# -Wlogical-op: Warn about suspicious uses of logical operators in expressions -# -Wmissing-field-initializers: Warn if a structure's initializer has some fields missing -# -Woverlength-strings: Warn about string constants that are longer than the "minimum maximum length specified in the C standard -# -Wformat: Check calls to printf and scanf, etc., to make sure that the arguments supplied have types appropriate to the format string specified, and that the conversions specified in the format string make sense. -# -Wformat-nonliteral: If -Wformat is specified, also warn if the format string is not a string literal and so cannot be checked, unless the format function takes its format arguments as a va_list. -# -Wuninitialized: Warn if an automatic variable is used without first being initialized -# -Wswitch-enum: Warn whenever a switch statement has an index of enumerated type and lacks a case for one or more of the named codes of that enumeration. -# -Wshadow: Warn whenever a local variable or type declaration shadows another variable, parameter, type, class member, or whenever a built-in function is shadowed. -# -Wfloat-equal: Warn if floating-point values are used in equality comparisons -# -Wpointer-arith: Warn about anything that depends on the "size of" a function type or of "void". GNU C assigns these types a size of 1 -# -Wundef: Warn if an undefined identifier is evaluated in an "#if" directive -# -Wbad-function-cast: Warn when a function call is cast to a non-matching type -# -Wwrite-strings: When compiling C, give string constants the type "const char[length]" so that copying the address of one into a non-"const" "char *" pointer produces a warning -# -Wparentheses: Warn if parentheses are omitted in certain contexts, such as when there is an assignment in a context where a truth value is expected, or when operators are nested whose precedence people often get confused about -# -Wlogical-op: Warn about suspicious uses of logical operators in expressions -# -Wstrict-prototypes: Warn if a function is declared or defined without specifying the argument types -# -Wmissing-prototypes: Warn if a global function is defined without a previous prototype declaration -# -Wredundant-decls: Warn if anything is declared more than once in the same scope -# -Winline: Warn if a function that is declared as inline cannot be inlined +set(EXTRAWARN_GCC6 "-Werror \ + -Waddress \ + -Wlogical-op \ + -Wmissing-field-initializers \ + -Woverlength-strings \ + -Wformat=2 \ + -Wformat-signedness \ + -Wuninitialized \ + -Wnull-dereference \ + -Wshift-overflow=2 \ + -Wunused-const-variable=2 \ + -Wstrict-aliasing \ + -Warray-bounds=2 \ + -Wno-aggressive-loop-optimizations \ + -Wswitch-enum \ + -Wshadow \ + -Wfloat-equal \ + -Wbad-function-cast \ + -Wwrite-strings \ + -Wparentheses \ + -Wlogical-op \ + -Wstrict-prototypes \ + -Wmissing-prototypes \ + -Wredundant-decls \ + -Wmissing-field-initializers \ + -Wnormalized=nfkc \ + -Woverride-init \ + -Wpacked \ + -Winline \ + -Wpacked \ + -Wredundant-decls \ + -Wnested-externs \ + -Wvla \ + -Wvector-operation-performance \ + -Wvolatile-register-var \ + -Wdisabled-optimization \ + -Wpointer-sign \ + -Wstack-protector \ + -Woverlength-strings") + +# Extra warnings flags available only in GCC 7 and higher +if(CMAKE_C_COMPILER_VERSION VERSION_EQUAL 7 OR CMAKE_C_COMPILER_VERSION VERSION_GREATER 7) + set(EXTRAWARN_GCC7 "-Wformat-overflow=2 \ + -Wformat-truncation=2 \ + -Wstringop-overflow=4 \ + -Walloc-zero \ + -Wint-in-bool-context") +else() + set(EXTRAWARN_GCC7 "") +endif() + +# Extra warnings flags available only in GCC 8 and higher if(CMAKE_C_COMPILER_VERSION VERSION_EQUAL 8 OR CMAKE_C_COMPILER_VERSION VERSION_GREATER 8) - # -Wduplicated-cond: Warn about duplicated conditions in an if-else-if chain - # -Wduplicated-branches: Warn when an if-else has identical branches - # -Wcast-align=strict: Warn whenever a pointer is cast such that the required alignment of the target is increased. For example, warn if a "char *" is cast to an "int *" regardless of the target machine. - # -Wlogical-not-parentheses: Warn about logical not used on the left hand side operand of a comparison - set(EXTRAWARN_GCC8 "-Wduplicated-cond -Wduplicated-branches -Wcast-align=strict -Wlogical-not-parentheses -Wsuggest-attribute=pure -Wsuggest-attribute=const -Wsuggest-attribute=malloc -Wsuggest-attribute=format -Wsuggest-attribute=cold") + set(EXTRAWARN_GCC8 "-Wduplicated-cond \ + -Wduplicated-branches \ + -Wcast-align=strict \ + -Wlogical-not-parentheses \ + -Wmultistatement-macros \ + -Wmissing-attributes \ + -Wsuggest-attribute=pure \ + -Wsuggest-attribute=const \ + -Wsuggest-attribute=malloc \ + -Wsuggest-attribute=format \ + -Wsuggest-attribute=cold") else() set(EXTRAWARN_GCC8 "") endif() -set(EXTRAWARN "-Werror -Waddress -Wlogical-op -Wmissing-field-initializers -Woverlength-strings -Wformat -Wformat-nonliteral -Wuninitialized -Wswitch-enum -Wshadow -Wfloat-equal -Wbad-function-cast -Wwrite-strings -Wparentheses -Wlogical-op -Wstrict-prototypes -Wmissing-prototypes -Wredundant-decls -Winline ${EXTRAWARN_GCC8}") + +# Extra warnings flags available only in GCC 9 and higher +# The only new warning -Wabsolute-value is implied by -Wextra + +# Extra warnings flags available only in GCC 10 and higher +# The new option -Wstring-compare is implied by -Wextra +# The new option -Wzero-length-bounds is implied by -Warray-bounds + +# Extra warnings flags available only in GCC 11 and higher +# All new options are implied by either -Wall or -Wextra \ + +# Extra warnings flags available only in GCC 12 and higher +if(CMAKE_C_COMPILER_VERSION VERSION_EQUAL 12 OR CMAKE_C_COMPILER_VERSION VERSION_GREATER 12) +set(EXTRAWARN_GCC12 "-Wbidi-chars \ + -Warray-compare") +else() +set(EXTRAWARN_GCC12 "") +endif() + +# Extra warnings flags available only in GCC 13 and higher +if(CMAKE_C_COMPILER_VERSION VERSION_EQUAL 13 OR CMAKE_C_COMPILER_VERSION VERSION_GREATER 13) +set(EXTRAWARN_GCC13 "-Wenum-int-mismatch") +else() +set(EXTRAWARN_GCC13 "") +endif() + +# Set extrawarn flags if CC is GCC +if (CMAKE_C_COMPILER_ID STREQUAL "GNU") + set(EXTRAWARN "${EXTRAWARN_GCC6} \ + ${EXTRAWARN_GCC7} \ + ${EXTRAWARN_GCC8} \ + ${EXTRAWARN_GCC12} \ + ${EXTRAWARN_GCC13}") +elseif (CMAKE_C_COMPILER_ID STREQUAL "Clang") + set(EXTRAWARN " + -Werror \ + -Wnewline-eof \ + -Wno-dangling-else \ + -Wno-gnu-zero-variadic-macro-arguments \ + -Wno-gnu-variable-sized-type-not-at-end \ + -Wno-declaration-after-statement \ + -Wno-reserved-identifier \ + -Wno-reserved-macro-identifier") +else() + message(WARNING "Unknown compiler, not setting warnings flags") + set(EXTRAWARN "") +endif() + +# Remove extra spaces from EXTRAWARN +string(REGEX REPLACE " +" " " EXTRAWARN "${EXTRAWARN}") + +# Separate EXTRAWARN into a list of arguments separate_arguments(EXTRAWARN) -# Do we want to compile a statically linked musl executable? -if(STATIC STREQUAL "true") +# -Wxor-used-as-pow + +# Do we want to compile a statically linked executable? +if(DEFINED ENV{STATIC}) + if($ENV{STATIC} STREQUAL "true") + set(STATIC true) + else() + set(STATIC false) + endif() +endif() +if(STATIC) + message(STATUS "Compiling statically linked executable") SET(CMAKE_FIND_LIBRARY_SUFFIXES ".a") SET(BUILD_SHARED_LIBS OFF) +else() + message(STATUS "Compiling dynamically linked executable") endif() # -pie -fPIE: (Dynamic) position independent executable -set(HARDENING_FLAGS "${HARDENING_FLAGS} -pie -fPIE") + +if (CMAKE_C_COMPILER_ID STREQUAL "GNU") + set(HARDENING_FLAGS "${HARDENING_FLAGS} -pie -fPIE") +endif() # -FILE_OFFSET_BITS=64: used by stat(). Avoids problems with files > 2 GB on 32bit machines # We define HAVE_POLL_H as this is needed for the musl builds to succeed -set(CMAKE_C_FLAGS "-pipe ${WARN_FLAGS} -D_FILE_OFFSET_BITS=64 ${HARDENING_FLAGS} ${DEBUG_FLAGS} ${CMAKE_C_FLAGS} -DHAVE_POLL_H ${SQLITE_DEFINES}") +set(CMAKE_C_FLAGS "-std=c99 -pipe ${WARN_FLAGS} -D_FILE_OFFSET_BITS=64 ${HARDENING_FLAGS} ${DEBUG_FLAGS} ${CMAKE_C_FLAGS} -DHAVE_POLL_H ${SQLITE_DEFINES}") set(CMAKE_C_FLAGS_DEBUG "-O0 -g3") set(CMAKE_C_FLAGS_RELEASE "-O3 -DNDEBUG") @@ -110,8 +219,6 @@ set(sources args.h capabilities.c capabilities.h - config.c - config.h daemon.c daemon.h datastructure.c @@ -132,6 +239,7 @@ set(sources log.h main.c main.h + metrics.h overTime.c overTime.h procps.c @@ -140,8 +248,6 @@ set(sources regex_r.h resolve.c resolve.h - setupVars.c - setupVars.h shmem.c shmem.h signals.c @@ -162,94 +268,110 @@ add_custom_target( COMMAND ${CMAKE_COMMAND} -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} -P ${CMAKE_CURRENT_SOURCE_DIR}/gen_version.cmake WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) -add_library(FTL OBJECT ${sources}) -target_compile_options(FTL PRIVATE ${EXTRAWARN}) -target_compile_definitions(FTL PRIVATE DNSMASQ_VERSION=\"${DNSMASQ_VERSION}\") -target_include_directories(FTL PRIVATE ${PROJECT_SOURCE_DIR}/src) -add_dependencies(FTL gen_version) - +add_library(core OBJECT ${sources}) +target_compile_options(core PRIVATE ${EXTRAWARN}) +target_compile_definitions(core PRIVATE DNSMASQ_VERSION=\"${DNSMASQ_VERSION}\") +target_include_directories(core PRIVATE ${PROJECT_SOURCE_DIR}/src) +add_dependencies(core gen_version) add_executable(pihole-FTL - $ + $ $ + $ + $ + $ + $ + $ + $ $ $ $ $ + $ $ $ + $ + $ $ ) -if(STATIC STREQUAL "true") +if(STATIC) set_target_properties(pihole-FTL PROPERTIES LINK_SEARCH_START_STATIC ON) set_target_properties(pihole-FTL PROPERTIES LINK_SEARCH_END_STATIC ON) - target_link_libraries(pihole-FTL -static-libgcc -static -pie) + target_link_libraries(pihole-FTL -static-libgcc -static) + set(LIBRARY_SUFFIX "${CMAKE_STATIC_LIBRARY_SUFFIX}") else() find_library(LIBMATH m) target_link_libraries(pihole-FTL ${LIBMATH}) + set(LIBRARY_SUFFIX "") endif() set(CMAKE_THREAD_PREFER_PTHREAD TRUE) set(THREADS_PREFER_PTHREAD_FLAG TRUE) find_package(Threads REQUIRED) # for DNSSEC we need the nettle (+ hogweed) crypto and the gmp math libraries -find_library(LIBHOGWEED NAMES libhogweed${CMAKE_STATIC_LIBRARY_SUFFIX} hogweed) -find_library(LIBGMP NAMES libgmp${CMAKE_STATIC_LIBRARY_SUFFIX} gmp) -find_library(LIBNETTLE NAMES libnettle${CMAKE_STATIC_LIBRARY_SUFFIX} nettle) -find_library(LIBIDN NAMES libidn${CMAKE_STATIC_LIBRARY_SUFFIX} idn) +find_library(LIBHOGWEED NAMES libhogweed${LIBRARY_SUFFIX} hogweed HINTS /usr/local/lib64) +find_library(LIBGMP NAMES libgmp${LIBRARY_SUFFIX} gmp) +find_library(LIBNETTLE NAMES libnettle${LIBRARY_SUFFIX} nettle HINTS /usr/local/lib64) -target_link_libraries(pihole-FTL rt Threads::Threads ${LIBHOGWEED} ${LIBGMP} ${LIBNETTLE} ${LIBIDN}) +# for IDN2 we need the idn2 library which in turn depends on the unistring library +find_library(LIBIDN2 NAMES libidn2${LIBRARY_SUFFIX} idn2) +find_library(LIBUNISTRING NAMES libunistring${LIBRARY_SUFFIX} unistring) + +target_link_libraries(pihole-FTL rt Threads::Threads ${LIBHOGWEED} ${LIBGMP} ${LIBNETTLE} ${LIBIDN2} ${LIBUNISTRING}) if(LUA_DL STREQUAL "true") find_library(LIBDL dl) target_link_libraries(pihole-FTL ${LIBDL}) endif() -find_library(LIBREADLINE NAMES libreadline${CMAKE_STATIC_LIBRARY_SUFFIX} readline) -find_library(LIBHISTORY NAMES libhistory${CMAKE_STATIC_LIBRARY_SUFFIX} history) -find_library(LIBTERMCAP NAMES libtermcap${CMAKE_STATIC_LIBRARY_SUFFIX} termcap) +add_subdirectory(api) +add_subdirectory(webserver) +add_subdirectory(zip) +add_subdirectory(database) +add_subdirectory(dnsmasq) +add_subdirectory(lua) +add_subdirectory(lua/scripts) +add_subdirectory(tre-regex) +add_subdirectory(syscalls) +add_subdirectory(config) +add_subdirectory(tools) + +find_library(LIBREADLINE NAMES libreadline${LIBRARY_SUFFIX} readline) +find_library(LIBHISTORY NAMES libhistory${LIBRARY_SUFFIX} history) +find_library(LIBTERMCAP NAMES libtermcap${LIBRARY_SUFFIX} termcap) if(LIBREADLINE AND LIBHISTORY AND LIBTERMCAP) message(STATUS "Building FTL with readline support: YES") - target_compile_definitions(FTL PRIVATE LUA_USE_READLINE) - target_compile_definitions(pihole-FTL PRIVATE LUA_USE_READLINE) + target_compile_definitions(lua PRIVATE LUA_USE_READLINE) + target_compile_definitions(sqlite3 PRIVATE HAVE_READLINE) target_link_libraries(pihole-FTL ${LIBREADLINE} ${LIBHISTORY} ${LIBTERMCAP}) else() message(STATUS "Building FTL with readline support: NO") endif() -# Do we want to compile an all-in FTL version? -if(DEFINED ENV{CI_ARCH}) - if($ENV{CI_ARCH} STREQUAL "x86_64_full") - add_definitions(-DDNSMASQ_ALL_OPTS) - set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}) - find_package(DBus REQUIRED) - # Use results of find_package() call. - include_directories(${DBUS_INCLUDE_DIRS}) - target_link_libraries(pihole-FTL ${DBUS_LIBRARIES}) - find_library(LIBMNL mnl) - find_library(LIBNFTNL nftnl) - find_library(LIBNFTABLES nftables) - find_library(LIBNFNETLINK nfnetlink) - find_library(LIBNETFILTER_CONNTRACK netfilter_conntrack) - target_link_libraries(pihole-FTL ${LIBMNL} ${LIBNFTABLES} ${LIBNFTNL} ${LIBNFNETLINK} ${LIBNETFILTER_CONNTRACK}) - endif() -endif() - if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "/usr" CACHE PATH "..." FORCE) endif() +find_library(LIBMBEDCRYPTO NAMES lmbedcrypto${LIBRARY_SUFFIX} mbedcrypto) +find_library(LIBMBEDX509 NAMES lmbedx509${LIBRARY_SUFFIX} mbedx509) +find_library(LIBMBEDTLS NAMES lmbedtls${LIBRARY_SUFFIX} mbedtls) +if(LIBMBEDCRYPTO AND LIBMBEDX509 AND LIBMBEDTLS) + # Enable TLS support in civetweb if mbedTLS is available + message(STATUS "Building FTL with TLS support: YES") + target_compile_definitions(core PRIVATE HAVE_MBEDTLS) + target_compile_definitions(civetweb PRIVATE USE_MBEDTLS) + target_compile_definitions(webserver PRIVATE HAVE_MBEDTLS) + # Link against the mbedTLS libraries, the order is important (!) + target_link_libraries(pihole-FTL ${LIBMBEDTLS} ${LIBMBEDX509} ${LIBMBEDCRYPTO}) +else() + # Disable TLS support in civetweb if mbedTLS is not available + message(STATUS "Building FTL with TLS support: NO") + target_compile_definitions(civetweb PRIVATE NO_SSL) +endif() + find_program(SETCAP setcap) install(TARGETS pihole-FTL RUNTIME DESTINATION bin PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) -install(CODE "execute_process(COMMAND ${SETCAP} CAP_NET_BIND_SERVICE,CAP_NET_RAW,CAP_NET_ADMIN,CAP_SYS_NICE+eip \$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/bin/pihole-FTL)") +install(CODE "execute_process(COMMAND ${SETCAP} CAP_NET_BIND_SERVICE,CAP_NET_RAW,CAP_NET_ADMIN,CAP_SYS_NICE,CAP_CHOWN+eip \$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/bin/pihole-FTL)") -add_subdirectory(api) -add_subdirectory(database) -add_subdirectory(dnsmasq) -add_subdirectory(lua) -add_subdirectory(lua/scripts) -add_subdirectory(tre-regex) -add_subdirectory(syscalls) -add_subdirectory(tools) diff --git a/src/FTL.h b/src/FTL.h index 4c819e196..69b88b5d1 100644 --- a/src/FTL.h +++ b/src/FTL.h @@ -39,8 +39,6 @@ #include // tolower() #include -// Unix socket -#include // Interfaces #include #include @@ -49,6 +47,10 @@ #define MAX(x,y) (((x) > (y)) ? (x) : (y)) // MIN(x,y) is already defined in dnsmasq.h +// Number of elements in an array +#define ArraySize(X) (sizeof(X)/sizeof(X[0])) + +// Constant socket buffer length #define SOCKETBUFFERLEN 1024 // How often do we garbage collect (to ensure we only have data fitting to the MAXLOGAGE defined above)? [seconds] @@ -56,7 +58,7 @@ #define GCinterval 600 // Delay applied to the garbage collecting [seconds] -// Default: -60 (one minute before a full hour) +// Default: -60 (one minute before the end of the interval set above) #define GCdelay (-60) // How many client connection do we accept at once? @@ -102,6 +104,10 @@ // Default: 1000 (one second) #define DATABASE_BUSY_TIMEOUT 1000 +// After how much time does a valid API session expire? [seconds] +// Default: 300 (five minutes) +#define API_SESSION_EXPIRE 300u + // After how many seconds do we check again if a client can be identified by other means? // (e.g., interface, MAC address, hostname) // Default: 60 (after one minutee) @@ -118,12 +124,31 @@ // Default: 180 [seconds] #define DELAY_UPTIME 180 +// REPLY_TIMEOUT defines until how far back in the history of queries we are +// checking for changed/updated queries. This value should not be set too high +// to avoid unnecessary spinning in the updating loop of the queries running +// every second. The value should be set to a value that is high enough to +// catch all queries that are still in the process of being resolved. +// Default: 30 [seconds] +#define REPLY_TIMEOUT 30 + +// Special exit code used to signal that FTL wants to restart +#define RESTART_FTL_CODE 22 + +// How often should the database be analyzed? +// Default: 604800 (once per week) +#define DATABASE_ANALYZE_INTERVAL 604800 + +// How often should we update client vendor's from the MAC vendor database? +// Default: 2592000 (once per month) +#define DATABASE_MACVENDOR_INTERVAL 2592000 + // Use out own syscalls handling functions that will detect possible errors // and report accordingly in the log. This will make debugging FTL crash // caused by insufficient memory or by code bugs (not properly dealing // with NULL pointers) much easier. #undef strdup // strdup() is a macro in itself, it needs special handling -#define free(ptr) FTLfree(ptr, __FILE__, __FUNCTION__, __LINE__) +#define free(ptr) FTLfree((void**)&ptr, __FILE__, __FUNCTION__, __LINE__) #define strdup(str_in) FTLstrdup(str_in, __FILE__, __FUNCTION__, __LINE__) #define calloc(numer_of_elements, element_size) FTLcalloc(numer_of_elements, element_size, __FILE__, __FUNCTION__, __LINE__) #define realloc(ptr, new_size) FTLrealloc(ptr, new_size, __FILE__, __FUNCTION__, __LINE__) @@ -146,10 +171,32 @@ #define pthread_mutex_lock(mutex) FTLpthread_mutex_lock(mutex, __FILE__, __FUNCTION__, __LINE__) #define fopen(pathname, mode) FTLfopen(pathname, mode, __FILE__, __FUNCTION__, __LINE__) #define ftlallocate(fd, offset, len) FTLfallocate(fd, offset, len, __FILE__, __FUNCTION__, __LINE__) +#define strlen(str) FTLstrlen(str, __FILE__, __FUNCTION__, __LINE__) +#define strnlen(str, maxlen) FTLstrnlen(str, maxlen, __FILE__, __FUNCTION__, __LINE__) +#define strcpy(dest, src) FTLstrcpy(dest, src, __FILE__, __FUNCTION__, __LINE__) +#define strncpy(dest, src, n) FTLstrncpy(dest, src, n, __FILE__, __FUNCTION__, __LINE__) +#define memset(s, c, n) FTLmemset(s, c, n, __FILE__, __FUNCTION__, __LINE__) +#define memcpy(dest, src, n) FTLmemcpy(dest, src, n, __FILE__, __FUNCTION__, __LINE__) +#define memmove(dest, src, n) FTLmemmove(dest, src, n, __FILE__, __FUNCTION__, __LINE__) +#define strstr(haystack, needle) FTLstrstr(haystack, needle, __FILE__, __FUNCTION__, __LINE__) +#define strcmp(s1, s2) FTLstrcmp(s1, s2, __FILE__, __FUNCTION__, __LINE__) +#define strncmp(s1, s2, n) FTLstrncmp(s1, s2, n, __FILE__, __FUNCTION__, __LINE__) +#define strcasecmp(s1, s2) FTLstrcasecmp(s1, s2, __FILE__, __FUNCTION__, __LINE__) +#define strncasecmp(s1, s2, n) FTLstrncasecmp(s1, s2, n, __FILE__, __FUNCTION__, __LINE__) +#define strcat(dest, src) FTLstrcat(dest, src, __FILE__, __FUNCTION__, __LINE__) +#define strncat(dest, src, n) FTLstrncat(dest, src, n, __FILE__, __FUNCTION__, __LINE__) +#define memcmp(s1, s2, n) FTLmemcmp(s1, s2, n, __FILE__, __FUNCTION__, __LINE__) +#define memmem(haystack, haystacklen, needle, needlelen) FTLmemmem(haystack, haystacklen, needle, needlelen, __FILE__, __FUNCTION__, __LINE__) #include "syscalls/syscalls.h" // Preprocessor help functions -#define str(x) # x +#define str(x) #x #define xstr(x) str(x) +// Intentionally ignore result of function declared warn_unused_result +#define igr(x) {__typeof__(x) __attribute__((unused)) d=(x);} + +#define max(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a > _b ? _a : _b; }) +#define min(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a < _b ? _a : _b; }) + #endif // FTL_H diff --git a/src/api/2fa.c b/src/api/2fa.c new file mode 100644 index 000000000..4e255df2f --- /dev/null +++ b/src/api/2fa.c @@ -0,0 +1,376 @@ +/* Pi-hole: A black hole for Internet advertisements +* (c) 2023 Pi-hole, LLC (https://pi-hole.net) +* Network-wide ad blocking via your own hardware. +* +* FTL Engine +* API Implementation 2FA methods +* +* This file is copyright under the latest version of the EUPL. +* Please see LICENSE file for your rights under this license. */ + +#include "FTL.h" +#include "api/api.h" +#include "webserver/json_macros.h" +#include "log.h" +#include "config/config.h" +// getrandom() +#include "daemon.h" +// generate_app_password() +#include "config/password.h" + +// TOTP+HMAC +#include +#include + +static uint32_t hotp(const uint8_t *key, size_t key_len, const uint64_t counter, const uint8_t digits) +{ + // Initialize HMAC-SHA1 (RFC 2104) + // TOTP uses HMAC-SHA1 (RFC 6238, section 5.1) + struct hmac_sha1_ctx ctx; + hmac_sha1_set_key(&ctx, key_len, key); + + // Convert counter to big endian + const uint64_t counter_be = htobe64(counter); + + // Compute HMAC-SHA1 + hmac_sha1_update(&ctx, sizeof(counter_be), (uint8_t*)&counter_be); + uint8_t out[SHA1_DIGEST_SIZE]; + hmac_sha1_digest(&ctx, SHA1_DIGEST_SIZE, out); + + // Truncate HMAC-SHA1 for ease of use + // RFC 6238 (section 5.3): offset = last nibble of hash + const uint8_t offset = out[SHA1_DIGEST_SIZE-1] & 0x0F; + // RFC 6238 (section 5.3): binary = (hash[offset] & 0x7F) << 24 | + // (hash[offset+1] & 0xFF) << 16 | + // (hash[offset+2] & 0xFF) << 8 | + // (hash[offset+3] & 0xFF) + const uint32_t binary = (out[offset] & 0x7F) << 24 | + (out[offset+1] & 0xFF) << 16 | + (out[offset+2] & 0xFF) << 8 | + (out[offset+3] & 0xFF); + // RFC 6238 (section 5.3): HOTP = binary mod 10^digits + uint32_t mask = 10; + for(unsigned int i = 1; i < digits; i++) + mask *= 10; + return binary % mask; +} + +// RFC 6238 (section 4.1): T0 is the Unix time to start counting time steps +// (default value is 0, i.e., the Unix epoch) and is also a system parameter. +#define RFC6238_T0 0 +// RFC 6238 (section 5.2): We RECOMMEND a default time-step size of 30 seconds. +// This default value of 30 seconds is selected as a balance between security +// and usability. +#define RFC6238_X 30 +// RFC 6238 (section 4, R6): The algorithm MUST use a strong shared secret. The +// length of the shared secret MUST be at least 128 bits (16 Byte). This +// document RECOMMENDs a shared secret length of 160 bits (20 Byte). +#define RFC6238_SECRET_LEN 160/8 +// The number of digits to truncate to is not specified in RFC 6238. RFC 4226 +// (section 5.3) specifies that the default is 6 (up to 8) digits, however, the +// example given in RFC 6238 uses 8 digits. +#define RFC6238_DIGITS 6 + +static uint32_t totp(const uint8_t *key, const size_t key_len, const time_t now) +{ + // Get time + // RFC 6238 (section 4.2): T = (Current Unix time - T0) / X + // T is an integer and represents the number of time steps between the + // initial time T0 and the current time. T needs to be big endian + const uint64_t T = (now - RFC6238_T0) / RFC6238_X; + + // RFC 6238 (section 4.2): TOTP(K, T) = HOTP(K,C) with C = T + return hotp(key, key_len, T, RFC6238_DIGITS); +} + +static bool decode_base32_to_uint8_array(const char *base32, uint8_t *out, const size_t out_len) +{ + // Base32 alphabet + const char *b32 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + + // Check input for validity + if(out_len == 0 || out_len*8/5 < strlen(base32) || out_len*8%5 != 0) + { + log_err("Decoding base32 2FA secret failed, invalid length (%zu)", out_len); + return false; + } + + // Initialize output array + memset(out, 0, out_len); + + // Iterate over input string + size_t out_pos = 0u; + for(size_t i = 0; i < strlen(base32); i++) + { + // Get current character + const char c = base32[i]; + + // Get position of current character in base32 alphabet + const char *c_pos = strchr(b32, toupper(c)); + if(c_pos == NULL) + { + log_err("Decoding base32 2FA secret failed, invalid character '%c'", c); + return false; + } + + // Get value of current character + const uint8_t c_val = (uint8_t)(c_pos-b32); + + // Iterate over 5 bits of the current character + for(unsigned int j = 0; j < 5; j++) + { + // Current bit position + const unsigned int bit = 4-j; + + // Get current bit in the current character + const uint8_t c_bit = (c_val >> bit) & 1; + + // Get current byte position in the output array + out_pos = (i*5+j)/8; + + // If we are out of bounds, return false + if(out_pos >= out_len) + { + log_err("Decoding base32 2FA secret failed, out of bounds (%zu >= %zu)", out_pos, out_len); + return false; + } + + // Set current bit in the output array + out[out_pos] |= c_bit << (7-((i*5+j)%8)); + } + } + + return true; +} + +static bool encode_uint8_t_array_to_base32(const uint8_t *in, const size_t in_len, char *base32, size_t base32_len) +{ + // Base32 alphabet + const char *b32 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + + // Check input for validity + if(in_len == 0 || in_len > base32_len*5/8 || in_len%5 != 0) + { + log_err("Encoding base32 2FA secret failed, invalid input length"); + return false; + } + + // Initialize base32 output array + memset(base32, 0, base32_len); + + // Iterate over input string + size_t base32_pos = 0u; + for(size_t i = 0; i < in_len; i++) + { + // Get current byte + const uint8_t b = in[i]; + + // Iterate over 8 bits of the current byte + for(unsigned int j = 0; j < 8; j++) + { + // Current bit position + const unsigned int bit = 7-j; + + // Get current bit in the current byte + const uint8_t b_bit = (b >> bit) & 1; + + // Get current byte position in the base32 output array + base32_pos = (i*8+j)/5; + + // If we are out of bounds, return false + if(base32_pos >= base32_len) + { + log_err("Decoding base32 2FA secret failed, base32 output array is too small"); + return false; + } + + // Set current bit in the base32 output array + base32[base32_pos] |= b_bit << (4-((i*8+j)%5)); + } + } + + // Iterate over base32 output array and replace each byte with its + // corresponding character in the base32 alphabet + for(size_t i = 0; i <= base32_pos; i++) + base32[i] = b32[(uint8_t)base32[i]]; + + return true; +} + +static uint32_t last_code = 0; +enum totp_status verifyTOTP(const uint32_t incode) +{ + // Decode base32 secret + uint8_t decoded_secret[RFC6238_SECRET_LEN]; + if(!decode_base32_to_uint8_array(config.webserver.api.totp_secret.v.s, decoded_secret, sizeof(decoded_secret))) + return false; + + // Get current time + const time_t now = time(NULL); + + // Verify code for the previous, the current and the next time step + for(int i = -1; i <= 1; i++) + { + const uint32_t gencode = totp(decoded_secret, sizeof(decoded_secret), now + i*RFC6238_X); + + // Verify code + // RFC 6238 (section 4.2): If the calculated value matches the value + // provided by the user, then the user is authenticated + // RFC 6238 (section 4.3): The server MUST NOT accept a TOTP value + // generated more than 30 seconds in the future + // RFC 6238 (section 4.3): The server MUST NOT accept a TOTP value + // generated more than 30 seconds in the past + // RFC 6238 (section 4.3): The server MUST NOT accept a TOTP value + // it accepted previously + if(gencode == incode) + { + if(gencode == last_code) + { + log_warn("2FA code has already been used (%i, %u), please wait %lu seconds", + i, gencode, (unsigned long)(RFC6238_X - (now % RFC6238_X))); + return TOTP_REUSED; + } + const char *which = i == -1 ? "previous" : i == 0 ? "current" : "next"; + log_debug(DEBUG_API, "2FA code from %s time step is valid", which); + last_code = gencode; + return TOTP_CORRECT; + } + } + + return TOTP_INVALID; +} + +// Print TOTP code to stdout (for CLI use) +int printTOTP(void) +{ + if(strlen(config.webserver.api.totp_secret.v.s) == 0) + { + puts("0"); + return EXIT_SUCCESS; + } + // Decode base32 secret + uint8_t decoded_secret[RFC6238_SECRET_LEN]; + if(!decode_base32_to_uint8_array(config.webserver.api.totp_secret.v.s, decoded_secret, sizeof(decoded_secret))) + return EXIT_FAILURE; + + // Get current time + const time_t now = time(NULL); + const uint32_t code = totp(decoded_secret, sizeof(decoded_secret), now); + + printf("%u\n", code); + + return EXIT_SUCCESS; +} + + +// A QR code may be generated from the data using +// otpauth://totp/